commit 91f63d6bae16f95f86a53300cfbcf0d96754173b parent e21815e6380e4d963d1567140dbe25277bd5e8be Author: Jim Blandy <jimb@red-bean.com> Date: Tue, 23 Dec 2025 12:42:11 +0000 Bug 2005294: Update wgpu to upstream 3f02781b (2025-12-16) r=webgpu-reviewers,supply-chain-reviewers,nical,aleiserson - Update wgpu crates to trunk commit 3f02781b. This is wgpu 28.0.0. - Update CTS expectations. Many tests promoted from backlog! - Adjust `wgpu_bindings::client::BindGroupEntry` to allow zero to be passed as a buffer binding size. With wgpu#8734, wgpu_core validation will catch this and report it properly, - Pass `None` for the `telemetry` argument to `wgpu_core::global::Global::new`; bug 1980057 will fill this in with a real telemetry struct. - Adapt to new `DontCare` variant in `wgc::command::LoadOp`. Forbid use of `DontCare` in the GPU process. In generated C++ bindings for wgpu-types, substitute our own definition of `WGPULoadOpDontCare`, since we don't use this and cbindgen can't create correct C++ for zero-sized types. - Include `subgroup_min_size` and `subgroup_max_size` in `AdapterInformation` struct. - Adapt to upstream terminology change: "push constants" are now "immediate data". Delete unused function `wgpu_render_bundle_set_push_constants`. - Don't bother to lock the value of `wgpu_hal::metal::Device::raw_device`. As of wgpu#8168, this function simply returns a `&metal::Device` directly. - Delete our `VkImageHolder` type, and use `wgpu_hal::vulkan`'s very nice `TextureMemory` enum instead. - Include a delta audit for the wgpu crates from version 27.0.0 to 28.0.0, since these entries are more valuable to people importing our `audits.toml` file than entries for specific git commits. - Update metal to 0.33.0. - Update vendored ron to 0.12.0. Delta audit is already present in `supply-chain/audits.toml`. Update webrender to use this as well. - Vendor new ron dependency typeid 1.0.3. This is a very small crate. Trust entry is already present in `supply-chain/audits.toml`. - Delete dependencies `gpu-alloc` and `gpu-alloc-types` from `third_party/rust`. These are no longer needed, since all `wgpu-hal` backends now use the `gpu-allocator` crate. - Update WebRender's local `Cargo.lock` to match the top-level's `once_cell`, `ron`, and `typeid` entries. Differential Revision: https://phabricator.services.mozilla.com/D275914 Diffstat:
289 files changed, 24958 insertions(+), 24696 deletions(-)
diff --git a/.cargo/config.toml.in b/.cargo/config.toml.in @@ -35,9 +35,9 @@ git = "https://github.com/franziskuskiefer/cose-rust" rev = "43c22248d136c8b38fe42ea709d08da6355cf04b" replace-with = "vendored-sources" -[source."git+https://github.com/gfx-rs/wgpu?rev=9a975b24deb379b31a8a7cb999d2da5f3227a91a"] +[source."git+https://github.com/gfx-rs/wgpu?rev=3f02781bb5a0a1fe1922ea36c9bdacf9792abcbc"] git = "https://github.com/gfx-rs/wgpu" -rev = "9a975b24deb379b31a8a7cb999d2da5f3227a91a" +rev = "3f02781bb5a0a1fe1922ea36c9bdacf9792abcbc" replace-with = "vendored-sources" [source."git+https://github.com/glandium/allocator-api2?rev=ad5f3d56a5a4519eff52af4ff85293431466ef5c"] diff --git a/Cargo.lock b/Cargo.lock @@ -2837,30 +2837,12 @@ dependencies = [ ] [[package]] -name = "gpu-alloc" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" -dependencies = [ - "bitflags 2.9.0", - "gpu-alloc-types", -] - -[[package]] -name = "gpu-alloc-types" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" -dependencies = [ - "bitflags 2.9.0", -] - -[[package]] name = "gpu-allocator" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51255ea7cfaadb6c5f1528d43e92a82acb2b96c43365989a28b2d44ee38f8795" dependencies = [ + "ash", "hashbrown 0.16.0", "log", "presser", @@ -4175,9 +4157,9 @@ dependencies = [ [[package]] name = "metal" -version = "0.32.0" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00c15a6f673ff72ddcc22394663290f870fb224c1bfce55734a75c414150e605" +checksum = "c7047791b5bc903b8cd963014b355f71dc9864a9a0b727057676c1dcae5cbc15" dependencies = [ "bitflags 2.9.0", "block", @@ -4801,8 +4783,8 @@ checksum = "a2983372caf4480544083767bf2d27defafe32af49ab4df3a0b7fc90793a3664" [[package]] name = "naga" -version = "27.0.0" -source = "git+https://github.com/gfx-rs/wgpu?rev=9a975b24deb379b31a8a7cb999d2da5f3227a91a#9a975b24deb379b31a8a7cb999d2da5f3227a91a" +version = "28.0.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=3f02781bb5a0a1fe1922ea36c9bdacf9792abcbc#3f02781bb5a0a1fe1922ea36c9bdacf9792abcbc" dependencies = [ "arrayvec", "bit-set", @@ -6051,14 +6033,15 @@ dependencies = [ [[package]] name = "ron" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db09040cc89e461f1a265139777a2bde7f8d8c67c4936f700c63ce3e2904d468" +checksum = "fd490c5b18261893f14449cbd28cb9c0b637aebf161cd77900bfdedaff21ec32" dependencies = [ - "base64 0.22.1", "bitflags 2.9.0", + "once_cell", "serde", "serde_derive", + "typeid", "unicode-ident", ] @@ -7283,6 +7266,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfc9d8d4e8c94375df96d6ac01a18c263d3d529bc4a53a207580ae9bc30e87c1" [[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] name = "typenum" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -7992,8 +7981,8 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "27.0.0" -source = "git+https://github.com/gfx-rs/wgpu?rev=9a975b24deb379b31a8a7cb999d2da5f3227a91a#9a975b24deb379b31a8a7cb999d2da5f3227a91a" +version = "28.0.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=3f02781bb5a0a1fe1922ea36c9bdacf9792abcbc#3f02781bb5a0a1fe1922ea36c9bdacf9792abcbc" dependencies = [ "arrayvec", "bit-set", @@ -8023,24 +8012,24 @@ dependencies = [ [[package]] name = "wgpu-core-deps-apple" -version = "27.0.0" -source = "git+https://github.com/gfx-rs/wgpu?rev=9a975b24deb379b31a8a7cb999d2da5f3227a91a#9a975b24deb379b31a8a7cb999d2da5f3227a91a" +version = "28.0.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=3f02781bb5a0a1fe1922ea36c9bdacf9792abcbc#3f02781bb5a0a1fe1922ea36c9bdacf9792abcbc" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-core-deps-windows-linux-android" -version = "27.0.0" -source = "git+https://github.com/gfx-rs/wgpu?rev=9a975b24deb379b31a8a7cb999d2da5f3227a91a#9a975b24deb379b31a8a7cb999d2da5f3227a91a" +version = "28.0.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=3f02781bb5a0a1fe1922ea36c9bdacf9792abcbc#3f02781bb5a0a1fe1922ea36c9bdacf9792abcbc" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-hal" -version = "27.0.0" -source = "git+https://github.com/gfx-rs/wgpu?rev=9a975b24deb379b31a8a7cb999d2da5f3227a91a#9a975b24deb379b31a8a7cb999d2da5f3227a91a" +version = "28.0.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=3f02781bb5a0a1fe1922ea36c9bdacf9792abcbc#3f02781bb5a0a1fe1922ea36c9bdacf9792abcbc" dependencies = [ "android_system_properties", "arrayvec", @@ -8052,7 +8041,6 @@ dependencies = [ "cfg-if", "cfg_aliases", "core-graphics-types 0.2.0", - "gpu-alloc", "gpu-allocator", "gpu-descriptor", "hashbrown 0.16.0", @@ -8077,8 +8065,8 @@ dependencies = [ [[package]] name = "wgpu-types" -version = "27.0.0" -source = "git+https://github.com/gfx-rs/wgpu?rev=9a975b24deb379b31a8a7cb999d2da5f3227a91a#9a975b24deb379b31a8a7cb999d2da5f3227a91a" +version = "28.0.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=3f02781bb5a0a1fe1922ea36c9bdacf9792abcbc#3f02781bb5a0a1fe1922ea36c9bdacf9792abcbc" dependencies = [ "bitflags 2.9.0", "bytemuck", diff --git a/dom/webgpu/Device.cpp b/dom/webgpu/Device.cpp @@ -472,6 +472,7 @@ already_AddRefed<BindGroup> Device::CreateBindGroup( } e.buffer = buffer->GetId(); e.offset = 0; + e.size_passed = false; e.size = 0; } else if (entry.mResource.IsGPUBufferBinding()) { const auto& bufBinding = entry.mResource.GetAsGPUBufferBinding(); @@ -481,7 +482,12 @@ already_AddRefed<BindGroup> Device::CreateBindGroup( } e.buffer = bufBinding.mBuffer->GetId(); e.offset = bufBinding.mOffset; - e.size = bufBinding.mSize.WasPassed() ? bufBinding.mSize.Value() : 0; + e.size_passed = bufBinding.mSize.WasPassed(); + if (e.size_passed) { + e.size = bufBinding.mSize.Value(); + } else { + e.size = 0; + } } else if (entry.mResource.IsGPUTexture()) { auto texture = entry.mResource.GetAsGPUTexture(); const dom::GPUTextureViewDescriptor defaultDesc{}; diff --git a/gfx/wgpu_bindings/Cargo.toml b/gfx/wgpu_bindings/Cargo.toml @@ -17,7 +17,7 @@ default = [] [dependencies.wgc] package = "wgpu-core" git = "https://github.com/gfx-rs/wgpu" -rev = "9a975b24deb379b31a8a7cb999d2da5f3227a91a" +rev = "3f02781bb5a0a1fe1922ea36c9bdacf9792abcbc" features = ["serde", "trace", "strict_asserts", "wgsl", "api_log_info"] # We want the wgpu-core Metal backend on macOS and iOS. @@ -25,32 +25,32 @@ features = ["serde", "trace", "strict_asserts", "wgsl", "api_log_info"] [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies.wgc] package = "wgpu-core" git = "https://github.com/gfx-rs/wgpu" -rev = "9a975b24deb379b31a8a7cb999d2da5f3227a91a" +rev = "3f02781bb5a0a1fe1922ea36c9bdacf9792abcbc" features = ["metal"] # We want the wgpu-core Direct3D backends on Windows. [target.'cfg(windows)'.dependencies.wgc] package = "wgpu-core" git = "https://github.com/gfx-rs/wgpu" -rev = "9a975b24deb379b31a8a7cb999d2da5f3227a91a" +rev = "3f02781bb5a0a1fe1922ea36c9bdacf9792abcbc" features = ["dx12"] # We want the wgpu-core Vulkan backend on Linux and Windows. [target.'cfg(any(windows, all(unix, not(any(target_os = "macos", target_os = "ios")))))'.dependencies.wgc] package = "wgpu-core" git = "https://github.com/gfx-rs/wgpu" -rev = "9a975b24deb379b31a8a7cb999d2da5f3227a91a" +rev = "3f02781bb5a0a1fe1922ea36c9bdacf9792abcbc" features = ["vulkan"] [dependencies.wgt] package = "wgpu-types" git = "https://github.com/gfx-rs/wgpu" -rev = "9a975b24deb379b31a8a7cb999d2da5f3227a91a" +rev = "3f02781bb5a0a1fe1922ea36c9bdacf9792abcbc" [dependencies.wgh] package = "wgpu-hal" git = "https://github.com/gfx-rs/wgpu" -rev = "9a975b24deb379b31a8a7cb999d2da5f3227a91a" +rev = "3f02781bb5a0a1fe1922ea36c9bdacf9792abcbc" features = ["device_lost_panic", "internal_error_panic"] [target.'cfg(windows)'.dependencies] @@ -60,7 +60,7 @@ windows = { version = "0.62", default-features = false, features = [ [target.'cfg(target_os = "macos")'.dependencies] objc = "0.2" -metal = "0.32" +metal = "0.33" io-surface = "0.15" # TODO(bug 1984493): The `block2` crate will be used by wgpu in the near # future. The direct dependency on it can be removed when wgpu is updated to a diff --git a/gfx/wgpu_bindings/cbindgen.toml b/gfx/wgpu_bindings/cbindgen.toml @@ -60,6 +60,15 @@ using WGPUTextureId = uint64_t; using WGPUTextureViewId = uint64_t; using WGPUExternalTextureId = uint64_t; using WGPUExternalTextureSourceId = uint64_t; + +// This is a zero-sized type in wgpu's API, but cbindgen generates C++ +// that leaves it as an incomplete type. Since WebGPU's GPULoadOp +// doesn't even have this variant (and probably never will, as its +// purpose is to allow behavior to vary freely per implementation), we +// can just give it a dummy definition that at least compiles. +struct WGPULoadOpDontCare { + char dummy; +}; """ include_version = true braces = "SameLine" @@ -72,6 +81,7 @@ style = "tag" prefix = "WGPU" renaming_overrides_prefixing = true exclude = [ + "LoadOpDontCare", "Option_AdapterId", "Option_BufferId", "Option_BindGroupId", diff --git a/gfx/wgpu_bindings/moz.yaml b/gfx/wgpu_bindings/moz.yaml @@ -8,8 +8,8 @@ origin: name: wgpu description: A cross-platform pure-Rust graphics API, modeled on the WebGPU standard url: https://github.com/gfx-rs/wgpu - release: 9a975b24deb379b31a8a7cb999d2da5f3227a91a (2025-11-16T19:09:51Z). - revision: 9a975b24deb379b31a8a7cb999d2da5f3227a91a + release: 3f02781bb5a0a1fe1922ea36c9bdacf9792abcbc (Wed Dec 17 22:05:10 2025 -0500) + revision: 3f02781bb5a0a1fe1922ea36c9bdacf9792abcbc license: ['MIT', 'Apache-2.0'] updatebot: diff --git a/gfx/wgpu_bindings/src/client.rs b/gfx/wgpu_bindings/src/client.rs @@ -218,7 +218,14 @@ pub struct BindGroupEntry { binding: u32, buffer: Option<id::BufferId>, offset: wgt::BufferAddress, - size: Option<wgt::BufferSize>, + + // In `wgpu_core::binding_model::BufferBinding`, these are an + // `Option<BufferAddress>`. But since `BufferAddress` can be zero, that is + // not a type that cbindgen can express in C++, so we use this pair of + // values instead. + size_passed: bool, + size: wgt::BufferAddress, + sampler: Option<id::SamplerId>, texture_view: Option<id::TextureViewId>, external_texture: Option<id::ExternalTextureId>, @@ -645,6 +652,8 @@ pub extern "C" fn wgpu_client_receive_server_message(client: &Client, byte_buf: vendor, support_use_shared_texture_in_swap_chain, transient_saves_memory, + subgroup_min_size, + subgroup_max_size, }) = adapter_information { let nss = |s: &str| { @@ -665,6 +674,8 @@ pub extern "C" fn wgpu_client_receive_server_message(client: &Client, byte_buf: vendor, support_use_shared_texture_in_swap_chain, transient_saves_memory, + subgroup_min_size, + subgroup_max_size, }; unsafe { wgpu_child_resolve_request_adapter_promise( @@ -1401,7 +1412,7 @@ pub unsafe extern "C" fn wgpu_command_encoder_begin_render_pass( let color_attachments: Vec<_> = color_attachments .as_slice() .iter() - .map(|format| Some(format.clone().to_wgpu())) + .map(|color_attachment| Some(color_attachment.clone().to_wgpu())) .collect(); let depth_stencil_attachment = depth_stencil_attachment.cloned().map(|dsa| dsa.to_wgpu()); let pass = crate::command::RecordedRenderPass::new( @@ -1569,7 +1580,7 @@ pub unsafe extern "C" fn wgpu_client_create_pipeline_layout( let wgpu_desc = wgc::binding_model::PipelineLayoutDescriptor { label, bind_group_layouts: Cow::Borrowed(desc.bind_group_layouts.as_slice()), - push_constant_ranges: Cow::Borrowed(&[]), + immediate_size: 0, }; let action = DeviceAction::CreatePipelineLayout(id, wgpu_desc); @@ -1598,7 +1609,7 @@ pub unsafe extern "C" fn wgpu_client_create_bind_group( wgc::binding_model::BindingResource::Buffer(wgc::binding_model::BufferBinding { buffer: id, offset: entry.offset, - size: entry.size, + size: entry.size_passed.then_some(entry.size), }) } else if let Some(id) = entry.sampler { wgc::binding_model::BindingResource::Sampler(id) @@ -2061,19 +2072,6 @@ pub extern "C" fn wgpu_render_bundle_set_index_buffer( } #[no_mangle] -pub unsafe extern "C" fn wgpu_render_bundle_set_push_constants( - pass: &mut RenderBundleEncoder, - stages: wgt::ShaderStages, - offset: u32, - size_bytes: u32, - data: *const u8, -) { - wgc::command::bundle_ffi::wgpu_render_bundle_set_push_constants( - pass, stages, offset, size_bytes, data, - ) -} - -#[no_mangle] pub extern "C" fn wgpu_render_bundle_draw( bundle: &mut RenderBundleEncoder, vertex_count: u32, diff --git a/gfx/wgpu_bindings/src/client/render_pass.rs b/gfx/wgpu_bindings/src/client/render_pass.rs @@ -145,6 +145,7 @@ impl<V1, V2> MapClearValue<V1, V2> for wgc::command::LoadOp<V1> { match self { Self::Clear(value) => wgc::command::LoadOp::Clear(f(value)), Self::Load => wgc::command::LoadOp::Load, + Self::DontCare(token) => wgc::command::LoadOp::DontCare(token), } } } diff --git a/gfx/wgpu_bindings/src/command.rs b/gfx/wgpu_bindings/src/command.rs @@ -709,6 +709,27 @@ pub fn replay_render_pass( src_pass: &RecordedRenderPass, error_buf: &mut crate::error::OwnedErrorBuffer, ) { + // Explicitly forbid `LoadOp::DontCare`, until wgpu#8780 is resolved. + // + // Since `DontCare` is not part of WebGPU (and is unlikely to become so), + // only a corrupted content process could ever produce such a render pass, + // so it suffices for us to just crash here if we see it. + for attachment in &src_pass.color_attachments { + if let Some(attachment) = attachment { + assert!(!matches!(attachment.load_op, wgt::LoadOp::DontCare(_))); + } + } + if let Some(ref attachment) = src_pass.depth_stencil_attachment { + assert!(!matches!( + attachment.depth.load_op, + Some(wgt::LoadOp::DontCare(_)) + )); + assert!(!matches!( + attachment.stencil.load_op, + Some(wgt::LoadOp::DontCare(_)) + )); + } + let (mut dst_pass, err) = global.command_encoder_begin_render_pass( id, &wgc::command::RenderPassDescriptor { diff --git a/gfx/wgpu_bindings/src/lib.rs b/gfx/wgpu_bindings/src/lib.rs @@ -127,6 +127,8 @@ pub struct AdapterInformation<S> { backend: wgt::Backend, support_use_shared_texture_in_swap_chain: bool, transient_saves_memory: bool, + subgroup_min_size: u32, + subgroup_max_size: u32, } #[repr(C)] diff --git a/gfx/wgpu_bindings/src/server.rs b/gfx/wgpu_bindings/src/server.rs @@ -170,6 +170,7 @@ pub extern "C" fn wgpu_server_new(owner: WebGPUParentPtr) -> *mut Global { for_device_loss: Some(99), }, }, + None, ); let global = Global { owner, global }; Box::into_raw(Box::new(global)) @@ -1491,25 +1492,6 @@ pub fn select_memory_type( None } -#[cfg(target_os = "linux")] -struct VkImageHolder { - pub device: vk::Device, - pub image: vk::Image, - pub memory: vk::DeviceMemory, - pub fn_destroy_image: vk::PFN_vkDestroyImage, - pub fn_free_memory: vk::PFN_vkFreeMemory, -} - -#[cfg(target_os = "linux")] -impl VkImageHolder { - fn destroy(&self) { - unsafe { - (self.fn_destroy_image)(self.device, self.image, ptr::null()); - (self.fn_free_memory)(self.device, self.memory, ptr::null()); - } - } -} - impl Global { #[cfg(target_os = "windows")] fn create_texture_with_shared_texture_d3d11( @@ -1765,14 +1747,6 @@ impl Global { } } - let image_holder = VkImageHolder { - device: device.handle(), - image, - memory, - fn_destroy_image: device.fp_v1_0().destroy_image, - fn_free_memory: device.fp_v1_0().free_memory, - }; - let hal_desc = wgh::TextureDescriptor { label: None, size: desc.size, @@ -1785,16 +1759,12 @@ impl Global { view_formats: vec![], }; - let image = image_holder.image; - let hal_texture = <wgh::api::Vulkan as wgh::Api>::Device::texture_from_raw( &hal_device, image, &hal_desc, - Some(Box::new(move || { - image_holder.destroy(); - })), None, + wgh::vulkan::TextureMemory::Dedicated(memory), ); let (_, error) = self.create_texture_from_hal( @@ -2538,6 +2508,8 @@ unsafe fn process_message( backend, transient_saves_memory, device_pci_bus_id: _, + subgroup_min_size, + subgroup_max_size, } = global.adapter_get_info(adapter_id); let is_hardware = match device_type { @@ -2576,6 +2548,8 @@ unsafe fn process_message( backend, support_use_shared_texture_in_swap_chain, transient_saves_memory, + subgroup_min_size, + subgroup_max_size, }; Some(info) } else { @@ -3170,7 +3144,7 @@ mod macos { global.create_texture_error(Some(id_in), &desc); return; }; - let metal_device = hal_device.raw_device().lock(); + let metal_device = hal_device.raw_device(); let metal_desc = metal::TextureDescriptor::new(); let texture_type = match desc.dimension { @@ -3296,8 +3270,7 @@ mod macos { descriptor.set_usage(usage); descriptor.set_storage_mode(metal::MTLStorageMode::Private); - let raw_device = device.lock(); - msg_send![*raw_device, newTextureWithDescriptor: descriptor iosurface:io_surface.obj plane:0] + msg_send![*device, newTextureWithDescriptor: descriptor iosurface:io_surface.obj plane:0] }) }; diff --git a/gfx/wr/Cargo.lock b/gfx/wr/Cargo.lock @@ -2351,9 +2351,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.18.0" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "ordered-float" @@ -2790,14 +2790,15 @@ dependencies = [ [[package]] name = "ron" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db09040cc89e461f1a265139777a2bde7f8d8c67c4936f700c63ce3e2904d468" +checksum = "fd490c5b18261893f14449cbd28cb9c0b637aebf161cd77900bfdedaff21ec32" dependencies = [ - "base64", "bitflags 2.4.2", + "once_cell", "serde", "serde_derive", + "typeid", "unicode-ident", ] @@ -3370,6 +3371,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] name = "typenum" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/gfx/wr/webrender/Cargo.toml b/gfx/wr/webrender/Cargo.toml @@ -41,7 +41,7 @@ num-traits = "0.2" plane-split = "0.18" png = { optional = true, version = "0.16" } rayon = "1" -ron = { optional = true, version = "0.11.0" } +ron = { optional = true, version = "0.12.0" } serde = { optional = true, version = "1.0", features = ["serde_derive"] } smallvec = "1" api = { version = "0.62.0", path = "../webrender_api", package = "webrender_api" } diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml @@ -3976,6 +3976,11 @@ who = "Erich Gubler <erichdongubler@gmail.com>" criteria = "safe-to-deploy" delta = "0.31.0 -> 0.32.0" +[[audits.metal]] +who = "Jim Blandy <jimb@red-bean.com>" +criteria = "safe-to-deploy" +delta = "0.32.0 -> 0.33.0" + [[audits.midir]] who = "Bobby Holley <bobbyholley@gmail.com>" criteria = "safe-to-deploy" @@ -4373,7 +4378,12 @@ who = [ "Jim Blandy <jimb@red-bean.com>", ] criteria = "safe-to-deploy" -delta = "27.0.0 -> 27.0.0@git:9a975b24deb379b31a8a7cb999d2da5f3227a91a" +delta = "27.0.0 -> 28.0.0" + +[[audits.naga]] +who = "Jim Blandy <jimb@red-bean.com>" +criteria = "safe-to-deploy" +delta = "28.0.0 -> 28.0.0@git:3f02781bb5a0a1fe1922ea36c9bdacf9792abcbc" importable = false [[audits.net2]] @@ -6965,7 +6975,12 @@ who = [ "Jim Blandy <jimb@red-bean.com>", ] criteria = "safe-to-deploy" -delta = "27.0.0 -> 27.0.0@git:9a975b24deb379b31a8a7cb999d2da5f3227a91a" +delta = "27.0.0 -> 28.0.0" + +[[audits.wgpu-core]] +who = "Jim Blandy <jimb@red-bean.com>" +criteria = "safe-to-deploy" +delta = "28.0.0 -> 28.0.0@git:3f02781bb5a0a1fe1922ea36c9bdacf9792abcbc" importable = false [[audits.wgpu-core-deps-apple]] @@ -6996,7 +7011,12 @@ who = [ "Jim Blandy <jimb@red-bean.com>", ] criteria = "safe-to-deploy" -delta = "27.0.0 -> 27.0.0@git:9a975b24deb379b31a8a7cb999d2da5f3227a91a" +delta = "27.0.0 -> 28.0.0" + +[[audits.wgpu-core-deps-apple]] +who = "Jim Blandy <jimb@red-bean.com>" +criteria = "safe-to-deploy" +delta = "28.0.0 -> 28.0.0@git:3f02781bb5a0a1fe1922ea36c9bdacf9792abcbc" importable = false [[audits.wgpu-core-deps-windows-linux-android]] @@ -7027,7 +7047,12 @@ who = [ "Jim Blandy <jimb@red-bean.com>", ] criteria = "safe-to-deploy" -delta = "27.0.0 -> 27.0.0@git:9a975b24deb379b31a8a7cb999d2da5f3227a91a" +delta = "27.0.0 -> 28.0.0" + +[[audits.wgpu-core-deps-windows-linux-android]] +who = "Jim Blandy <jimb@red-bean.com>" +criteria = "safe-to-deploy" +delta = "28.0.0 -> 28.0.0@git:3f02781bb5a0a1fe1922ea36c9bdacf9792abcbc" importable = false [[audits.wgpu-hal]] @@ -7136,7 +7161,12 @@ who = [ "Jim Blandy <jimb@red-bean.com>", ] criteria = "safe-to-deploy" -delta = "27.0.0 -> 27.0.0@git:9a975b24deb379b31a8a7cb999d2da5f3227a91a" +delta = "27.0.0 -> 28.0.0" + +[[audits.wgpu-hal]] +who = "Jim Blandy <jimb@red-bean.com>" +criteria = "safe-to-deploy" +delta = "28.0.0 -> 28.0.0@git:3f02781bb5a0a1fe1922ea36c9bdacf9792abcbc" importable = false [[audits.wgpu-types]] @@ -7240,7 +7270,12 @@ who = [ "Jim Blandy <jimb@red-bean.com>", ] criteria = "safe-to-deploy" -delta = "27.0.0 -> 27.0.0@git:9a975b24deb379b31a8a7cb999d2da5f3227a91a" +delta = "27.0.0 -> 28.0.0" + +[[audits.wgpu-types]] +who = "Jim Blandy <jimb@red-bean.com>" +criteria = "safe-to-deploy" +delta = "28.0.0 -> 28.0.0@git:3f02781bb5a0a1fe1922ea36c9bdacf9792abcbc" importable = false [[audits.whatsys]] diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock @@ -762,6 +762,13 @@ user-id = 6743 user-login = "epage" user-name = "Ed Page" +[[publisher.typeid]] +version = "1.0.3" +when = "2025-03-04" +user-id = 3618 +user-login = "dtolnay" +user-name = "David Tolnay" + [[publisher.unicode-ident]] version = "1.0.6" when = "2022-12-17" diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxColorAttachmentBytesPerSample/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxColorAttachmentBytesPerSample/cts.https.html.ini @@ -1,5 +1,7 @@ [cts.https.html?q=webgpu:api,validation,capability_checks,limits,maxColorAttachmentBytesPerSample:beginRenderPass,at_over:*] - implementation-status: backlog + implementation-status: + if os == "win": backlog + if os == "linux": backlog [:limitTest="atDefault";testValueName="atLimit";sampleCount=1;interleaveFormat="r8unorm"] [:limitTest="atDefault";testValueName="atLimit";sampleCount=1;interleaveFormat="rg16uint"] @@ -21,34 +23,54 @@ [:limitTest="atDefault";testValueName="atLimit";sampleCount=4;interleaveFormat="rgba8unorm"] [:limitTest="atDefault";testValueName="overLimit";sampleCount=1;interleaveFormat="r8unorm"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="atDefault";testValueName="overLimit";sampleCount=1;interleaveFormat="rg16uint"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="atDefault";testValueName="overLimit";sampleCount=1;interleaveFormat="rg8unorm"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="atDefault";testValueName="overLimit";sampleCount=1;interleaveFormat="rgba16uint"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="atDefault";testValueName="overLimit";sampleCount=1;interleaveFormat="rgba8unorm"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="atDefault";testValueName="overLimit";sampleCount=4;interleaveFormat="r8unorm"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="atDefault";testValueName="overLimit";sampleCount=4;interleaveFormat="rg16uint"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="atDefault";testValueName="overLimit";sampleCount=4;interleaveFormat="rg8unorm"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="atDefault";testValueName="overLimit";sampleCount=4;interleaveFormat="rgba16uint"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="atDefault";testValueName="overLimit";sampleCount=4;interleaveFormat="rgba8unorm"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="atMaximum";testValueName="atLimit";sampleCount=1;interleaveFormat="r8unorm"] @@ -71,34 +93,54 @@ [:limitTest="atMaximum";testValueName="atLimit";sampleCount=4;interleaveFormat="rgba8unorm"] [:limitTest="atMaximum";testValueName="overLimit";sampleCount=1;interleaveFormat="r8unorm"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="atMaximum";testValueName="overLimit";sampleCount=1;interleaveFormat="rg16uint"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="atMaximum";testValueName="overLimit";sampleCount=1;interleaveFormat="rg8unorm"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="atMaximum";testValueName="overLimit";sampleCount=1;interleaveFormat="rgba16uint"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="atMaximum";testValueName="overLimit";sampleCount=1;interleaveFormat="rgba8unorm"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="atMaximum";testValueName="overLimit";sampleCount=4;interleaveFormat="r8unorm"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="atMaximum";testValueName="overLimit";sampleCount=4;interleaveFormat="rg16uint"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="atMaximum";testValueName="overLimit";sampleCount=4;interleaveFormat="rg8unorm"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="atMaximum";testValueName="overLimit";sampleCount=4;interleaveFormat="rgba16uint"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="atMaximum";testValueName="overLimit";sampleCount=4;interleaveFormat="rgba8unorm"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="betweenDefaultAndMaximum";testValueName="atLimit";sampleCount=1;interleaveFormat="r8unorm"] @@ -121,34 +163,54 @@ [:limitTest="betweenDefaultAndMaximum";testValueName="atLimit";sampleCount=4;interleaveFormat="rgba8unorm"] [:limitTest="betweenDefaultAndMaximum";testValueName="overLimit";sampleCount=1;interleaveFormat="r8unorm"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="betweenDefaultAndMaximum";testValueName="overLimit";sampleCount=1;interleaveFormat="rg16uint"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="betweenDefaultAndMaximum";testValueName="overLimit";sampleCount=1;interleaveFormat="rg8unorm"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="betweenDefaultAndMaximum";testValueName="overLimit";sampleCount=1;interleaveFormat="rgba16uint"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="betweenDefaultAndMaximum";testValueName="overLimit";sampleCount=1;interleaveFormat="rgba8unorm"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="betweenDefaultAndMaximum";testValueName="overLimit";sampleCount=4;interleaveFormat="r8unorm"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="betweenDefaultAndMaximum";testValueName="overLimit";sampleCount=4;interleaveFormat="rg16uint"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="betweenDefaultAndMaximum";testValueName="overLimit";sampleCount=4;interleaveFormat="rg8unorm"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="betweenDefaultAndMaximum";testValueName="overLimit";sampleCount=4;interleaveFormat="rgba16uint"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="betweenDefaultAndMaximum";testValueName="overLimit";sampleCount=4;interleaveFormat="rgba8unorm"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="overMaximum";testValueName="atLimit";sampleCount=1;interleaveFormat="r8unorm"] @@ -211,34 +273,54 @@ [:limitTest="underDefault";testValueName="atLimit";sampleCount=4;interleaveFormat="rgba8unorm"] [:limitTest="underDefault";testValueName="overLimit";sampleCount=1;interleaveFormat="r8unorm"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="underDefault";testValueName="overLimit";sampleCount=1;interleaveFormat="rg16uint"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="underDefault";testValueName="overLimit";sampleCount=1;interleaveFormat="rg8unorm"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="underDefault";testValueName="overLimit";sampleCount=1;interleaveFormat="rgba16uint"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="underDefault";testValueName="overLimit";sampleCount=1;interleaveFormat="rgba8unorm"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="underDefault";testValueName="overLimit";sampleCount=4;interleaveFormat="r8unorm"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="underDefault";testValueName="overLimit";sampleCount=4;interleaveFormat="rg16uint"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="underDefault";testValueName="overLimit";sampleCount=4;interleaveFormat="rg8unorm"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="underDefault";testValueName="overLimit";sampleCount=4;interleaveFormat="rgba16uint"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:limitTest="underDefault";testValueName="overLimit";sampleCount=4;interleaveFormat="rgba8unorm"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [cts.https.html?q=webgpu:api,validation,capability_checks,limits,maxColorAttachmentBytesPerSample:createRenderBundle,at_over:*] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/api/validation/createBindGroup/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/api/validation/createBindGroup/cts.https.html.ini @@ -351,9 +351,7 @@ [cts.https.html?q=webgpu:api,validation,createBindGroup:buffer_offset_and_size_for_bind_groups_match:*] - implementation-status: backlog [:] - expected: FAIL [cts.https.html?q=webgpu:api,validation,createBindGroup:external_texture,texture_view,dimension:*] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/api/validation/encoding/cmds/compute_pass/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/api/validation/encoding/cmds/compute_pass/cts.https.html.ini @@ -33,9 +33,7 @@ [cts.https.html?q=webgpu:api,validation,encoding,cmds,compute_pass:indirect_dispatch_buffer_state:*] - implementation-status: backlog [:] - expected: FAIL [cts.https.html?q=webgpu:api,validation,encoding,cmds,compute_pass:pipeline,device_mismatch:*] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/api/validation/encoding/cmds/copyTextureToTexture/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/api/validation/encoding/cmds/copyTextureToTexture/cts.https.html.ini @@ -5756,23 +5756,23 @@ [:srcFormat="r32float";dstFormat="rg16float"] expected: - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:srcFormat="r32float";dstFormat="rg16sint"] expected: - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:srcFormat="r32float";dstFormat="rg16snorm"] expected: - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:srcFormat="r32float";dstFormat="rg16uint"] expected: - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:srcFormat="r32float";dstFormat="rg16unorm"] expected: - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:srcFormat="r32float";dstFormat="rg32float"] expected: @@ -5816,23 +5816,23 @@ [:srcFormat="r32float";dstFormat="rgba16float"] expected: - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:srcFormat="r32float";dstFormat="rgba16sint"] expected: - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:srcFormat="r32float";dstFormat="rgba16snorm"] expected: - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:srcFormat="r32float";dstFormat="rgba16uint"] expected: - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:srcFormat="r32float";dstFormat="rgba16unorm"] expected: - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:srcFormat="r32float";dstFormat="rgba32float"] expected: diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/api/validation/encoding/cmds/render/indirect_draw/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/api/validation/encoding/cmds/render/indirect_draw/cts.https.html.ini @@ -3,12 +3,7 @@ [cts.https.html?q=webgpu:api,validation,encoding,cmds,render,indirect_draw:indirect_buffer_state:*] - implementation-status: backlog - expected: CRASH [:] - expected: - if os == "win": FAIL - if os == "mac": FAIL [cts.https.html?q=webgpu:api,validation,encoding,cmds,render,indirect_draw:indirect_buffer_usage:*] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/api/validation/encoding/programmable/pipeline_bind_group_compat/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/api/validation/encoding/programmable/pipeline_bind_group_compat/cts.https.html.ini @@ -1,5 +1,4 @@ [cts.https.html?q=webgpu:api,validation,encoding,programmable,pipeline_bind_group_compat:bgl_binding_mismatch:*] - implementation-status: backlog [:encoderType="compute%20pass";call="dispatch";callWithZero=false] [:encoderType="compute%20pass";call="dispatch";callWithZero=true] @@ -9,28 +8,20 @@ [:encoderType="compute%20pass";call="dispatchIndirect";callWithZero=true] [:encoderType="render%20bundle";call="draw";callWithZero=false] - expected: FAIL [:encoderType="render%20bundle";call="draw";callWithZero=true] - expected: FAIL [:encoderType="render%20bundle";call="drawIndexed";callWithZero=false] - expected: FAIL [:encoderType="render%20bundle";call="drawIndexed";callWithZero=true] - expected: FAIL [:encoderType="render%20bundle";call="drawIndexedIndirect";callWithZero=false] - expected: FAIL [:encoderType="render%20bundle";call="drawIndexedIndirect";callWithZero=true] - expected: FAIL [:encoderType="render%20bundle";call="drawIndirect";callWithZero=false] - expected: FAIL [:encoderType="render%20bundle";call="drawIndirect";callWithZero=true] - expected: FAIL [:encoderType="render%20pass";call="draw";callWithZero=false] @@ -50,9 +41,6 @@ [cts.https.html?q=webgpu:api,validation,encoding,programmable,pipeline_bind_group_compat:bgl_resource_type_mismatch:*] - implementation-status: backlog - expected: - if os == "win": CRASH [:encoderType="compute%20pass";call="dispatch";callWithZero=false] [:encoderType="compute%20pass";call="dispatch";callWithZero=true] @@ -62,28 +50,20 @@ [:encoderType="compute%20pass";call="dispatchIndirect";callWithZero=true] [:encoderType="render%20bundle";call="draw";callWithZero=false] - expected: FAIL [:encoderType="render%20bundle";call="draw";callWithZero=true] - expected: FAIL [:encoderType="render%20bundle";call="drawIndexed";callWithZero=false] - expected: FAIL [:encoderType="render%20bundle";call="drawIndexed";callWithZero=true] - expected: FAIL [:encoderType="render%20bundle";call="drawIndexedIndirect";callWithZero=false] - expected: FAIL [:encoderType="render%20bundle";call="drawIndexedIndirect";callWithZero=true] - expected: FAIL [:encoderType="render%20bundle";call="drawIndirect";callWithZero=false] - expected: FAIL [:encoderType="render%20bundle";call="drawIndirect";callWithZero=true] - expected: FAIL [:encoderType="render%20pass";call="draw";callWithZero=false] @@ -103,7 +83,6 @@ [cts.https.html?q=webgpu:api,validation,encoding,programmable,pipeline_bind_group_compat:bgl_visibility_mismatch:*] - implementation-status: backlog [:encoderType="compute%20pass";call="dispatch";callWithZero=false] [:encoderType="compute%20pass";call="dispatch";callWithZero=true] @@ -113,28 +92,20 @@ [:encoderType="compute%20pass";call="dispatchIndirect";callWithZero=true] [:encoderType="render%20bundle";call="draw";callWithZero=false] - expected: FAIL [:encoderType="render%20bundle";call="draw";callWithZero=true] - expected: FAIL [:encoderType="render%20bundle";call="drawIndexed";callWithZero=false] - expected: FAIL [:encoderType="render%20bundle";call="drawIndexed";callWithZero=true] - expected: FAIL [:encoderType="render%20bundle";call="drawIndexedIndirect";callWithZero=false] - expected: FAIL [:encoderType="render%20bundle";call="drawIndexedIndirect";callWithZero=true] - expected: FAIL [:encoderType="render%20bundle";call="drawIndirect";callWithZero=false] - expected: FAIL [:encoderType="render%20bundle";call="drawIndirect";callWithZero=true] - expected: FAIL [:encoderType="render%20pass";call="draw";callWithZero=false] @@ -154,7 +125,6 @@ [cts.https.html?q=webgpu:api,validation,encoding,programmable,pipeline_bind_group_compat:bind_groups_and_pipeline_layout_mismatch:*] - implementation-status: backlog [:encoderType="compute%20pass";call="dispatch";callWithZero=false] [:encoderType="compute%20pass";call="dispatch";callWithZero=true] @@ -164,28 +134,20 @@ [:encoderType="compute%20pass";call="dispatchIndirect";callWithZero=true] [:encoderType="render%20bundle";call="draw";callWithZero=false] - expected: FAIL [:encoderType="render%20bundle";call="draw";callWithZero=true] - expected: FAIL [:encoderType="render%20bundle";call="drawIndexed";callWithZero=false] - expected: FAIL [:encoderType="render%20bundle";call="drawIndexed";callWithZero=true] - expected: FAIL [:encoderType="render%20bundle";call="drawIndexedIndirect";callWithZero=false] - expected: FAIL [:encoderType="render%20bundle";call="drawIndexedIndirect";callWithZero=true] - expected: FAIL [:encoderType="render%20bundle";call="drawIndirect";callWithZero=false] - expected: FAIL [:encoderType="render%20bundle";call="drawIndirect";callWithZero=true] - expected: FAIL [:encoderType="render%20pass";call="draw";callWithZero=false] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/api/validation/encoding/queries/begin_end/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/api/validation/encoding/queries/begin_end/cts.https.html.ini @@ -3,11 +3,7 @@ [cts.https.html?q=webgpu:api,validation,encoding,queries,begin_end:occlusion_query,begin_end_balance:*] - implementation-status: backlog - expected: - if os == "win": CRASH [:] - expected: FAIL [cts.https.html?q=webgpu:api,validation,encoding,queries,begin_end:occlusion_query,begin_end_invalid_nesting:*] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/api/validation/image_copy/buffer_related/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/api/validation/image_copy/buffer_related/cts.https.html.ini @@ -17,7 +17,6 @@ [cts.https.html?q=webgpu:api,validation,image_copy,buffer_related:bytes_per_row_alignment:*] - implementation-status: backlog [:method="CopyB2T";format="astc-10x10-unorm";dimension="2d"] [:method="CopyB2T";format="astc-10x10-unorm";dimension="3d"] @@ -131,106 +130,72 @@ [:method="CopyB2T";format="astc-8x8-unorm-srgb";dimension="3d"] [:method="CopyB2T";format="bc1-rgba-unorm";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="bc1-rgba-unorm";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="bc1-rgba-unorm-srgb";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="bc1-rgba-unorm-srgb";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="bc2-rgba-unorm";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="bc2-rgba-unorm";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="bc2-rgba-unorm-srgb";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="bc2-rgba-unorm-srgb";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="bc3-rgba-unorm";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="bc3-rgba-unorm";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="bc3-rgba-unorm-srgb";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="bc3-rgba-unorm-srgb";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="bc4-r-snorm";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="bc4-r-snorm";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="bc4-r-unorm";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="bc4-r-unorm";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="bc5-rg-snorm";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="bc5-rg-snorm";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="bc5-rg-unorm";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="bc5-rg-unorm";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="bc6h-rgb-float";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="bc6h-rgb-float";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="bc6h-rgb-ufloat";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="bc6h-rgb-ufloat";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="bc7-rgba-unorm";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="bc7-rgba-unorm";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="bc7-rgba-unorm-srgb";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="bc7-rgba-unorm-srgb";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="bgra8unorm";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="bgra8unorm";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="bgra8unorm";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="bgra8unorm-srgb";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="bgra8unorm-srgb";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="bgra8unorm-srgb";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="depth16unorm";dimension="2d"] @@ -255,22 +220,16 @@ [:method="CopyB2T";format="etc2-rgba8unorm-srgb";dimension="2d"] [:method="CopyB2T";format="r16float";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="r16float";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="r16float";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="r16sint";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="r16sint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="r16sint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="r16snorm";dimension="1d"] @@ -279,13 +238,10 @@ [:method="CopyB2T";format="r16snorm";dimension="3d"] [:method="CopyB2T";format="r16uint";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="r16uint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="r16uint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="r16unorm";dimension="1d"] @@ -294,94 +250,64 @@ [:method="CopyB2T";format="r16unorm";dimension="3d"] [:method="CopyB2T";format="r32float";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="r32float";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="r32float";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="r32sint";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="r32sint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="r32sint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="r32uint";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="r32uint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="r32uint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="r8sint";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="r8sint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="r8sint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="r8snorm";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="r8snorm";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="r8snorm";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="r8uint";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="r8uint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="r8uint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="r8unorm";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="r8unorm";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="r8unorm";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rg11b10ufloat";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="rg11b10ufloat";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rg11b10ufloat";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rg16float";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="rg16float";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rg16float";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rg16sint";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="rg16sint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rg16sint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rg16snorm";dimension="1d"] @@ -390,13 +316,10 @@ [:method="CopyB2T";format="rg16snorm";dimension="3d"] [:method="CopyB2T";format="rg16uint";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="rg16uint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rg16uint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rg16unorm";dimension="1d"] @@ -405,112 +328,76 @@ [:method="CopyB2T";format="rg16unorm";dimension="3d"] [:method="CopyB2T";format="rg32float";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="rg32float";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rg32float";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rg32sint";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="rg32sint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rg32sint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rg32uint";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="rg32uint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rg32uint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rg8sint";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="rg8sint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rg8sint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rg8snorm";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="rg8snorm";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rg8snorm";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rg8uint";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="rg8uint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rg8uint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rg8unorm";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="rg8unorm";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rg8unorm";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rgb10a2uint";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="rgb10a2uint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rgb10a2uint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rgb10a2unorm";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="rgb10a2unorm";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rgb10a2unorm";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rgb9e5ufloat";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="rgb9e5ufloat";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rgb9e5ufloat";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rgba16float";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="rgba16float";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rgba16float";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rgba16sint";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="rgba16sint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rgba16sint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rgba16snorm";dimension="1d"] @@ -519,13 +406,10 @@ [:method="CopyB2T";format="rgba16snorm";dimension="3d"] [:method="CopyB2T";format="rgba16uint";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="rgba16uint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rgba16uint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rgba16unorm";dimension="1d"] @@ -534,76 +418,52 @@ [:method="CopyB2T";format="rgba16unorm";dimension="3d"] [:method="CopyB2T";format="rgba32float";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="rgba32float";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rgba32float";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rgba32sint";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="rgba32sint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rgba32sint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rgba32uint";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="rgba32uint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rgba32uint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rgba8sint";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="rgba8sint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rgba8sint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rgba8snorm";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="rgba8snorm";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rgba8snorm";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rgba8uint";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="rgba8uint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rgba8uint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rgba8unorm";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="rgba8unorm";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rgba8unorm";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rgba8unorm-srgb";dimension="1d"] - expected: FAIL [:method="CopyB2T";format="rgba8unorm-srgb";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rgba8unorm-srgb";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="stencil8";dimension="2d"] @@ -1511,8 +1371,6 @@ [cts.https.html?q=webgpu:api,validation,image_copy,buffer_related:usage:*] - implementation-status: backlog [:method="CopyB2T"] - expected: FAIL [:method="CopyT2B"] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/api/validation/image_copy/layout_related/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/api/validation/image_copy/layout_related/cts.https.html.ini @@ -2709,14 +2709,11 @@ [cts.https.html?q=webgpu:api,validation,image_copy,layout_related:bound_on_offset:*] - implementation-status: backlog + implementation-status: + if os == "mac": backlog expected: if os == "mac": [OK, TIMEOUT] [:method="CopyB2T"] - expected: - if os == "win": FAIL - if os == "linux": FAIL - if os == "mac": [FAIL, TIMEOUT, NOTRUN] [:method="CopyT2B"] expected: @@ -2728,32 +2725,17 @@ [cts.https.html?q=webgpu:api,validation,image_copy,layout_related:bound_on_rows_per_image:*] - implementation-status: backlog + implementation-status: + if os == "mac": backlog expected: if os == "mac": [OK, TIMEOUT] [:method="CopyB2T";dimension="1d";size=[4,1,1\]] - expected: - if os == "win": FAIL - if os == "linux": FAIL - if os == "mac": [FAIL, TIMEOUT, NOTRUN] [:method="CopyB2T";dimension="2d";size=[4,4,1\]] - expected: - if os == "win": FAIL - if os == "linux": FAIL - if os == "mac": [FAIL, TIMEOUT, NOTRUN] [:method="CopyB2T";dimension="2d";size=[4,4,3\]] - expected: - if os == "win": FAIL - if os == "linux": FAIL - if os == "mac": [FAIL, TIMEOUT, NOTRUN] [:method="CopyB2T";dimension="3d";size=[4,4,3\]] - expected: - if os == "win": FAIL - if os == "linux": FAIL - if os == "mac": [FAIL, TIMEOUT, NOTRUN] [:method="CopyT2B";dimension="1d";size=[4,1,1\]] expected: @@ -4151,540 +4133,370 @@ [cts.https.html?q=webgpu:api,validation,image_copy,layout_related:required_bytes_in_copy:*] - implementation-status: backlog + implementation-status: + if os == "mac": backlog expected: if os == "mac": TIMEOUT [:method="CopyB2T";format="astc-10x10-unorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-10x10-unorm";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-10x10-unorm-srgb";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-10x10-unorm-srgb";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-10x5-unorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-10x5-unorm";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-10x5-unorm-srgb";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-10x5-unorm-srgb";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-10x6-unorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-10x6-unorm";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-10x6-unorm-srgb";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-10x6-unorm-srgb";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-10x8-unorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-10x8-unorm";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-10x8-unorm-srgb";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-10x8-unorm-srgb";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-12x10-unorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-12x10-unorm";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-12x10-unorm-srgb";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-12x10-unorm-srgb";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-12x12-unorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-12x12-unorm";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-12x12-unorm-srgb";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-12x12-unorm-srgb";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-4x4-unorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-4x4-unorm";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-4x4-unorm-srgb";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-4x4-unorm-srgb";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-5x4-unorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-5x4-unorm";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-5x4-unorm-srgb";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-5x4-unorm-srgb";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-5x5-unorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-5x5-unorm";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-5x5-unorm-srgb";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-5x5-unorm-srgb";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-6x5-unorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-6x5-unorm";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-6x5-unorm-srgb";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-6x5-unorm-srgb";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-6x6-unorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-6x6-unorm";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-6x6-unorm-srgb";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-6x6-unorm-srgb";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-8x5-unorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-8x5-unorm";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-8x5-unorm-srgb";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-8x5-unorm-srgb";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-8x6-unorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-8x6-unorm";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-8x6-unorm-srgb";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-8x6-unorm-srgb";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-8x8-unorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-8x8-unorm";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-8x8-unorm-srgb";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="astc-8x8-unorm-srgb";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="bc1-rgba-unorm";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="bc1-rgba-unorm";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="bc1-rgba-unorm-srgb";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="bc1-rgba-unorm-srgb";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="bc2-rgba-unorm";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="bc2-rgba-unorm";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="bc2-rgba-unorm-srgb";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="bc2-rgba-unorm-srgb";dimension="3d"] - expected: - if os == "win": FAIL - if os == "linux": FAIL - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [FAIL, TIMEOUT, NOTRUN] [:method="CopyB2T";format="bc3-rgba-unorm";dimension="2d"] - expected: - if os == "win": FAIL - if os == "linux": FAIL - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [FAIL, TIMEOUT, NOTRUN] [:method="CopyB2T";format="bc3-rgba-unorm";dimension="3d"] - expected: - if os == "win": FAIL - if os == "linux": FAIL - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [FAIL, TIMEOUT, NOTRUN] [:method="CopyB2T";format="bc3-rgba-unorm-srgb";dimension="2d"] - expected: - if os == "win": FAIL - if os == "linux": FAIL - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [FAIL, TIMEOUT, NOTRUN] [:method="CopyB2T";format="bc3-rgba-unorm-srgb";dimension="3d"] - expected: - if os == "win": FAIL - if os == "linux": FAIL - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [FAIL, TIMEOUT, NOTRUN] [:method="CopyB2T";format="bc4-r-snorm";dimension="2d"] - expected: - if os == "win": FAIL - if os == "linux": FAIL - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [FAIL, TIMEOUT, NOTRUN] [:method="CopyB2T";format="bc4-r-snorm";dimension="3d"] - expected: - if os == "win": FAIL - if os == "linux": FAIL - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [FAIL, TIMEOUT, NOTRUN] [:method="CopyB2T";format="bc4-r-unorm";dimension="2d"] - expected: - if os == "win": FAIL - if os == "linux": FAIL - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [FAIL, TIMEOUT, NOTRUN] [:method="CopyB2T";format="bc4-r-unorm";dimension="3d"] - expected: - if os == "win": FAIL - if os == "linux": FAIL - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [FAIL, TIMEOUT, NOTRUN] [:method="CopyB2T";format="bc5-rg-snorm";dimension="2d"] - expected: - if os == "win": FAIL - if os == "linux": FAIL - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [FAIL, TIMEOUT, NOTRUN] [:method="CopyB2T";format="bc5-rg-snorm";dimension="3d"] - expected: - if os == "win": FAIL - if os == "linux": FAIL - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [FAIL, TIMEOUT, NOTRUN] [:method="CopyB2T";format="bc5-rg-unorm";dimension="2d"] - expected: - if os == "win": FAIL - if os == "linux": FAIL - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [FAIL, TIMEOUT, NOTRUN] [:method="CopyB2T";format="bc5-rg-unorm";dimension="3d"] - expected: - if os == "win": FAIL - if os == "linux": FAIL - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [FAIL, TIMEOUT, NOTRUN] [:method="CopyB2T";format="bc6h-rgb-float";dimension="2d"] - expected: - if os == "win": FAIL - if os == "linux": FAIL - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [FAIL, TIMEOUT, NOTRUN] [:method="CopyB2T";format="bc6h-rgb-float";dimension="3d"] expected: - if os == "win": FAIL - if os == "linux": FAIL - if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and debug: [PASS, TIMEOUT, NOTRUN] if os == "mac" and not debug: [FAIL, TIMEOUT, NOTRUN] [:method="CopyB2T";format="bc6h-rgb-ufloat";dimension="2d"] - expected: - if os == "win": FAIL - if os == "linux": FAIL - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [FAIL, TIMEOUT, NOTRUN] [:method="CopyB2T";format="bc6h-rgb-ufloat";dimension="3d"] - expected: - if os == "win": FAIL - if os == "linux": FAIL - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [FAIL, TIMEOUT, NOTRUN] [:method="CopyB2T";format="bc7-rgba-unorm";dimension="2d"] expected: - if os == "win": FAIL - if os == "linux": FAIL - if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and debug: [PASS, TIMEOUT, NOTRUN] if os == "mac" and not debug: [FAIL, TIMEOUT, NOTRUN] [:method="CopyB2T";format="bc7-rgba-unorm";dimension="3d"] expected: - if os == "win": FAIL - if os == "linux": FAIL - if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and debug: [PASS, TIMEOUT, NOTRUN] if os == "mac" and not debug: [FAIL, TIMEOUT, NOTRUN] [:method="CopyB2T";format="bc7-rgba-unorm-srgb";dimension="2d"] expected: - if os == "win": FAIL - if os == "linux": FAIL - if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and debug: [PASS, TIMEOUT, NOTRUN] if os == "mac" and not debug: [FAIL, TIMEOUT, NOTRUN] [:method="CopyB2T";format="bc7-rgba-unorm-srgb";dimension="3d"] expected: - if os == "win": FAIL - if os == "linux": FAIL - if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and debug: [PASS, TIMEOUT, NOTRUN] if os == "mac" and not debug: [FAIL, TIMEOUT, NOTRUN] [:method="CopyB2T";format="bgra8unorm";dimension="1d"] [:method="CopyB2T";format="bgra8unorm";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="bgra8unorm";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="bgra8unorm-srgb";dimension="1d"] [:method="CopyB2T";format="bgra8unorm-srgb";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="bgra8unorm-srgb";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="depth16unorm";dimension="2d"] [:method="CopyB2T";format="eac-r11snorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="eac-r11unorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="eac-rg11snorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="eac-rg11unorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="etc2-rgb8a1unorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="etc2-rgb8a1unorm-srgb";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="etc2-rgb8unorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="etc2-rgb8unorm-srgb";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="etc2-rgba8unorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="etc2-rgba8unorm-srgb";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyB2T";format="r16float";dimension="1d"] [:method="CopyB2T";format="r16float";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="r16float";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="r16sint";dimension="1d"] [:method="CopyB2T";format="r16sint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="r16sint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="r16snorm";dimension="1d"] @@ -4695,10 +4507,8 @@ [:method="CopyB2T";format="r16uint";dimension="1d"] [:method="CopyB2T";format="r16uint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="r16uint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="r16unorm";dimension="1d"] @@ -4709,82 +4519,62 @@ [:method="CopyB2T";format="r32float";dimension="1d"] [:method="CopyB2T";format="r32float";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="r32float";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="r32sint";dimension="1d"] [:method="CopyB2T";format="r32sint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="r32sint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="r32uint";dimension="1d"] [:method="CopyB2T";format="r32uint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="r32uint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="r8sint";dimension="1d"] [:method="CopyB2T";format="r8sint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="r8sint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="r8snorm";dimension="1d"] [:method="CopyB2T";format="r8snorm";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="r8snorm";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="r8uint";dimension="1d"] [:method="CopyB2T";format="r8uint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="r8uint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="r8unorm";dimension="1d"] [:method="CopyB2T";format="r8unorm";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="r8unorm";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rg11b10ufloat";dimension="1d"] [:method="CopyB2T";format="rg11b10ufloat";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rg11b10ufloat";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rg16float";dimension="1d"] [:method="CopyB2T";format="rg16float";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rg16float";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rg16sint";dimension="1d"] [:method="CopyB2T";format="rg16sint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rg16sint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rg16snorm";dimension="1d"] @@ -4795,10 +4585,8 @@ [:method="CopyB2T";format="rg16uint";dimension="1d"] [:method="CopyB2T";format="rg16uint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rg16uint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rg16unorm";dimension="1d"] @@ -4809,98 +4597,74 @@ [:method="CopyB2T";format="rg32float";dimension="1d"] [:method="CopyB2T";format="rg32float";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rg32float";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rg32sint";dimension="1d"] [:method="CopyB2T";format="rg32sint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rg32sint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rg32uint";dimension="1d"] [:method="CopyB2T";format="rg32uint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rg32uint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rg8sint";dimension="1d"] [:method="CopyB2T";format="rg8sint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rg8sint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rg8snorm";dimension="1d"] [:method="CopyB2T";format="rg8snorm";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rg8snorm";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rg8uint";dimension="1d"] [:method="CopyB2T";format="rg8uint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rg8uint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rg8unorm";dimension="1d"] [:method="CopyB2T";format="rg8unorm";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rg8unorm";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rgb10a2uint";dimension="1d"] [:method="CopyB2T";format="rgb10a2uint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rgb10a2uint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rgb10a2unorm";dimension="1d"] [:method="CopyB2T";format="rgb10a2unorm";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rgb10a2unorm";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rgb9e5ufloat";dimension="1d"] [:method="CopyB2T";format="rgb9e5ufloat";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rgb9e5ufloat";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rgba16float";dimension="1d"] [:method="CopyB2T";format="rgba16float";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rgba16float";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rgba16sint";dimension="1d"] [:method="CopyB2T";format="rgba16sint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rgba16sint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rgba16snorm";dimension="1d"] @@ -4911,10 +4675,8 @@ [:method="CopyB2T";format="rgba16uint";dimension="1d"] [:method="CopyB2T";format="rgba16uint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rgba16uint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rgba16unorm";dimension="1d"] @@ -4925,66 +4687,50 @@ [:method="CopyB2T";format="rgba32float";dimension="1d"] [:method="CopyB2T";format="rgba32float";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rgba32float";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rgba32sint";dimension="1d"] [:method="CopyB2T";format="rgba32sint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rgba32sint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rgba32uint";dimension="1d"] [:method="CopyB2T";format="rgba32uint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rgba32uint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rgba8sint";dimension="1d"] [:method="CopyB2T";format="rgba8sint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rgba8sint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rgba8snorm";dimension="1d"] [:method="CopyB2T";format="rgba8snorm";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rgba8snorm";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rgba8uint";dimension="1d"] [:method="CopyB2T";format="rgba8uint";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rgba8uint";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rgba8unorm";dimension="1d"] [:method="CopyB2T";format="rgba8unorm";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rgba8unorm";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="rgba8unorm-srgb";dimension="1d"] [:method="CopyB2T";format="rgba8unorm-srgb";dimension="2d"] - expected: FAIL [:method="CopyB2T";format="rgba8unorm-srgb";dimension="3d"] - expected: FAIL [:method="CopyB2T";format="stencil8";dimension="2d"] @@ -5214,51 +4960,63 @@ [:method="CopyT2B";format="bc1-rgba-unorm";dimension="2d"] expected: - if os == "mac": [TIMEOUT, NOTRUN] + if os == "mac" and debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac" and not debug: [TIMEOUT, NOTRUN] [:method="CopyT2B";format="bc1-rgba-unorm";dimension="3d"] expected: - if os == "mac": [TIMEOUT, NOTRUN] + if os == "mac" and debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac" and not debug: [TIMEOUT, NOTRUN] [:method="CopyT2B";format="bc1-rgba-unorm-srgb";dimension="2d"] expected: - if os == "mac": [TIMEOUT, NOTRUN] + if os == "mac" and debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac" and not debug: [TIMEOUT, NOTRUN] [:method="CopyT2B";format="bc1-rgba-unorm-srgb";dimension="3d"] expected: - if os == "mac": [TIMEOUT, NOTRUN] + if os == "mac" and debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac" and not debug: [TIMEOUT, NOTRUN] [:method="CopyT2B";format="bc2-rgba-unorm";dimension="2d"] expected: - if os == "mac": [TIMEOUT, NOTRUN] + if os == "mac" and debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac" and not debug: [TIMEOUT, NOTRUN] [:method="CopyT2B";format="bc2-rgba-unorm";dimension="3d"] expected: - if os == "mac": [TIMEOUT, NOTRUN] + if os == "mac" and debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac" and not debug: [TIMEOUT, NOTRUN] [:method="CopyT2B";format="bc2-rgba-unorm-srgb";dimension="2d"] expected: - if os == "mac": [TIMEOUT, NOTRUN] + if os == "mac" and debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac" and not debug: [TIMEOUT, NOTRUN] [:method="CopyT2B";format="bc2-rgba-unorm-srgb";dimension="3d"] expected: - if os == "mac": [TIMEOUT, NOTRUN] + if os == "mac" and debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac" and not debug: [TIMEOUT, NOTRUN] [:method="CopyT2B";format="bc3-rgba-unorm";dimension="2d"] expected: - if os == "mac": [TIMEOUT, NOTRUN] + if os == "mac" and debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac" and not debug: [TIMEOUT, NOTRUN] [:method="CopyT2B";format="bc3-rgba-unorm";dimension="3d"] expected: - if os == "mac": [TIMEOUT, NOTRUN] + if os == "mac" and debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac" and not debug: [TIMEOUT, NOTRUN] [:method="CopyT2B";format="bc3-rgba-unorm-srgb";dimension="2d"] expected: - if os == "mac": [TIMEOUT, NOTRUN] + if os == "mac" and debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac" and not debug: [TIMEOUT, NOTRUN] [:method="CopyT2B";format="bc3-rgba-unorm-srgb";dimension="3d"] expected: - if os == "mac": [TIMEOUT, NOTRUN] + if os == "mac" and debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac" and not debug: [TIMEOUT, NOTRUN] [:method="CopyT2B";format="bc4-r-snorm";dimension="2d"] expected: @@ -5326,41 +5084,37 @@ [:method="CopyT2B";format="bgra8unorm";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="bgra8unorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="bgra8unorm";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="bgra8unorm-srgb";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="bgra8unorm-srgb";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="bgra8unorm-srgb";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="depth16unorm";dimension="2d"] expected: - if os == "mac": [TIMEOUT, NOTRUN] + if os == "mac" and debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac" and not debug: [TIMEOUT, NOTRUN] [:method="CopyT2B";format="depth32float";dimension="2d"] expected: - if os == "mac": [TIMEOUT, NOTRUN] + if os == "mac" and debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac" and not debug: [TIMEOUT, NOTRUN] [:method="CopyT2B";format="eac-r11snorm";dimension="2d"] expected: @@ -5404,618 +5158,504 @@ [:method="CopyT2B";format="r16float";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r16float";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r16float";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r16sint";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r16sint";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r16sint";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r16snorm";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r16snorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r16snorm";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r16uint";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r16uint";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r16uint";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r16unorm";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r16unorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r16unorm";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r32float";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r32float";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r32float";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r32sint";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r32sint";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r32sint";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r32uint";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r32uint";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r32uint";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r8sint";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r8sint";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r8sint";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r8snorm";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r8snorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r8snorm";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r8uint";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r8uint";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r8uint";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r8unorm";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r8unorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="r8unorm";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg11b10ufloat";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg11b10ufloat";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg11b10ufloat";dimension="3d"] expected: - if os == "mac": [TIMEOUT, NOTRUN] + if os == "mac" and debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac" and not debug: [TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg16float";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg16float";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg16float";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg16sint";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg16sint";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg16sint";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg16snorm";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg16snorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg16snorm";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg16uint";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg16uint";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg16uint";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg16unorm";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg16unorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg16unorm";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg32float";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg32float";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg32float";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg32sint";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg32sint";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg32sint";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg32uint";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg32uint";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg32uint";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg8sint";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg8sint";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg8sint";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg8snorm";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg8snorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg8snorm";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg8uint";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg8uint";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg8uint";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg8unorm";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg8unorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rg8unorm";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgb10a2uint";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgb10a2uint";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgb10a2uint";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgb10a2unorm";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgb10a2unorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgb10a2unorm";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgb9e5ufloat";dimension="1d"] expected: - if os == "mac": [TIMEOUT, NOTRUN] + if os == "mac" and debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac" and not debug: [TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgb9e5ufloat";dimension="2d"] expected: - if os == "mac": [TIMEOUT, NOTRUN] + if os == "mac" and debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac" and not debug: [TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgb9e5ufloat";dimension="3d"] expected: - if os == "mac": [TIMEOUT, NOTRUN] + if os == "mac" and debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac" and not debug: [TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba16float";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba16float";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba16float";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba16sint";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba16sint";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba16sint";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba16snorm";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba16snorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba16snorm";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba16uint";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba16uint";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba16uint";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba16unorm";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba16unorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba16unorm";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba32float";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba32float";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba32float";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba32sint";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba32sint";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba32sint";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba32uint";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba32uint";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba32uint";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba8sint";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba8sint";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba8sint";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba8snorm";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba8snorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba8snorm";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba8uint";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba8uint";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba8uint";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba8unorm";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba8unorm";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba8unorm";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba8unorm-srgb";dimension="1d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba8unorm-srgb";dimension="2d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="rgba8unorm-srgb";dimension="3d"] expected: - if os == "mac" and debug: [TIMEOUT, NOTRUN] - if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac": [PASS, TIMEOUT, NOTRUN] [:method="CopyT2B";format="stencil8";dimension="2d"] expected: - if os == "mac": [TIMEOUT, NOTRUN] + if os == "mac" and debug: [PASS, TIMEOUT, NOTRUN] + if os == "mac" and not debug: [TIMEOUT, NOTRUN] [:method="WriteTexture";format="astc-10x10-unorm";dimension="2d"] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/api/validation/render_pass/render_pass_descriptor/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/api/validation/render_pass/render_pass_descriptor/cts.https.html.ini @@ -72,12 +72,18 @@ [cts.https.html?q=webgpu:api,validation,render_pass,render_pass_descriptor:color_attachments,limits,maxColorAttachmentBytesPerSample,aligned:*] - implementation-status: backlog + implementation-status: + if os == "win": backlog + if os == "linux": backlog [:format="bgra8unorm"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:format="bgra8unorm-srgb"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:format="r16float"] @@ -102,7 +108,9 @@ [:format="r8unorm"] [:format="rg11b10ufloat"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:format="rg16float"] @@ -115,13 +123,19 @@ [:format="rg16unorm"] [:format="rg32float"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:format="rg32sint"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:format="rg32uint"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:format="rg8sint"] @@ -130,50 +144,74 @@ [:format="rg8unorm"] [:format="rgb10a2uint"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:format="rgb10a2unorm"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:format="rgba16float"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:format="rgba16sint"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:format="rgba16snorm"] [:format="rgba16uint"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:format="rgba16unorm"] [:format="rgba32float"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:format="rgba32sint"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:format="rgba32uint"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:format="rgba8sint"] [:format="rgba8uint"] [:format="rgba8unorm"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [:format="rgba8unorm-srgb"] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [cts.https.html?q=webgpu:api,validation,render_pass,render_pass_descriptor:color_attachments,limits,maxColorAttachmentBytesPerSample,unaligned:*] - implementation-status: backlog + implementation-status: + if os == "win": backlog + if os == "linux": backlog [:formats=["r32float","rgba8unorm","rgba32float","r8unorm","r8unorm"\]] [:formats=["r8unorm","r32float","rgba8unorm","rgba32float","r8unorm"\]] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL [cts.https.html?q=webgpu:api,validation,render_pass,render_pass_descriptor:color_attachments,limits,maxColorAttachments:*] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/api/validation/state/device_lost/destroy/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/api/validation/state/device_lost/destroy/cts.https.html.ini @@ -8221,15 +8221,13 @@ [:format="r16sint";usageType="render";usageCopy="none";awaitLost=false] expected: - if os == "win" and debug: FAIL - if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "win": [FAIL, TIMEOUT, NOTRUN] if os == "linux": FAIL if os == "mac": [TIMEOUT, NOTRUN] [:format="r16sint";usageType="render";usageCopy="none";awaitLost=true] expected: - if os == "win" and debug: FAIL - if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "win": [FAIL, TIMEOUT, NOTRUN] if os == "linux": FAIL if os == "mac": [TIMEOUT, NOTRUN] @@ -8259,111 +8257,99 @@ [:format="r16sint";usageType="storage";usageCopy="dst";awaitLost=false] expected: - if os == "win" and debug: FAIL - if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "win": [FAIL, TIMEOUT, NOTRUN] if os == "linux": FAIL if os == "mac": [TIMEOUT, NOTRUN] [:format="r16sint";usageType="storage";usageCopy="dst";awaitLost=true] expected: - if os == "win" and debug: FAIL - if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "win": [FAIL, TIMEOUT, NOTRUN] if os == "linux": FAIL if os == "mac": [TIMEOUT, NOTRUN] [:format="r16sint";usageType="storage";usageCopy="none";awaitLost=false] expected: - if os == "win" and debug: FAIL - if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "win": [FAIL, TIMEOUT, NOTRUN] if os == "linux": FAIL if os == "mac": [TIMEOUT, NOTRUN] [:format="r16sint";usageType="storage";usageCopy="none";awaitLost=true] expected: - if os == "win" and debug: FAIL - if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "win": [FAIL, TIMEOUT, NOTRUN] if os == "linux": FAIL if os == "mac": [TIMEOUT, NOTRUN] [:format="r16sint";usageType="storage";usageCopy="src";awaitLost=false] expected: - if os == "win" and debug: FAIL - if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "win": [FAIL, TIMEOUT, NOTRUN] if os == "linux": FAIL if os == "mac": [TIMEOUT, NOTRUN] [:format="r16sint";usageType="storage";usageCopy="src";awaitLost=true] expected: - if os == "win" and debug: FAIL - if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "win": [FAIL, TIMEOUT, NOTRUN] if os == "linux": FAIL if os == "mac": [TIMEOUT, NOTRUN] [:format="r16sint";usageType="storage";usageCopy="src-dest";awaitLost=false] expected: - if os == "win" and debug: FAIL - if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "win": [FAIL, TIMEOUT, NOTRUN] if os == "linux": FAIL if os == "mac": [TIMEOUT, NOTRUN] [:format="r16sint";usageType="storage";usageCopy="src-dest";awaitLost=true] expected: - if os == "win" and debug: FAIL - if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "win": [FAIL, TIMEOUT, NOTRUN] if os == "linux": FAIL if os == "mac": [TIMEOUT, NOTRUN] [:format="r16sint";usageType="texture";usageCopy="dst";awaitLost=false] expected: - if os == "win" and debug: FAIL - if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "win": [FAIL, TIMEOUT, NOTRUN] if os == "linux": FAIL if os == "mac": [TIMEOUT, NOTRUN] [:format="r16sint";usageType="texture";usageCopy="dst";awaitLost=true] expected: - if os == "win" and debug: FAIL - if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "win": [FAIL, TIMEOUT, NOTRUN] if os == "linux": FAIL if os == "mac": [TIMEOUT, NOTRUN] [:format="r16sint";usageType="texture";usageCopy="none";awaitLost=false] expected: - if os == "win": FAIL + if os == "win" and debug: [FAIL, TIMEOUT, NOTRUN] + if os == "win" and not debug: FAIL if os == "linux": FAIL if os == "mac": [TIMEOUT, NOTRUN] [:format="r16sint";usageType="texture";usageCopy="none";awaitLost=true] expected: - if os == "win": FAIL + if os == "win" and debug: [FAIL, TIMEOUT, NOTRUN] + if os == "win" and not debug: FAIL if os == "linux": FAIL if os == "mac": [TIMEOUT, NOTRUN] [:format="r16sint";usageType="texture";usageCopy="src";awaitLost=false] expected: - if os == "win" and debug: FAIL - if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "win": [FAIL, TIMEOUT, NOTRUN] if os == "linux": FAIL if os == "mac": [TIMEOUT, NOTRUN] [:format="r16sint";usageType="texture";usageCopy="src";awaitLost=true] expected: - if os == "win" and debug: FAIL - if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "win": [FAIL, TIMEOUT, NOTRUN] if os == "linux": FAIL if os == "mac": [TIMEOUT, NOTRUN] [:format="r16sint";usageType="texture";usageCopy="src-dest";awaitLost=false] expected: - if os == "win" and debug: FAIL - if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "win": [FAIL, TIMEOUT, NOTRUN] if os == "linux": FAIL if os == "mac": [TIMEOUT, NOTRUN] [:format="r16sint";usageType="texture";usageCopy="src-dest";awaitLost=true] expected: - if os == "win" and debug: FAIL - if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "win": [FAIL, TIMEOUT, NOTRUN] if os == "linux": FAIL if os == "mac": [TIMEOUT, NOTRUN] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/execution/expression/call/builtin/subgroupMul/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/execution/expression/call/builtin/subgroupMul/cts.https.html.ini @@ -9758,8 +9758,7 @@ [:case=747;type="f16";wgSize=[128,1,1\]] expected: - if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: [PASS, TIMEOUT, NOTRUN] + if os == "win": [PASS, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: [PASS, TIMEOUT, NOTRUN] if os == "mac": [TIMEOUT, NOTRUN] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureGatherCompare/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureGatherCompare/cts.https.html.ini @@ -3,6 +3,7 @@ implementation-status: backlog expected: if os == "win" and debug: TIMEOUT + if os == "win" and not debug: [OK, TIMEOUT] if os == "linux" and debug: TIMEOUT if os == "mac" and debug: TIMEOUT if os == "mac" and not debug: [OK, TIMEOUT] @@ -1857,93 +1858,165 @@ [:stage="v";format="depth16unorm";filt="linear";modeU="c";modeV="c";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth16unorm";filt="linear";modeU="c";modeV="c";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth16unorm";filt="linear";modeU="c";modeV="m";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth16unorm";filt="linear";modeU="c";modeV="m";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth16unorm";filt="linear";modeU="c";modeV="r";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth16unorm";filt="linear";modeU="c";modeV="r";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth16unorm";filt="linear";modeU="m";modeV="c";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth16unorm";filt="linear";modeU="m";modeV="c";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth16unorm";filt="linear";modeU="m";modeV="m";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth16unorm";filt="linear";modeU="m";modeV="m";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth16unorm";filt="linear";modeU="m";modeV="r";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth16unorm";filt="linear";modeU="m";modeV="r";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth16unorm";filt="linear";modeU="r";modeV="c";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth16unorm";filt="linear";modeU="r";modeV="c";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth16unorm";filt="linear";modeU="r";modeV="m";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth16unorm";filt="linear";modeU="r";modeV="m";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth16unorm";filt="linear";modeU="r";modeV="r";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth16unorm";filt="linear";modeU="r";modeV="r";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth16unorm";filt="nearest";modeU="c";modeV="c";offset=false] expected: @@ -1989,33 +2062,57 @@ [:stage="v";format="depth16unorm";filt="nearest";modeU="m";modeV="c";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth16unorm";filt="nearest";modeU="m";modeV="c";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth16unorm";filt="nearest";modeU="m";modeV="m";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth16unorm";filt="nearest";modeU="m";modeV="m";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth16unorm";filt="nearest";modeU="m";modeV="r";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth16unorm";filt="nearest";modeU="m";modeV="r";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth16unorm";filt="nearest";modeU="r";modeV="c";offset=false] expected: @@ -2029,13 +2126,21 @@ [:stage="v";format="depth16unorm";filt="nearest";modeU="r";modeV="m";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth16unorm";filt="nearest";modeU="r";modeV="m";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth16unorm";filt="nearest";modeU="r";modeV="r";offset=false] expected: @@ -2044,218 +2149,386 @@ [:stage="v";format="depth16unorm";filt="nearest";modeU="r";modeV="r";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="linear";modeU="c";modeV="c";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="linear";modeU="c";modeV="c";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="linear";modeU="c";modeV="m";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="linear";modeU="c";modeV="m";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="linear";modeU="c";modeV="r";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="linear";modeU="c";modeV="r";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="linear";modeU="m";modeV="c";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="linear";modeU="m";modeV="c";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="linear";modeU="m";modeV="m";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="linear";modeU="m";modeV="m";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="linear";modeU="m";modeV="r";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="linear";modeU="m";modeV="r";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="linear";modeU="r";modeV="c";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="linear";modeU="r";modeV="c";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="linear";modeU="r";modeV="m";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="linear";modeU="r";modeV="m";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="linear";modeU="r";modeV="r";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="linear";modeU="r";modeV="r";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="nearest";modeU="c";modeV="c";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="nearest";modeU="c";modeV="c";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="nearest";modeU="c";modeV="m";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="nearest";modeU="c";modeV="m";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="nearest";modeU="c";modeV="r";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="nearest";modeU="c";modeV="r";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="nearest";modeU="m";modeV="c";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="nearest";modeU="m";modeV="c";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="nearest";modeU="m";modeV="m";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="nearest";modeU="m";modeV="m";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="nearest";modeU="m";modeV="r";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="nearest";modeU="m";modeV="r";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="nearest";modeU="r";modeV="c";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="nearest";modeU="r";modeV="c";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="nearest";modeU="r";modeV="m";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="nearest";modeU="r";modeV="m";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="nearest";modeU="r";modeV="r";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus";filt="nearest";modeU="r";modeV="r";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus-stencil8";filt="linear";modeU="c";modeV="c";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus-stencil8";filt="linear";modeU="c";modeV="c";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus-stencil8";filt="linear";modeU="c";modeV="m";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus-stencil8";filt="linear";modeU="c";modeV="m";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus-stencil8";filt="linear";modeU="c";modeV="r";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus-stencil8";filt="linear";modeU="c";modeV="r";offset=true] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2264,7 +2537,7 @@ [:stage="v";format="depth24plus-stencil8";filt="linear";modeU="m";modeV="c";offset=false] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2273,7 +2546,7 @@ [:stage="v";format="depth24plus-stencil8";filt="linear";modeU="m";modeV="c";offset=true] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2282,7 +2555,7 @@ [:stage="v";format="depth24plus-stencil8";filt="linear";modeU="m";modeV="m";offset=false] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2291,7 +2564,7 @@ [:stage="v";format="depth24plus-stencil8";filt="linear";modeU="m";modeV="m";offset=true] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2300,7 +2573,7 @@ [:stage="v";format="depth24plus-stencil8";filt="linear";modeU="m";modeV="r";offset=false] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2309,7 +2582,7 @@ [:stage="v";format="depth24plus-stencil8";filt="linear";modeU="m";modeV="r";offset=true] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2317,18 +2590,26 @@ [:stage="v";format="depth24plus-stencil8";filt="linear";modeU="r";modeV="c";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus-stencil8";filt="linear";modeU="r";modeV="c";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus-stencil8";filt="linear";modeU="r";modeV="m";offset=false] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2337,7 +2618,7 @@ [:stage="v";format="depth24plus-stencil8";filt="linear";modeU="r";modeV="m";offset=true] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2345,28 +2626,44 @@ [:stage="v";format="depth24plus-stencil8";filt="linear";modeU="r";modeV="r";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus-stencil8";filt="linear";modeU="r";modeV="r";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus-stencil8";filt="nearest";modeU="c";modeV="c";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus-stencil8";filt="nearest";modeU="c";modeV="c";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus-stencil8";filt="nearest";modeU="c";modeV="m";offset=false] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2375,7 +2672,7 @@ [:stage="v";format="depth24plus-stencil8";filt="nearest";modeU="c";modeV="m";offset=true] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2383,13 +2680,17 @@ [:stage="v";format="depth24plus-stencil8";filt="nearest";modeU="c";modeV="r";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus-stencil8";filt="nearest";modeU="c";modeV="r";offset=true] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2397,48 +2698,80 @@ [:stage="v";format="depth24plus-stencil8";filt="nearest";modeU="m";modeV="c";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus-stencil8";filt="nearest";modeU="m";modeV="c";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus-stencil8";filt="nearest";modeU="m";modeV="m";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus-stencil8";filt="nearest";modeU="m";modeV="m";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus-stencil8";filt="nearest";modeU="m";modeV="r";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus-stencil8";filt="nearest";modeU="m";modeV="r";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus-stencil8";filt="nearest";modeU="r";modeV="c";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus-stencil8";filt="nearest";modeU="r";modeV="c";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus-stencil8";filt="nearest";modeU="r";modeV="m";offset=false] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2446,203 +2779,359 @@ [:stage="v";format="depth24plus-stencil8";filt="nearest";modeU="r";modeV="m";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus-stencil8";filt="nearest";modeU="r";modeV="r";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth24plus-stencil8";filt="nearest";modeU="r";modeV="r";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="linear";modeU="c";modeV="c";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="linear";modeU="c";modeV="c";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="linear";modeU="c";modeV="m";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="linear";modeU="c";modeV="m";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="linear";modeU="c";modeV="r";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="linear";modeU="c";modeV="r";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="linear";modeU="m";modeV="c";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="linear";modeU="m";modeV="c";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="linear";modeU="m";modeV="m";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="linear";modeU="m";modeV="m";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="linear";modeU="m";modeV="r";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="linear";modeU="m";modeV="r";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="linear";modeU="r";modeV="c";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="linear";modeU="r";modeV="c";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="linear";modeU="r";modeV="m";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="linear";modeU="r";modeV="m";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="linear";modeU="r";modeV="r";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="linear";modeU="r";modeV="r";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="nearest";modeU="c";modeV="c";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="nearest";modeU="c";modeV="c";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="nearest";modeU="c";modeV="m";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="nearest";modeU="c";modeV="m";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="nearest";modeU="c";modeV="r";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="nearest";modeU="c";modeV="r";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="nearest";modeU="m";modeV="c";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="nearest";modeU="m";modeV="c";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="nearest";modeU="m";modeV="m";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="nearest";modeU="m";modeV="m";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="nearest";modeU="m";modeV="r";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="nearest";modeU="m";modeV="r";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="nearest";modeU="r";modeV="c";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="nearest";modeU="r";modeV="c";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="nearest";modeU="r";modeV="m";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="nearest";modeU="r";modeV="m";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="nearest";modeU="r";modeV="r";offset=false] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float";filt="nearest";modeU="r";modeV="r";offset=true] expected: - if debug: [TIMEOUT, NOTRUN] - if not debug: FAIL + if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: FAIL + if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:stage="v";format="depth32float-stencil8";filt="linear";modeU="c";modeV="c";offset=false] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2651,7 +3140,7 @@ [:stage="v";format="depth32float-stencil8";filt="linear";modeU="c";modeV="c";offset=true] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2660,7 +3149,7 @@ [:stage="v";format="depth32float-stencil8";filt="linear";modeU="c";modeV="m";offset=false] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2669,7 +3158,7 @@ [:stage="v";format="depth32float-stencil8";filt="linear";modeU="c";modeV="m";offset=true] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2678,7 +3167,7 @@ [:stage="v";format="depth32float-stencil8";filt="linear";modeU="c";modeV="r";offset=false] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2687,7 +3176,7 @@ [:stage="v";format="depth32float-stencil8";filt="linear";modeU="c";modeV="r";offset=true] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2696,7 +3185,7 @@ [:stage="v";format="depth32float-stencil8";filt="linear";modeU="m";modeV="c";offset=false] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2705,7 +3194,7 @@ [:stage="v";format="depth32float-stencil8";filt="linear";modeU="m";modeV="c";offset=true] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2714,7 +3203,7 @@ [:stage="v";format="depth32float-stencil8";filt="linear";modeU="m";modeV="m";offset=false] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2723,7 +3212,7 @@ [:stage="v";format="depth32float-stencil8";filt="linear";modeU="m";modeV="m";offset=true] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2732,7 +3221,7 @@ [:stage="v";format="depth32float-stencil8";filt="linear";modeU="m";modeV="r";offset=false] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2741,7 +3230,7 @@ [:stage="v";format="depth32float-stencil8";filt="linear";modeU="m";modeV="r";offset=true] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2750,7 +3239,7 @@ [:stage="v";format="depth32float-stencil8";filt="linear";modeU="r";modeV="c";offset=false] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2759,7 +3248,7 @@ [:stage="v";format="depth32float-stencil8";filt="linear";modeU="r";modeV="c";offset=true] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2768,7 +3257,7 @@ [:stage="v";format="depth32float-stencil8";filt="linear";modeU="r";modeV="m";offset=false] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2777,7 +3266,7 @@ [:stage="v";format="depth32float-stencil8";filt="linear";modeU="r";modeV="m";offset=true] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2786,7 +3275,7 @@ [:stage="v";format="depth32float-stencil8";filt="linear";modeU="r";modeV="r";offset=false] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2795,7 +3284,7 @@ [:stage="v";format="depth32float-stencil8";filt="linear";modeU="r";modeV="r";offset=true] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2804,7 +3293,7 @@ [:stage="v";format="depth32float-stencil8";filt="nearest";modeU="c";modeV="c";offset=false] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2813,7 +3302,7 @@ [:stage="v";format="depth32float-stencil8";filt="nearest";modeU="c";modeV="c";offset=true] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2822,7 +3311,7 @@ [:stage="v";format="depth32float-stencil8";filt="nearest";modeU="c";modeV="m";offset=false] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2831,7 +3320,7 @@ [:stage="v";format="depth32float-stencil8";filt="nearest";modeU="c";modeV="m";offset=true] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2840,7 +3329,7 @@ [:stage="v";format="depth32float-stencil8";filt="nearest";modeU="c";modeV="r";offset=false] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2849,7 +3338,7 @@ [:stage="v";format="depth32float-stencil8";filt="nearest";modeU="c";modeV="r";offset=true] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2858,7 +3347,7 @@ [:stage="v";format="depth32float-stencil8";filt="nearest";modeU="m";modeV="c";offset=false] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2867,7 +3356,7 @@ [:stage="v";format="depth32float-stencil8";filt="nearest";modeU="m";modeV="c";offset=true] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2876,7 +3365,7 @@ [:stage="v";format="depth32float-stencil8";filt="nearest";modeU="m";modeV="m";offset=false] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2885,7 +3374,7 @@ [:stage="v";format="depth32float-stencil8";filt="nearest";modeU="m";modeV="m";offset=true] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2894,7 +3383,7 @@ [:stage="v";format="depth32float-stencil8";filt="nearest";modeU="m";modeV="r";offset=false] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2903,7 +3392,7 @@ [:stage="v";format="depth32float-stencil8";filt="nearest";modeU="m";modeV="r";offset=true] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2912,7 +3401,7 @@ [:stage="v";format="depth32float-stencil8";filt="nearest";modeU="r";modeV="c";offset=false] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2921,7 +3410,7 @@ [:stage="v";format="depth32float-stencil8";filt="nearest";modeU="r";modeV="c";offset=true] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2930,7 +3419,7 @@ [:stage="v";format="depth32float-stencil8";filt="nearest";modeU="r";modeV="m";offset=false] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2939,7 +3428,7 @@ [:stage="v";format="depth32float-stencil8";filt="nearest";modeU="r";modeV="m";offset=true] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2948,7 +3437,7 @@ [:stage="v";format="depth32float-stencil8";filt="nearest";modeU="r";modeV="r";offset=false] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] @@ -2957,7 +3446,7 @@ [:stage="v";format="depth32float-stencil8";filt="nearest";modeU="r";modeV="r";offset=true] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "win" and not debug: FAIL + if os == "win" and not debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and debug: [TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureSampleCompare/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureSampleCompare/cts.https.html.ini @@ -1340,16 +1340,32 @@ expected: FAIL [:format="depth32float";filt="linear";modeU="c";modeV="m";offset=false] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL + if os == "mac" and debug: [FAIL, TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:format="depth32float";filt="linear";modeU="c";modeV="m";offset=true] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL + if os == "mac" and debug: [FAIL, TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:format="depth32float";filt="linear";modeU="c";modeV="r";offset=false] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL + if os == "mac" and debug: [FAIL, TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:format="depth32float";filt="linear";modeU="c";modeV="r";offset=true] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL + if os == "mac" and debug: [FAIL, TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:format="depth32float";filt="linear";modeU="m";modeV="c";offset=false] expected: @@ -1394,22 +1410,46 @@ if os == "mac" and not debug: FAIL [:format="depth32float";filt="linear";modeU="r";modeV="c";offset=false] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL + if os == "mac" and debug: [FAIL, TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:format="depth32float";filt="linear";modeU="r";modeV="c";offset=true] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL + if os == "mac" and debug: [FAIL, TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:format="depth32float";filt="linear";modeU="r";modeV="m";offset=false] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL + if os == "mac" and debug: [FAIL, TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:format="depth32float";filt="linear";modeU="r";modeV="m";offset=true] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL + if os == "mac" and debug: [FAIL, TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:format="depth32float";filt="linear";modeU="r";modeV="r";offset=false] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL + if os == "mac" and debug: [FAIL, TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:format="depth32float";filt="linear";modeU="r";modeV="r";offset=true] - expected: FAIL + expected: + if os == "win": FAIL + if os == "linux": FAIL + if os == "mac" and debug: [FAIL, TIMEOUT, NOTRUN] + if os == "mac" and not debug: FAIL [:format="depth32float";filt="nearest";modeU="c";modeV="c";offset=false] expected: diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureSampleLevel/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureSampleLevel/cts.https.html.ini @@ -19692,7 +19692,8 @@ [:stage="c";format="bc3-rgba-unorm";filt="linear";modeU="c";modeV="c";offset=false] expected: - if os == "linux": [TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: [PASS, TIMEOUT, NOTRUN] if os == "mac": [TIMEOUT, NOTRUN] [:stage="c";format="bc3-rgba-unorm";filt="linear";modeU="c";modeV="c";offset=true] @@ -88532,7 +88533,7 @@ [:stage="c";format="depth32float";filt="nearest";mode="c"] expected: - if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and debug: [FAIL, TIMEOUT, NOTRUN] if os == "win" and not debug: FAIL if os == "linux" and debug: [FAIL, TIMEOUT, NOTRUN] if os == "linux" and not debug: FAIL @@ -88550,7 +88551,7 @@ [:stage="c";format="depth32float";filt="nearest";mode="r"] expected: - if os == "win" and debug: [TIMEOUT, NOTRUN] + if os == "win" and debug: [FAIL, TIMEOUT, NOTRUN] if os == "win" and not debug: FAIL if os == "linux": FAIL if os == "mac" and debug: [TIMEOUT, NOTRUN] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/execution/flow_control/call/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/execution/flow_control/call/cts.https.html.ini @@ -5,21 +5,15 @@ [cts.https.html?q=webgpu:shader,execution,flow_control,call:arg_eval_logical_and:*] - implementation-status: backlog [:preventValueOptimizations=false] - expected: FAIL [:preventValueOptimizations=true] - expected: FAIL [cts.https.html?q=webgpu:shader,execution,flow_control,call:arg_eval_logical_or:*] - implementation-status: backlog [:preventValueOptimizations=false] - expected: FAIL [:preventValueOptimizations=true] - expected: FAIL [cts.https.html?q=webgpu:shader,execution,flow_control,call:arg_eval_pointers:*] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/execution/flow_control/eval_order/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/execution/flow_control/eval_order/cts.https.html.ini @@ -99,15 +99,11 @@ [cts.https.html?q=webgpu:shader,execution,flow_control,eval_order:logical_and:*] - implementation-status: backlog [:] - expected: FAIL [cts.https.html?q=webgpu:shader,execution,flow_control,eval_order:logical_or:*] - implementation-status: backlog [:] - expected: FAIL [cts.https.html?q=webgpu:shader,execution,flow_control,eval_order:matrix_index:*] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/execution/flow_control/for/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/execution/flow_control/for/cts.https.html.ini @@ -53,21 +53,15 @@ [cts.https.html?q=webgpu:shader,execution,flow_control,for:for_logical_and_condition:*] - implementation-status: backlog [:preventValueOptimizations=false] - expected: FAIL [:preventValueOptimizations=true] - expected: FAIL [cts.https.html?q=webgpu:shader,execution,flow_control,for:for_logical_or_condition:*] - implementation-status: backlog [:preventValueOptimizations=false] - expected: FAIL [:preventValueOptimizations=true] - expected: FAIL [cts.https.html?q=webgpu:shader,execution,flow_control,for:nested_for_break:*] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/execution/flow_control/loop/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/execution/flow_control/loop/cts.https.html.ini @@ -5,21 +5,15 @@ [cts.https.html?q=webgpu:shader,execution,flow_control,loop:loop_break_if_logical_and_condition:*] - implementation-status: backlog [:preventValueOptimizations=false] - expected: FAIL [:preventValueOptimizations=true] - expected: FAIL [cts.https.html?q=webgpu:shader,execution,flow_control,loop:loop_break_if_logical_or_condition:*] - implementation-status: backlog [:preventValueOptimizations=false] - expected: FAIL [:preventValueOptimizations=true] - expected: FAIL [cts.https.html?q=webgpu:shader,execution,flow_control,loop:loop_continue:*] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/execution/flow_control/while/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/execution/flow_control/while/cts.https.html.ini @@ -17,21 +17,15 @@ [cts.https.html?q=webgpu:shader,execution,flow_control,while:while_logical_and_condition:*] - implementation-status: backlog [:preventValueOptimizations=false] - expected: FAIL [:preventValueOptimizations=true] - expected: FAIL [cts.https.html?q=webgpu:shader,execution,flow_control,while:while_logical_or_condition:*] - implementation-status: backlog [:preventValueOptimizations=false] - expected: FAIL [:preventValueOptimizations=true] - expected: FAIL [cts.https.html?q=webgpu:shader,execution,flow_control,while:while_nested_break:*] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/execution/robust_access_vertex/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/execution/robust_access_vertex/cts.https.html.ini @@ -217,6 +217,8 @@ [:indexed=true;indirect=false;drawCallTestParameter="baseVertex";type="float32x2";additionalBuffers=0;partialLastNumber=false;offsetVertexBuffer=false] [:indexed=true;indirect=false;drawCallTestParameter="baseVertex";type="float32x2";additionalBuffers=0;partialLastNumber=false;offsetVertexBuffer=true] + expected: + if os == "linux" and debug: [PASS, FAIL] [:indexed=true;indirect=false;drawCallTestParameter="baseVertex";type="float32x2";additionalBuffers=0;partialLastNumber=true;offsetVertexBuffer=false] @@ -254,6 +256,9 @@ [:indexed=true;indirect=false;drawCallTestParameter="baseVertex";type="float32x4";additionalBuffers=0;partialLastNumber=false;offsetVertexBuffer=false] [:indexed=true;indirect=false;drawCallTestParameter="baseVertex";type="float32x4";additionalBuffers=0;partialLastNumber=false;offsetVertexBuffer=true] + expected: + if os == "linux" and debug: [PASS, FAIL] + if os == "linux" and not debug: FAIL [:indexed=true;indirect=false;drawCallTestParameter="baseVertex";type="float32x4";additionalBuffers=0;partialLastNumber=true;offsetVertexBuffer=false] @@ -264,6 +269,7 @@ [:indexed=true;indirect=false;drawCallTestParameter="baseVertex";type="float32x4";additionalBuffers=4;partialLastNumber=false;offsetVertexBuffer=true] expected: if os == "win": FAIL + if os == "linux": FAIL [:indexed=true;indirect=false;drawCallTestParameter="baseVertex";type="float32x4";additionalBuffers=4;partialLastNumber=true;offsetVertexBuffer=false] @@ -291,7 +297,7 @@ [:indexed=true;indirect=false;drawCallTestParameter="vertexCountInIndexBuffer";type="float32x2";additionalBuffers=0;partialLastNumber=false;offsetVertexBuffer=true] expected: - if os == "linux": FAIL + if os == "linux" and debug: FAIL [:indexed=true;indirect=false;drawCallTestParameter="vertexCountInIndexBuffer";type="float32x2";additionalBuffers=0;partialLastNumber=true;offsetVertexBuffer=false] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/decl/const/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/decl/const/cts.https.html.ini @@ -69,9 +69,7 @@ [cts.https.html?q=webgpu:shader,validation,decl,const:placement:*] - implementation-status: backlog [:scope="_undef_"] - expected: FAIL [:scope="fn-decl"] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/decl/var/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/decl/var/cts.https.html.ini @@ -1,7 +1,6 @@ [cts.https.html?q=webgpu:shader,validation,decl,var:address_space_access_mode:*] implementation-status: backlog [:address_space="function";access_mode="";trailing_comma=false] - expected: FAIL [:address_space="function";access_mode="";trailing_comma=true] expected: FAIL @@ -510,11 +509,9 @@ [cts.https.html?q=webgpu:shader,validation,decl,var:implicit_access_mode:*] - implementation-status: backlog [:addressSpace="function";explicitSpace=false;explicitAccess=false;accessMode="";stage="compute"] [:addressSpace="function";explicitSpace=true;explicitAccess=false;accessMode="";stage="compute"] - expected: FAIL [:addressSpace="private";explicitSpace=true;explicitAccess=false;accessMode="";stage="compute"] @@ -1413,16 +1410,13 @@ [cts.https.html?q=webgpu:shader,validation,decl,var:read_access:*] - implementation-status: backlog [:addressSpace="function";explicitSpace=false;explicitAccess=false;accessMode="";stage="compute"] [:addressSpace="function";explicitSpace=false;explicitAccess=true;accessMode="";stage="compute"] [:addressSpace="function";explicitSpace=true;explicitAccess=false;accessMode="";stage="compute"] - expected: FAIL [:addressSpace="function";explicitSpace=true;explicitAccess=true;accessMode="";stage="compute"] - expected: FAIL [:addressSpace="private";explicitSpace=true;explicitAccess=false;accessMode="";stage="compute"] @@ -1625,16 +1619,13 @@ [cts.https.html?q=webgpu:shader,validation,decl,var:write_access:*] - implementation-status: backlog [:addressSpace="function";explicitSpace=false;explicitAccess=false;accessMode="";stage="compute"] [:addressSpace="function";explicitSpace=false;explicitAccess=true;accessMode="";stage="compute"] [:addressSpace="function";explicitSpace=true;explicitAccess=false;accessMode="";stage="compute"] - expected: FAIL [:addressSpace="function";explicitSpace=true;explicitAccess=true;accessMode="";stage="compute"] - expected: FAIL [:addressSpace="private";explicitSpace=true;explicitAccess=false;accessMode="";stage="compute"] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/binary/short_circuiting_and_or/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/binary/short_circuiting_and_or/cts.https.html.ini @@ -336,33 +336,45 @@ [:op="%26%26";lhs="abstract-int";rhs="vec2%3Cu32%3E"] [:op="%26%26";lhs="bool";rhs="abstract-float"] + expected: FAIL [:op="%26%26";lhs="bool";rhs="abstract-int"] + expected: FAIL [:op="%26%26";lhs="bool";rhs="bool"] [:op="%26%26";lhs="bool";rhs="f16"] + expected: FAIL [:op="%26%26";lhs="bool";rhs="f32"] + expected: FAIL [:op="%26%26";lhs="bool";rhs="i32"] + expected: FAIL [:op="%26%26";lhs="bool";rhs="u32"] + expected: FAIL [:op="%26%26";lhs="bool";rhs="vec2%3Cabstract-float%3E"] + expected: FAIL [:op="%26%26";lhs="bool";rhs="vec2%3Cabstract-int%3E"] + expected: FAIL [:op="%26%26";lhs="bool";rhs="vec2%3Cbool%3E"] expected: FAIL [:op="%26%26";lhs="bool";rhs="vec2%3Cf16%3E"] + expected: FAIL [:op="%26%26";lhs="bool";rhs="vec2%3Cf32%3E"] + expected: FAIL [:op="%26%26";lhs="bool";rhs="vec2%3Ci32%3E"] + expected: FAIL [:op="%26%26";lhs="bool";rhs="vec2%3Cu32%3E"] + expected: FAIL [:op="%26%26";lhs="f16";rhs="abstract-float"] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/abs/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/abs/cts.https.html.ini @@ -23,7 +23,6 @@ [:test="ptr"] [:test="ptr_deref"] - expected: FAIL [:test="sampler"] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/acos/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/acos/cts.https.html.ini @@ -48,7 +48,6 @@ [:test="ptr"] [:test="ptr_deref"] - expected: FAIL [:test="sampler"] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/acosh/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/acosh/cts.https.html.ini @@ -44,7 +44,6 @@ [:test="ptr"] [:test="ptr_deref"] - expected: FAIL [:test="sampler"] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/all/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/all/cts.https.html.ini @@ -57,7 +57,6 @@ [cts.https.html?q=webgpu:shader,validation,expression,call,builtin,all:arguments:*] - implementation-status: backlog [:test="alias"] [:test="array"] @@ -83,7 +82,6 @@ [:test="ptr"] [:test="ptr_deref"] - expected: FAIL [:test="sampler"] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/any/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/any/cts.https.html.ini @@ -57,7 +57,6 @@ [cts.https.html?q=webgpu:shader,validation,expression,call,builtin,any:arguments:*] - implementation-status: backlog [:test="alias"] [:test="array"] @@ -83,7 +82,6 @@ [:test="ptr"] [:test="ptr_deref"] - expected: FAIL [:test="sampler"] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/asin/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/asin/cts.https.html.ini @@ -48,7 +48,6 @@ [:test="ptr"] [:test="ptr_deref"] - expected: FAIL [:test="sampler"] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/asinh/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/asinh/cts.https.html.ini @@ -44,7 +44,6 @@ [:test="ptr"] [:test="ptr_deref"] - expected: FAIL [:test="sampler"] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/atan/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/atan/cts.https.html.ini @@ -44,7 +44,6 @@ [:test="ptr"] [:test="ptr_deref"] - expected: FAIL [:test="sampler"] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/atan2/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/atan2/cts.https.html.ini @@ -196,7 +196,6 @@ [:test="ptr"] [:test="ptr_deref"] - expected: FAIL [:test="sampler"] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/atanh/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/atanh/cts.https.html.ini @@ -52,7 +52,6 @@ [:test="ptr"] [:test="ptr_deref"] - expected: FAIL [:test="sampler"] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/ceil/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/ceil/cts.https.html.ini @@ -1,5 +1,4 @@ [cts.https.html?q=webgpu:shader,validation,expression,call,builtin,ceil:arguments:*] - implementation-status: backlog [:test="alias"] [:test="array"] @@ -19,7 +18,6 @@ [:test="ptr"] [:test="ptr_deref"] - expected: FAIL [:test="sampler"] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/clamp/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/clamp/cts.https.html.ini @@ -20,7 +20,6 @@ [:type="ptr"] [:type="ptr_deref"] - expected: FAIL [:type="sampler"] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/countLeadingZeros/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/countLeadingZeros/cts.https.html.ini @@ -20,7 +20,6 @@ [:test="ptr"] [:test="ptr_deref"] - expected: FAIL [:test="sampler"] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/countOneBits/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/countOneBits/cts.https.html.ini @@ -20,7 +20,6 @@ [:test="ptr"] [:test="ptr_deref"] - expected: FAIL [:test="sampler"] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/countTrailingZeros/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/countTrailingZeros/cts.https.html.ini @@ -20,7 +20,6 @@ [:test="ptr"] [:test="ptr_deref"] - expected: FAIL [:test="sampler"] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/firstLeadingBit/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/firstLeadingBit/cts.https.html.ini @@ -20,7 +20,6 @@ [:test="ptr"] [:test="ptr_deref"] - expected: FAIL [:test="sampler"] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/firstTrailingBit/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/firstTrailingBit/cts.https.html.ini @@ -20,7 +20,6 @@ [:test="ptr"] [:test="ptr_deref"] - expected: FAIL [:test="sampler"] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/modf/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/modf/cts.https.html.ini @@ -1,5 +1,4 @@ [cts.https.html?q=webgpu:shader,validation,expression,call,builtin,modf:arguments:*] - implementation-status: backlog [:test="alias"] [:test="array"] @@ -19,7 +18,6 @@ [:test="ptr"] [:test="ptr_deref"] - expected: FAIL [:test="sampler"] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/reverseBits/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/reverseBits/cts.https.html.ini @@ -20,7 +20,6 @@ [:test="ptr"] [:test="ptr_deref"] - expected: FAIL [:test="sampler"] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/round/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/round/cts.https.html.ini @@ -1,5 +1,4 @@ [cts.https.html?q=webgpu:shader,validation,expression,call,builtin,round:arguments:*] - implementation-status: backlog [:test="alias"] [:test="array"] @@ -19,7 +18,6 @@ [:test="ptr"] [:test="ptr_deref"] - expected: FAIL [:test="sampler"] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/saturate/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/saturate/cts.https.html.ini @@ -1,5 +1,4 @@ [cts.https.html?q=webgpu:shader,validation,expression,call,builtin,saturate:arguments:*] - implementation-status: backlog [:test="alias"] [:test="array"] @@ -19,7 +18,6 @@ [:test="ptr"] [:test="ptr_deref"] - expected: FAIL [:test="sampler"] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/select/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/select/cts.https.html.ini @@ -1659,7 +1659,6 @@ [:test="ptr"] [:test="ptr_deref"] - expected: FAIL [:test="sampler"] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/smoothstep/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/call/builtin/smoothstep/cts.https.html.ini @@ -110,7 +110,6 @@ [:test="ptr"] [:test="ptr_deref"] - expected: FAIL [:test="sampler"] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/unary/address_of_and_indirection/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/expression/unary/address_of_and_indirection/cts.https.html.ini @@ -1,64 +1,43 @@ [cts.https.html?q=webgpu:shader,validation,expression,unary,address_of_and_indirection:basic:*] - implementation-status: backlog [:addressSpace="function";accessMode="read";storageType="bool";derefType="deref_address_of_identifier"] - expected: FAIL [:addressSpace="function";accessMode="read";storageType="bool";derefType="deref_pointer"] - expected: FAIL [:addressSpace="function";accessMode="read";storageType="f16";derefType="deref_address_of_identifier"] - expected: FAIL [:addressSpace="function";accessMode="read";storageType="f16";derefType="deref_pointer"] - expected: FAIL [:addressSpace="function";accessMode="read";storageType="f32";derefType="deref_address_of_identifier"] - expected: FAIL [:addressSpace="function";accessMode="read";storageType="f32";derefType="deref_pointer"] - expected: FAIL [:addressSpace="function";accessMode="read";storageType="i32";derefType="deref_address_of_identifier"] - expected: FAIL [:addressSpace="function";accessMode="read";storageType="i32";derefType="deref_pointer"] - expected: FAIL [:addressSpace="function";accessMode="read";storageType="u32";derefType="deref_address_of_identifier"] - expected: FAIL [:addressSpace="function";accessMode="read";storageType="u32";derefType="deref_pointer"] - expected: FAIL [:addressSpace="function";accessMode="read_write";storageType="bool";derefType="deref_address_of_identifier"] - expected: FAIL [:addressSpace="function";accessMode="read_write";storageType="bool";derefType="deref_pointer"] - expected: FAIL [:addressSpace="function";accessMode="read_write";storageType="f16";derefType="deref_address_of_identifier"] - expected: FAIL [:addressSpace="function";accessMode="read_write";storageType="f16";derefType="deref_pointer"] - expected: FAIL [:addressSpace="function";accessMode="read_write";storageType="f32";derefType="deref_address_of_identifier"] - expected: FAIL [:addressSpace="function";accessMode="read_write";storageType="f32";derefType="deref_pointer"] - expected: FAIL [:addressSpace="function";accessMode="read_write";storageType="i32";derefType="deref_address_of_identifier"] - expected: FAIL [:addressSpace="function";accessMode="read_write";storageType="i32";derefType="deref_pointer"] - expected: FAIL [:addressSpace="function";accessMode="read_write";storageType="u32";derefType="deref_address_of_identifier"] - expected: FAIL [:addressSpace="function";accessMode="read_write";storageType="u32";derefType="deref_pointer"] - expected: FAIL [:addressSpace="private";accessMode="read";storageType="bool";derefType="deref_address_of_identifier"] @@ -198,57 +177,39 @@ [cts.https.html?q=webgpu:shader,validation,expression,unary,address_of_and_indirection:composite:*] - implementation-status: backlog [:addressSpace="function";compositeType="array";storageType="bool"] - expected: FAIL [:addressSpace="function";compositeType="array";storageType="f16"] - expected: FAIL [:addressSpace="function";compositeType="array";storageType="f32"] - expected: FAIL [:addressSpace="function";compositeType="array";storageType="i32"] - expected: FAIL [:addressSpace="function";compositeType="array";storageType="u32"] - expected: FAIL [:addressSpace="function";compositeType="mat";storageType="f16"] - expected: FAIL [:addressSpace="function";compositeType="mat";storageType="f32"] - expected: FAIL [:addressSpace="function";compositeType="struct";storageType="bool"] - expected: FAIL [:addressSpace="function";compositeType="struct";storageType="f16"] - expected: FAIL [:addressSpace="function";compositeType="struct";storageType="f32"] - expected: FAIL [:addressSpace="function";compositeType="struct";storageType="i32"] - expected: FAIL [:addressSpace="function";compositeType="struct";storageType="u32"] - expected: FAIL [:addressSpace="function";compositeType="vec";storageType="bool"] - expected: FAIL [:addressSpace="function";compositeType="vec";storageType="f16"] - expected: FAIL [:addressSpace="function";compositeType="vec";storageType="f32"] - expected: FAIL [:addressSpace="function";compositeType="vec";storageType="i32"] - expected: FAIL [:addressSpace="function";compositeType="vec";storageType="u32"] - expected: FAIL [:addressSpace="private";compositeType="array";storageType="bool"] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/shader_io/align/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/shader_io/align/cts.https.html.ini @@ -69,9 +69,7 @@ [cts.https.html?q=webgpu:shader,validation,shader_io,align:placement:*] - implementation-status: backlog [:scope="_undef_";attribute={"private-var":false,"storage-var":false,"struct-member":true,"fn-decl":false,"fn-param":false,"fn-var":false,"fn-return":false,"while-stmt":false}] - expected: FAIL [:scope="fn-decl";attribute={"private-var":false,"storage-var":false,"struct-member":true,"fn-decl":false,"fn-param":false,"fn-var":false,"fn-return":false,"while-stmt":false}] @@ -86,7 +84,6 @@ [:scope="storage-var";attribute={"private-var":false,"storage-var":false,"struct-member":true,"fn-decl":false,"fn-param":false,"fn-var":false,"fn-return":false,"while-stmt":false}] [:scope="struct-member";attribute={"private-var":false,"storage-var":false,"struct-member":true,"fn-decl":false,"fn-param":false,"fn-var":false,"fn-return":false,"while-stmt":false}] - expected: FAIL [:scope="while-stmt";attribute={"private-var":false,"storage-var":false,"struct-member":true,"fn-decl":false,"fn-param":false,"fn-var":false,"fn-return":false,"while-stmt":false}] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/shader_io/builtins/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/shader_io/builtins/cts.https.html.ini @@ -85,9 +85,7 @@ [cts.https.html?q=webgpu:shader,validation,shader_io,builtins:placement:*] - implementation-status: backlog [:scope="_undef_";attribute={"private-var":false,"storage-var":false,"struct-member":true,"non-ep-param":false,"non-ep-ret":false,"fn-decl":false,"fn-var":false,"fn-return":false,"while-stmt":false}] - expected: FAIL [:scope="fn-decl";attribute={"private-var":false,"storage-var":false,"struct-member":true,"non-ep-param":false,"non-ep-ret":false,"fn-decl":false,"fn-var":false,"fn-return":false,"while-stmt":false}] @@ -102,7 +100,6 @@ [:scope="storage-var";attribute={"private-var":false,"storage-var":false,"struct-member":true,"non-ep-param":false,"non-ep-ret":false,"fn-decl":false,"fn-var":false,"fn-return":false,"while-stmt":false}] [:scope="struct-member";attribute={"private-var":false,"storage-var":false,"struct-member":true,"non-ep-param":false,"non-ep-ret":false,"fn-decl":false,"fn-var":false,"fn-return":false,"while-stmt":false}] - expected: FAIL [:scope="while-stmt";attribute={"private-var":false,"storage-var":false,"struct-member":true,"non-ep-param":false,"non-ep-ret":false,"fn-decl":false,"fn-var":false,"fn-return":false,"while-stmt":false}] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/shader_io/pipeline_stage/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/shader_io/pipeline_stage/cts.https.html.ini @@ -95,13 +95,10 @@ [cts.https.html?q=webgpu:shader,validation,shader_io,pipeline_stage:placement:*] implementation-status: backlog [:scope="_undef_";attr="%40compute"] - expected: FAIL [:scope="_undef_";attr="%40fragment"] - expected: FAIL [:scope="_undef_";attr="%40vertex"] - expected: FAIL [:scope="fn-param";attr="%40compute"] @@ -122,16 +119,22 @@ [:scope="fn-var";attr="%40vertex"] [:scope="private-var";attr="%40compute"] + expected: FAIL [:scope="private-var";attr="%40fragment"] + expected: FAIL [:scope="private-var";attr="%40vertex"] + expected: FAIL [:scope="storage-var";attr="%40compute"] + expected: FAIL [:scope="storage-var";attr="%40fragment"] + expected: FAIL [:scope="storage-var";attr="%40vertex"] + expected: FAIL [:scope="struct-member";attr="%40compute"] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/statement/for/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/statement/for/cts.https.html.ini @@ -113,10 +113,8 @@ [:test="init_var"] [:test="init_var_function"] - expected: FAIL [:test="init_var_function_type"] - expected: FAIL [:test="init_var_type"] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/types/pointer/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/shader/validation/types/pointer/cts.https.html.ini @@ -99,7 +99,6 @@ [cts.https.html?q=webgpu:shader,validation,types,pointer:let_ptr_explicit_type_matches_var:*] - implementation-status: backlog [:addressSpace="function";explicitSpace=false;explicitAccess=false;accessMode="";stage="compute";ptrStoreType="i32"] [:addressSpace="function";explicitSpace=false;explicitAccess=false;accessMode="";stage="compute";ptrStoreType="u32"] @@ -109,12 +108,10 @@ [:addressSpace="function";explicitSpace=false;explicitAccess=true;accessMode="";stage="compute";ptrStoreType="u32"] [:addressSpace="function";explicitSpace=true;explicitAccess=false;accessMode="";stage="compute";ptrStoreType="i32"] - expected: FAIL [:addressSpace="function";explicitSpace=true;explicitAccess=false;accessMode="";stage="compute";ptrStoreType="u32"] [:addressSpace="function";explicitSpace=true;explicitAccess=true;accessMode="";stage="compute";ptrStoreType="i32"] - expected: FAIL [:addressSpace="function";explicitSpace=true;explicitAccess=true;accessMode="";stage="compute";ptrStoreType="u32"] @@ -156,7 +153,6 @@ [cts.https.html?q=webgpu:shader,validation,types,pointer:let_ptr_reads:*] - implementation-status: backlog [:addressSpace="function";explicitSpace=false;explicitAccess=false;accessMode="";stage="compute";inferPtrType=false;ptrStoreType="i32"] [:addressSpace="function";explicitSpace=false;explicitAccess=false;accessMode="";stage="compute";inferPtrType=true;ptrStoreType="i32"] @@ -166,16 +162,12 @@ [:addressSpace="function";explicitSpace=false;explicitAccess=true;accessMode="";stage="compute";inferPtrType=true;ptrStoreType="i32"] [:addressSpace="function";explicitSpace=true;explicitAccess=false;accessMode="";stage="compute";inferPtrType=false;ptrStoreType="i32"] - expected: FAIL [:addressSpace="function";explicitSpace=true;explicitAccess=false;accessMode="";stage="compute";inferPtrType=true;ptrStoreType="i32"] - expected: FAIL [:addressSpace="function";explicitSpace=true;explicitAccess=true;accessMode="";stage="compute";inferPtrType=false;ptrStoreType="i32"] - expected: FAIL [:addressSpace="function";explicitSpace=true;explicitAccess=true;accessMode="";stage="compute";inferPtrType=true;ptrStoreType="i32"] - expected: FAIL [:addressSpace="private";explicitSpace=true;explicitAccess=false;accessMode="";stage="compute";inferPtrType=false;ptrStoreType="i32"] @@ -215,7 +207,6 @@ [cts.https.html?q=webgpu:shader,validation,types,pointer:let_ptr_writes:*] - implementation-status: backlog [:addressSpace="function";explicitSpace=false;explicitAccess=false;accessMode="";stage="compute";inferPtrType=false;ptrStoreType="i32"] [:addressSpace="function";explicitSpace=false;explicitAccess=false;accessMode="";stage="compute";inferPtrType=true;ptrStoreType="i32"] @@ -225,16 +216,12 @@ [:addressSpace="function";explicitSpace=false;explicitAccess=true;accessMode="";stage="compute";inferPtrType=true;ptrStoreType="i32"] [:addressSpace="function";explicitSpace=true;explicitAccess=false;accessMode="";stage="compute";inferPtrType=false;ptrStoreType="i32"] - expected: FAIL [:addressSpace="function";explicitSpace=true;explicitAccess=false;accessMode="";stage="compute";inferPtrType=true;ptrStoreType="i32"] - expected: FAIL [:addressSpace="function";explicitSpace=true;explicitAccess=true;accessMode="";stage="compute";inferPtrType=false;ptrStoreType="i32"] - expected: FAIL [:addressSpace="function";explicitSpace=true;explicitAccess=true;accessMode="";stage="compute";inferPtrType=true;ptrStoreType="i32"] - expected: FAIL [:addressSpace="private";explicitSpace=true;explicitAccess=false;accessMode="";stage="compute";inferPtrType=false;ptrStoreType="i32"] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/web_platform/copyToTexture/ImageBitmap/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/web_platform/copyToTexture/ImageBitmap/cts.https.html.ini @@ -4101,7 +4101,7 @@ [:orientation="none";colorSpaceConversion="none";srcFlipYInCopy=false;dstFormat="rg8unorm";dstPremultiplied=false] expected: if os == "linux" and debug: [PASS, TIMEOUT, NOTRUN] - if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and debug: [PASS, TIMEOUT, NOTRUN] [:orientation="none";colorSpaceConversion="none";srcFlipYInCopy=false;dstFormat="rg8unorm";dstPremultiplied=true] expected: @@ -4158,9 +4158,7 @@ [:orientation="none";colorSpaceConversion="none";srcFlipYInCopy=false;dstFormat="rgba8unorm";dstPremultiplied=true] expected: - if os == "win" and debug: [PASS, TIMEOUT, NOTRUN] - if os == "linux" and debug: [PASS, TIMEOUT, NOTRUN] - if os == "mac" and debug: [TIMEOUT, NOTRUN] + if debug: [PASS, TIMEOUT, NOTRUN] [:orientation="none";colorSpaceConversion="none";srcFlipYInCopy=false;dstFormat="rgba8unorm-srgb";dstPremultiplied=false] expected: diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/web_platform/copyToTexture/ImageBitmap/dedicated.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/web_platform/copyToTexture/ImageBitmap/dedicated.https.html.ini @@ -1459,7 +1459,8 @@ [:alpha="premultiply";orientation="flipY";colorSpaceConversion="default";srcFlipYInCopy=false;dstFormat="rgba8unorm-srgb";dstPremultiplied=true] expected: if os == "win" and debug: [TIMEOUT, NOTRUN] - if os == "linux": [TIMEOUT, NOTRUN] + if os == "linux" and debug: [TIMEOUT, NOTRUN] + if os == "linux" and not debug: [PASS, TIMEOUT, NOTRUN] if os == "mac": [TIMEOUT, NOTRUN] [:alpha="premultiply";orientation="flipY";colorSpaceConversion="default";srcFlipYInCopy=true;dstFormat="bgra8unorm";dstPremultiplied=false] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/web_platform/copyToTexture/ImageBitmap/shared.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/web_platform/copyToTexture/ImageBitmap/shared.https.html.ini @@ -103,7 +103,8 @@ expected: if os == "win" and debug: TIMEOUT if os == "linux": TIMEOUT - if os == "mac": TIMEOUT + if os == "mac" and debug: [TIMEOUT, CRASH] + if os == "mac" and not debug: TIMEOUT [:alpha="none";orientation="flipY";colorSpaceConversion="default";srcFlipYInCopy=false;dstFormat="bgra8unorm";dstPremultiplied=false] expected: if debug: [TIMEOUT, NOTRUN] diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/web_platform/copyToTexture/canvas/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/web_platform/copyToTexture/canvas/cts.https.html.ini @@ -2229,6 +2229,7 @@ expected: if os == "linux": [TIMEOUT, NOTRUN] if os == "mac" and debug: [TIMEOUT, NOTRUN] + if os == "mac" and not debug: [PASS, TIMEOUT, NOTRUN] [:canvasType="offscreen";contextName="webgl2";dstColorFormat="rg16float";srcPremultiplied=false;dstAlphaMode="opaque";srcDoFlipYDuringCopy=true] expected: diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/web_platform/worker/worker/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/web_platform/worker/worker/cts.https.html.ini @@ -4,7 +4,6 @@ [cts.https.html?q=webgpu:web_platform,worker,worker:service_worker:*] disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1942431 - [:] [cts.https.html?q=webgpu:web_platform,worker,worker:shared_worker:*] diff --git a/third_party/rust/gpu-alloc-types/.cargo-checksum.json b/third_party/rust/gpu-alloc-types/.cargo-checksum.json @@ -1 +0,0 @@ -{"files":{"Cargo.toml":"ce92a2d32d4b2e50d617595e689b6bca736b7792280480c4057eaebef15bf325","README.md":"5a96b135d18e172f090bd6147830ab3a1b5cefac5f02be28f06cf88eea61b64f","src/device.rs":"8173609d5fb700f8b82d6335729c592ff62c306b12894a9bab9da45a47810e4a","src/lib.rs":"0f77c21a5770b54affa146e5f4c15abcb83599de551226d336ee48ec5185f866","src/types.rs":"aa861f891a63c232441c5a9fe5fed61cac2d62780108c01ebe6cb64a8547b4e2"},"package":"98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4"} -\ No newline at end of file diff --git a/third_party/rust/gpu-alloc-types/Cargo.toml b/third_party/rust/gpu-alloc-types/Cargo.toml @@ -1,38 +0,0 @@ -# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO -# -# When uploading crates to the registry Cargo will automatically -# "normalize" Cargo.toml files for maximal compatibility -# with all versions of Cargo and also rewrite `path` dependencies -# to registry (e.g., crates.io) dependencies. -# -# If you are reading this file be aware that the original Cargo.toml -# will likely look very different (and much more reasonable). -# See Cargo.toml.orig for the original contents. - -[package] -edition = "2018" -name = "gpu-alloc-types" -version = "0.3.0" -authors = ["Zakarum <zakarumych@ya.ru>"] -description = "Core types of gpu-alloc crate" -homepage = "https://github.com/zakarumych/gpu-alloc" -documentation = "https://docs.rs/gpu-alloc-types" -readme = "README.md" -keywords = [ - "gpu", - "vulkan", - "allocation", - "no-std", -] -categories = [ - "graphics", - "memory-management", - "no-std", - "game-development", -] -license = "MIT OR Apache-2.0" -repository = "https://github.com/zakarumych/gpu-alloc" - -[dependencies.bitflags] -version = "2.0" -default-features = false diff --git a/third_party/rust/gpu-alloc-types/README.md b/third_party/rust/gpu-alloc-types/README.md @@ -1,51 +0,0 @@ -# gpu-alloc - -[](https://crates.io/crates/gpu-alloc) -[](https://docs.rs/gpu-alloc) -[](https://github.com/zakarumych/gpu-alloc/actions?query=workflow%3ARust) -[](COPYING) - - - -Implementation agnostic memory allocator for Vulkan like APIs. - -This crate is intended to be used as part of safe API implementations.\ -Use with caution. There are unsafe functions all over the place. - -## Usage - -Start with fetching `DeviceProperties` from `gpu-alloc-<backend>` crate for the backend of choice.\ -Then create `GpuAllocator` instance and use it for all device memory allocations.\ -`GpuAllocator` will take care for all necessary bookkeeping like memory object count limit, -heap budget and memory mapping. - -#### Backends implementations - -Backend supporting crates should not depend on this crate.\ -Instead they should depend on `gpu-alloc-types` which is much more stable, -allowing to upgrade `gpu-alloc` version without `gpu-alloc-<backend>` upgrade. - - -Supported Rust Versions - -The minimum supported version is 1.40. -The current version is not guaranteed to build on Rust versions earlier than the minimum supported version. - -`gpu-alloc-erupt` crate requires version 1.48 or higher due to dependency on `erupt` crate. - -## License - -Licensed under either of - -* Apache License, Version 2.0, ([license/APACHE](license/APACHE) or http://www.apache.org/licenses/LICENSE-2.0) -* MIT license ([license/MIT](license/MIT) or http://opensource.org/licenses/MIT) - -at your option. - -## Contributions - -Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. - -## Donate - -[](https://www.patreon.com/zakarum) diff --git a/third_party/rust/gpu-alloc-types/src/device.rs b/third_party/rust/gpu-alloc-types/src/device.rs @@ -1,158 +0,0 @@ -use { - crate::types::{MemoryHeap, MemoryType}, - alloc::borrow::Cow, - core::ptr::NonNull, -}; - -/// Memory exhausted error. -#[derive(Debug)] -pub enum OutOfMemory { - /// Device memory exhausted. - OutOfDeviceMemory, - - /// Host memory exhausted. - OutOfHostMemory, -} - -/// Memory mapped error. -#[derive(Debug)] -pub enum DeviceMapError { - /// Device memory exhausted. - OutOfDeviceMemory, - - /// Host memory exhausted. - OutOfHostMemory, - - /// Map failed due to implementation specific error. - MapFailed, -} - -/// Specifies range of the mapped memory region. -#[derive(Debug)] -pub struct MappedMemoryRange<'a, M> { - /// Memory object reference. - pub memory: &'a M, - - /// Offset in bytes from start of the memory object. - pub offset: u64, - - /// Size in bytes of the memory range. - pub size: u64, -} - -/// Properties of the device that will be used for allocating memory objects. -/// -/// See `gpu-alloc-<backend>` crate to learn how to obtain one for backend of choice. -#[derive(Debug)] -pub struct DeviceProperties<'a> { - /// Array of memory types provided by the device. - pub memory_types: Cow<'a, [MemoryType]>, - - /// Array of memory heaps provided by the device. - pub memory_heaps: Cow<'a, [MemoryHeap]>, - - /// Maximum number of valid memory allocations that can exist simultaneously within the device. - pub max_memory_allocation_count: u32, - - /// Maximum size for single allocation supported by the device. - pub max_memory_allocation_size: u64, - - /// Atom size for host mappable non-coherent memory. - pub non_coherent_atom_size: u64, - - /// Specifies if feature required to fetch device address is enabled. - pub buffer_device_address: bool, -} - -bitflags::bitflags! { - /// Allocation flags - #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] - #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] - pub struct AllocationFlags : u8 { - /// Specifies that the memory can be used for buffers created - /// with flag that allows fetching device address. - const DEVICE_ADDRESS = 0x1; - } -} - -/// Abstract device that can be used to allocate memory objects. -pub trait MemoryDevice<M> { - /// Allocates new memory object from device. - /// This function may be expensive and even limit maximum number of memory - /// objects allocated. - /// Which is the reason for sub-allocation this crate provides. - /// - /// # Safety - /// - /// `memory_type` must be valid index for memory type associated with this device. - /// Retrieving this information is implementation specific. - /// - /// `flags` must be supported by the device. - unsafe fn allocate_memory( - &self, - size: u64, - memory_type: u32, - flags: AllocationFlags, - ) -> Result<M, OutOfMemory>; - - /// Deallocate memory object. - /// - /// # Safety - /// - /// Memory object must have been allocated from this device.\ - /// All clones of specified memory handle must be dropped before calling this function. - unsafe fn deallocate_memory(&self, memory: M); - - /// Map region of device memory to host memory space. - /// - /// # Safety - /// - /// * Memory object must have been allocated from this device. - /// * Memory object must not be already mapped. - /// * Memory must be allocated from type with `HOST_VISIBLE` property. - /// * `offset + size` must not overflow. - /// * `offset + size` must not be larger than memory object size specified when - /// memory object was allocated from this device. - unsafe fn map_memory( - &self, - memory: &mut M, - offset: u64, - size: u64, - ) -> Result<NonNull<u8>, DeviceMapError>; - - /// Unmap previously mapped memory region. - /// - /// # Safety - /// - /// * Memory object must have been allocated from this device. - /// * Memory object must be mapped - unsafe fn unmap_memory(&self, memory: &mut M); - - /// Invalidates ranges of memory mapped regions. - /// - /// # Safety - /// - /// * Memory objects must have been allocated from this device. - /// * `offset` and `size` in each element of `ranges` must specify - /// subregion of currently mapped memory region - /// * if `memory` in some element of `ranges` does not contain `HOST_COHERENT` property - /// then `offset` and `size` of that element must be multiple of `non_coherent_atom_size`. - unsafe fn invalidate_memory_ranges( - &self, - ranges: &[MappedMemoryRange<'_, M>], - ) -> Result<(), OutOfMemory>; - - /// Flushes ranges of memory mapped regions. - /// - /// # Safety - /// - /// * Memory objects must have been allocated from this device. - /// * `offset` and `size` in each element of `ranges` must specify - /// subregion of currently mapped memory region - /// * if `memory` in some element of `ranges` does not contain `HOST_COHERENT` property - /// then `offset` and `size` of that element must be multiple of `non_coherent_atom_size`. - unsafe fn flush_memory_ranges( - &self, - ranges: &[MappedMemoryRange<'_, M>], - ) -> Result<(), OutOfMemory>; -} diff --git a/third_party/rust/gpu-alloc-types/src/lib.rs b/third_party/rust/gpu-alloc-types/src/lib.rs @@ -1,8 +0,0 @@ -#![no_std] - -extern crate alloc; - -mod device; -mod types; - -pub use self::{device::*, types::*}; diff --git a/third_party/rust/gpu-alloc-types/src/types.rs b/third_party/rust/gpu-alloc-types/src/types.rs @@ -1,55 +0,0 @@ -bitflags::bitflags! { - /// Memory properties type. - #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] - #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] - pub struct MemoryPropertyFlags: u8 { - /// This flag is set for device-local memory types. - /// Device-local memory is situated "close" to the GPU cores - /// and allows for fast access. - const DEVICE_LOCAL = 0x01; - - /// This flag is set for host-visible memory types. - /// Host-visible memory can be mapped to the host memory range. - const HOST_VISIBLE = 0x02; - - /// This flag is set for host-coherent memory types. - /// Host-coherent memory does not requires manual invalidation for - /// modifications on GPU to become visible on host; - /// nor flush for modification on host to become visible on GPU. - /// Access synchronization is still required. - const HOST_COHERENT = 0x04; - - /// This flag is set for host-cached memory types. - /// Host-cached memory uses cache in host memory for faster reads from host. - const HOST_CACHED = 0x08; - - /// This flag is set for lazily-allocated memory types. - /// Lazily-allocated memory must be used (and only) for transient image attachments. - const LAZILY_ALLOCATED = 0x10; - - /// This flag is set for protected memory types. - /// Protected memory can be used for writing by protected operations - /// and can be read only by protected operations. - /// Protected memory cannot be host-visible. - /// Implementation must guarantee that there is no way for data to flow - /// from protected to unprotected memory. - const PROTECTED = 0x20; - } -} - -/// Defines memory type. -#[derive(Clone, Copy, Debug)] -pub struct MemoryType { - /// Heap index of the memory type. - pub heap: u32, - - /// Property flags of the memory type. - pub props: MemoryPropertyFlags, -} - -/// Defines memory heap. -#[derive(Clone, Copy, Debug)] -pub struct MemoryHeap { - /// Size of memory heap in bytes. - pub size: u64, -} diff --git a/third_party/rust/gpu-alloc/.cargo-checksum.json b/third_party/rust/gpu-alloc/.cargo-checksum.json @@ -1 +0,0 @@ -{"files":{"Cargo.toml":"3647062a9589eedcc361f51f1e1f0852ae6be3c028d0dc8f8d3c0c944bbca61e","README.md":"5a96b135d18e172f090bd6147830ab3a1b5cefac5f02be28f06cf88eea61b64f","src/allocator.rs":"28f20bbfc3866b4eb94a025027925268178921393f5ab8eb5febad1acf94dce8","src/block.rs":"3553343eb171e7ef8b771a1582489cbbbe22b3a1aca4d54b9ff0174fd30bf0da","src/buddy.rs":"253df5f689b643cf97cfd27be6512ea84b84d3c2097a35f3f2d73f537ab353b3","src/config.rs":"1851264db7576f92f3b455e4667339e1c48b7f1b88f8fae7be95c6a9badde455","src/error.rs":"50e30bccc4ba3b23d99298a65155eec45f7d91b66773e828460907ebdcb8ee41","src/freelist.rs":"92c9241d899f3e92a70b0eb5af0b557a3a76aa14c4a6d23876c3ed7857b5f15b","src/heap.rs":"347c25a8560d39d1abb807fb5c34a6a51d4786c0be24a92293f9c8217bace340","src/lib.rs":"07077fb465fb471b39db99bb0311082133d1c2044c2e192d20f7bbb9e743dd0b","src/slab.rs":"6a2b818087850bd5615b085d912490dbd46bbca0df28daa86932505b3983c64a","src/usage.rs":"99009f2ff532904de3ef66520336cd25bd1b795afcb0158385e78a5908f8308f","src/util.rs":"ce3fd7a278eb4d4bd030d4db5495313f56dc91b0765c394f88d126f11c2b4e75"},"package":"fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171"} -\ No newline at end of file diff --git a/third_party/rust/gpu-alloc/Cargo.toml b/third_party/rust/gpu-alloc/Cargo.toml @@ -1,61 +0,0 @@ -# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO -# -# When uploading crates to the registry Cargo will automatically -# "normalize" Cargo.toml files for maximal compatibility -# with all versions of Cargo and also rewrite `path` dependencies -# to registry (e.g., crates.io) dependencies. -# -# If you are reading this file be aware that the original Cargo.toml -# will likely look very different (and much more reasonable). -# See Cargo.toml.orig for the original contents. - -[package] -edition = "2018" -name = "gpu-alloc" -version = "0.6.0" -authors = ["Zakarum <zakarumych@ya.ru>"] -description = "Implementation agnostic memory allocator for Vulkan like APIs" -homepage = "https://github.com/zakarumych/gpu-alloc" -documentation = "https://docs.rs/gpu-alloc-types" -readme = "README.md" -keywords = [ - "gpu", - "vulkan", - "allocation", - "no-std", -] -categories = [ - "graphics", - "memory-management", - "no-std", - "game-development", -] -license = "MIT OR Apache-2.0" -repository = "https://github.com/zakarumych/gpu-alloc" - -[dependencies.bitflags] -version = "2.0" -default-features = false - -[dependencies.gpu-alloc-types] -version = "=0.3.0" - -[dependencies.serde] -version = "1.0" -features = ["derive"] -optional = true -default-features = false - -[dependencies.tracing] -version = "0.1.27" -features = ["attributes"] -optional = true -default-features = false - -[features] -default = ["std"] -serde = [ - "dep:serde", - "bitflags/serde", -] -std = [] diff --git a/third_party/rust/gpu-alloc/README.md b/third_party/rust/gpu-alloc/README.md @@ -1,51 +0,0 @@ -# gpu-alloc - -[](https://crates.io/crates/gpu-alloc) -[](https://docs.rs/gpu-alloc) -[](https://github.com/zakarumych/gpu-alloc/actions?query=workflow%3ARust) -[](COPYING) - - - -Implementation agnostic memory allocator for Vulkan like APIs. - -This crate is intended to be used as part of safe API implementations.\ -Use with caution. There are unsafe functions all over the place. - -## Usage - -Start with fetching `DeviceProperties` from `gpu-alloc-<backend>` crate for the backend of choice.\ -Then create `GpuAllocator` instance and use it for all device memory allocations.\ -`GpuAllocator` will take care for all necessary bookkeeping like memory object count limit, -heap budget and memory mapping. - -#### Backends implementations - -Backend supporting crates should not depend on this crate.\ -Instead they should depend on `gpu-alloc-types` which is much more stable, -allowing to upgrade `gpu-alloc` version without `gpu-alloc-<backend>` upgrade. - - -Supported Rust Versions - -The minimum supported version is 1.40. -The current version is not guaranteed to build on Rust versions earlier than the minimum supported version. - -`gpu-alloc-erupt` crate requires version 1.48 or higher due to dependency on `erupt` crate. - -## License - -Licensed under either of - -* Apache License, Version 2.0, ([license/APACHE](license/APACHE) or http://www.apache.org/licenses/LICENSE-2.0) -* MIT license ([license/MIT](license/MIT) or http://opensource.org/licenses/MIT) - -at your option. - -## Contributions - -Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. - -## Donate - -[](https://www.patreon.com/zakarum) diff --git a/third_party/rust/gpu-alloc/src/allocator.rs b/third_party/rust/gpu-alloc/src/allocator.rs @@ -1,611 +0,0 @@ -use { - crate::{ - align_down, - block::{MemoryBlock, MemoryBlockFlavor}, - buddy::{BuddyAllocator, BuddyBlock}, - config::Config, - error::AllocationError, - freelist::{FreeListAllocator, FreeListBlock}, - heap::Heap, - usage::{MemoryForUsage, UsageFlags}, - MemoryBounds, Request, - }, - alloc::boxed::Box, - core::convert::TryFrom as _, - gpu_alloc_types::{ - AllocationFlags, DeviceProperties, MemoryDevice, MemoryPropertyFlags, MemoryType, - OutOfMemory, - }, -}; - -/// Memory allocator for Vulkan-like APIs. -#[derive(Debug)] -pub struct GpuAllocator<M> { - dedicated_threshold: u64, - preferred_dedicated_threshold: u64, - transient_dedicated_threshold: u64, - max_memory_allocation_size: u64, - memory_for_usage: MemoryForUsage, - memory_types: Box<[MemoryType]>, - memory_heaps: Box<[Heap]>, - allocations_remains: u32, - non_coherent_atom_mask: u64, - starting_free_list_chunk: u64, - final_free_list_chunk: u64, - minimal_buddy_size: u64, - initial_buddy_dedicated_size: u64, - buffer_device_address: bool, - - buddy_allocators: Box<[Option<BuddyAllocator<M>>]>, - freelist_allocators: Box<[Option<FreeListAllocator<M>>]>, -} - -/// Hints for allocator to decide on allocation strategy. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -#[non_exhaustive] -pub enum Dedicated { - /// Allocation directly from device.\ - /// Very slow. - /// Count of allocations is limited.\ - /// Use with caution.\ - /// Must be used if resource has to be bound to dedicated memory object. - Required, - - /// Hint for allocator that dedicated memory object is preferred.\ - /// Should be used if it is known that resource placed in dedicated memory object - /// would allow for better performance.\ - /// Implementation is allowed to return block to shared memory object. - Preferred, -} - -impl<M> GpuAllocator<M> -where - M: MemoryBounds + 'static, -{ - /// Creates new instance of `GpuAllocator`. - /// Provided `DeviceProperties` should match properties of `MemoryDevice` that will be used - /// with created `GpuAllocator` instance. - #[cfg_attr(feature = "tracing", tracing::instrument)] - pub fn new(config: Config, props: DeviceProperties<'_>) -> Self { - assert!( - props.non_coherent_atom_size.is_power_of_two(), - "`non_coherent_atom_size` must be power of two" - ); - - assert!( - isize::try_from(props.non_coherent_atom_size).is_ok(), - "`non_coherent_atom_size` must fit host address space" - ); - - GpuAllocator { - dedicated_threshold: config.dedicated_threshold, - preferred_dedicated_threshold: config - .preferred_dedicated_threshold - .min(config.dedicated_threshold), - - transient_dedicated_threshold: config - .transient_dedicated_threshold - .max(config.dedicated_threshold), - - max_memory_allocation_size: props.max_memory_allocation_size, - - memory_for_usage: MemoryForUsage::new(props.memory_types.as_ref()), - - memory_types: props.memory_types.as_ref().iter().copied().collect(), - memory_heaps: props - .memory_heaps - .as_ref() - .iter() - .map(|heap| Heap::new(heap.size)) - .collect(), - - buffer_device_address: props.buffer_device_address, - - allocations_remains: props.max_memory_allocation_count, - non_coherent_atom_mask: props.non_coherent_atom_size - 1, - - starting_free_list_chunk: config.starting_free_list_chunk, - final_free_list_chunk: config.final_free_list_chunk, - minimal_buddy_size: config.minimal_buddy_size, - initial_buddy_dedicated_size: config.initial_buddy_dedicated_size, - - buddy_allocators: props.memory_types.as_ref().iter().map(|_| None).collect(), - freelist_allocators: props.memory_types.as_ref().iter().map(|_| None).collect(), - } - } - - /// Allocates memory block from specified `device` according to the `request`. - /// - /// # Safety - /// - /// * `device` must be one with `DeviceProperties` that were provided to create this `GpuAllocator` instance. - /// * Same `device` instance must be used for all interactions with one `GpuAllocator` instance - /// and memory blocks allocated from it. - #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, device)))] - pub unsafe fn alloc( - &mut self, - device: &impl MemoryDevice<M>, - request: Request, - ) -> Result<MemoryBlock<M>, AllocationError> { - self.alloc_internal(device, request, None) - } - - /// Allocates memory block from specified `device` according to the `request`. - /// This function allows user to force specific allocation strategy. - /// Improper use can lead to suboptimal performance or too large overhead. - /// Prefer `GpuAllocator::alloc` if doubt. - /// - /// # Safety - /// - /// * `device` must be one with `DeviceProperties` that were provided to create this `GpuAllocator` instance. - /// * Same `device` instance must be used for all interactions with one `GpuAllocator` instance - /// and memory blocks allocated from it. - #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, device)))] - pub unsafe fn alloc_with_dedicated( - &mut self, - device: &impl MemoryDevice<M>, - request: Request, - dedicated: Dedicated, - ) -> Result<MemoryBlock<M>, AllocationError> { - self.alloc_internal(device, request, Some(dedicated)) - } - - unsafe fn alloc_internal( - &mut self, - device: &impl MemoryDevice<M>, - mut request: Request, - dedicated: Option<Dedicated>, - ) -> Result<MemoryBlock<M>, AllocationError> { - enum Strategy { - Buddy, - Dedicated, - FreeList, - } - - request.usage = with_implicit_usage_flags(request.usage); - - if request.usage.contains(UsageFlags::DEVICE_ADDRESS) { - assert!(self.buffer_device_address, "`DEVICE_ADDRESS` cannot be requested when `DeviceProperties::buffer_device_address` is false"); - } - - if request.size > self.max_memory_allocation_size { - return Err(AllocationError::OutOfDeviceMemory); - } - - if let Some(Dedicated::Required) = dedicated { - if self.allocations_remains == 0 { - return Err(AllocationError::TooManyObjects); - } - } - - if 0 == self.memory_for_usage.mask(request.usage) & request.memory_types { - #[cfg(feature = "tracing")] - tracing::error!( - "Cannot serve request {:?}, no memory among bitset `{}` support usage {:?}", - request, - request.memory_types, - request.usage - ); - - return Err(AllocationError::NoCompatibleMemoryTypes); - } - - let transient = request.usage.contains(UsageFlags::TRANSIENT); - - for &index in self.memory_for_usage.types(request.usage) { - if 0 == request.memory_types & (1 << index) { - // Skip memory type incompatible with the request. - continue; - } - - let memory_type = &self.memory_types[index as usize]; - let heap = memory_type.heap; - let heap = &mut self.memory_heaps[heap as usize]; - - if request.size > heap.size() { - // Impossible to use memory type from this heap. - continue; - } - - let atom_mask = if host_visible_non_coherent(memory_type.props) { - self.non_coherent_atom_mask - } else { - 0 - }; - - let flags = if self.buffer_device_address { - AllocationFlags::DEVICE_ADDRESS - } else { - AllocationFlags::empty() - }; - - let strategy = match (dedicated, transient) { - (Some(Dedicated::Required), _) => Strategy::Dedicated, - (Some(Dedicated::Preferred), _) - if request.size >= self.preferred_dedicated_threshold => - { - Strategy::Dedicated - } - (_, true) => { - let threshold = self.transient_dedicated_threshold.min(heap.size() / 32); - - if request.size < threshold { - Strategy::FreeList - } else { - Strategy::Dedicated - } - } - (_, false) => { - let threshold = self.dedicated_threshold.min(heap.size() / 32); - - if request.size < threshold { - Strategy::Buddy - } else { - Strategy::Dedicated - } - } - }; - - match strategy { - Strategy::Dedicated => { - #[cfg(feature = "tracing")] - tracing::debug!( - "Allocating memory object `{}@{:?}`", - request.size, - memory_type - ); - - match device.allocate_memory(request.size, index, flags) { - Ok(memory) => { - self.allocations_remains -= 1; - heap.alloc(request.size); - - return Ok(MemoryBlock::new( - index, - memory_type.props, - 0, - request.size, - atom_mask, - MemoryBlockFlavor::Dedicated { memory }, - )); - } - Err(OutOfMemory::OutOfDeviceMemory) => continue, - Err(OutOfMemory::OutOfHostMemory) => { - return Err(AllocationError::OutOfHostMemory) - } - } - } - Strategy::FreeList => { - let allocator = match &mut self.freelist_allocators[index as usize] { - Some(allocator) => allocator, - slot => { - let starting_free_list_chunk = match align_down( - self.starting_free_list_chunk.min(heap.size() / 32), - atom_mask, - ) { - 0 => atom_mask, - other => other, - }; - - let final_free_list_chunk = match align_down( - self.final_free_list_chunk - .max(self.starting_free_list_chunk) - .max(self.transient_dedicated_threshold) - .min(heap.size() / 32), - atom_mask, - ) { - 0 => atom_mask, - other => other, - }; - - slot.get_or_insert(FreeListAllocator::new( - starting_free_list_chunk, - final_free_list_chunk, - index, - memory_type.props, - if host_visible_non_coherent(memory_type.props) { - self.non_coherent_atom_mask - } else { - 0 - }, - )) - } - }; - let result = allocator.alloc( - device, - request.size, - request.align_mask, - flags, - heap, - &mut self.allocations_remains, - ); - - match result { - Ok(block) => { - return Ok(MemoryBlock::new( - index, - memory_type.props, - block.offset, - block.size, - atom_mask, - MemoryBlockFlavor::FreeList { - chunk: block.chunk, - ptr: block.ptr, - memory: block.memory, - }, - )) - } - Err(AllocationError::OutOfDeviceMemory) => continue, - Err(err) => return Err(err), - } - } - - Strategy::Buddy => { - let allocator = match &mut self.buddy_allocators[index as usize] { - Some(allocator) => allocator, - slot => { - let minimal_buddy_size = self - .minimal_buddy_size - .min(heap.size() / 1024) - .next_power_of_two(); - - let initial_buddy_dedicated_size = self - .initial_buddy_dedicated_size - .min(heap.size() / 32) - .next_power_of_two(); - - slot.get_or_insert(BuddyAllocator::new( - minimal_buddy_size, - initial_buddy_dedicated_size, - index, - memory_type.props, - if host_visible_non_coherent(memory_type.props) { - self.non_coherent_atom_mask - } else { - 0 - }, - )) - } - }; - let result = allocator.alloc( - device, - request.size, - request.align_mask, - flags, - heap, - &mut self.allocations_remains, - ); - - match result { - Ok(block) => { - return Ok(MemoryBlock::new( - index, - memory_type.props, - block.offset, - block.size, - atom_mask, - MemoryBlockFlavor::Buddy { - chunk: block.chunk, - ptr: block.ptr, - index: block.index, - memory: block.memory, - }, - )) - } - Err(AllocationError::OutOfDeviceMemory) => continue, - Err(err) => return Err(err), - } - } - } - } - - Err(AllocationError::OutOfDeviceMemory) - } - - /// Creates a memory block from an existing memory allocation, transferring ownership to the allocator. - /// - /// This function allows the [`GpuAllocator`] to manage memory allocated outside of the typical - /// [`GpuAllocator::alloc`] family of functions. - /// - /// # Usage - /// - /// If you need to import external memory, such as a Win32 `HANDLE` or a Linux `dmabuf`, import the device - /// memory using the graphics api and platform dependent functions. Once that is done, call this function - /// to make the [`GpuAllocator`] take ownership of the imported memory. - /// - /// When calling this function, you **must** ensure there are [enough remaining allocations](GpuAllocator::remaining_allocations). - /// - /// # Safety - /// - /// - The `memory` must be allocated with the same device that was provided to create this [`GpuAllocator`] - /// instance. - /// - The `memory` must be valid. - /// - The `props`, `offset` and `size` must match the properties, offset and size of the memory allocation. - /// - The memory must have been allocated with the specified `memory_type`. - /// - There must be enough remaining allocations. - /// - The memory allocation must not come from an existing memory block created by this allocator. - /// - The underlying memory object must be deallocated using the returned [`MemoryBlock`] with - /// [`GpuAllocator::dealloc`]. - pub unsafe fn import_memory( - &mut self, - memory: M, - memory_type: u32, - props: MemoryPropertyFlags, - offset: u64, - size: u64, - ) -> MemoryBlock<M> { - // Get the heap which the imported memory is from. - let heap = self - .memory_types - .get(memory_type as usize) - .expect("Invalid memory type specified when importing memory") - .heap; - let heap = &mut self.memory_heaps[heap as usize]; - - #[cfg(feature = "tracing")] - tracing::debug!( - "Importing memory object {:?} `{}@{:?}`", - memory, - size, - memory_type - ); - - assert_ne!( - self.allocations_remains, 0, - "Out of allocations when importing a memory block. Ensure you check GpuAllocator::remaining_allocations before import." - ); - self.allocations_remains -= 1; - - let atom_mask = if host_visible_non_coherent(props) { - self.non_coherent_atom_mask - } else { - 0 - }; - - heap.alloc(size); - - MemoryBlock::new( - memory_type, - props, - offset, - size, - atom_mask, - MemoryBlockFlavor::Dedicated { memory }, - ) - } - - /// Deallocates memory block previously allocated from this `GpuAllocator` instance. - /// - /// # Safety - /// - /// * Memory block must have been allocated by this `GpuAllocator` instance - /// * `device` must be one with `DeviceProperties` that were provided to create this `GpuAllocator` instance - /// * Same `device` instance must be used for all interactions with one `GpuAllocator` instance - /// and memory blocks allocated from it - #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, device)))] - pub unsafe fn dealloc(&mut self, device: &impl MemoryDevice<M>, block: MemoryBlock<M>) { - let memory_type = block.memory_type(); - let offset = block.offset(); - let size = block.size(); - let flavor = block.deallocate(); - match flavor { - MemoryBlockFlavor::Dedicated { memory } => { - let heap = self.memory_types[memory_type as usize].heap; - device.deallocate_memory(memory); - self.allocations_remains += 1; - self.memory_heaps[heap as usize].dealloc(size); - } - MemoryBlockFlavor::Buddy { - chunk, - ptr, - index, - memory, - } => { - let heap = self.memory_types[memory_type as usize].heap; - let heap = &mut self.memory_heaps[heap as usize]; - - let allocator = self.buddy_allocators[memory_type as usize] - .as_mut() - .expect("Allocator should exist"); - - allocator.dealloc( - device, - BuddyBlock { - memory, - ptr, - offset, - size, - chunk, - index, - }, - heap, - &mut self.allocations_remains, - ); - } - MemoryBlockFlavor::FreeList { chunk, ptr, memory } => { - let heap = self.memory_types[memory_type as usize].heap; - let heap = &mut self.memory_heaps[heap as usize]; - - let allocator = self.freelist_allocators[memory_type as usize] - .as_mut() - .expect("Allocator should exist"); - - allocator.dealloc( - device, - FreeListBlock { - memory, - ptr, - chunk, - offset, - size, - }, - heap, - &mut self.allocations_remains, - ); - } - } - } - - /// Returns the maximum allocation size supported. - pub fn max_allocation_size(&self) -> u64 { - self.max_memory_allocation_size - } - - /// Returns the number of remaining available allocations. - /// - /// This may be useful if you need know if the allocator can allocate a number of allocations ahead of - /// time. This function is also useful for ensuring you do not allocate too much memory outside allocator - /// (such as external memory). - pub fn remaining_allocations(&self) -> u32 { - self.allocations_remains - } - - /// Sets the number of remaining available allocations. - /// - /// # Safety - /// - /// The caller is responsible for ensuring the number of remaining allocations does not exceed how many - /// remaining allocations there actually are on the memory device. - pub unsafe fn set_remaining_allocations(&mut self, remaining: u32) { - self.allocations_remains = remaining; - } - - /// Deallocates leftover memory objects. - /// Should be used before dropping. - /// - /// # Safety - /// - /// * `device` must be one with `DeviceProperties` that were provided to create this `GpuAllocator` instance - /// * Same `device` instance must be used for all interactions with one `GpuAllocator` instance - /// and memory blocks allocated from it - #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, device)))] - pub unsafe fn cleanup(&mut self, device: &impl MemoryDevice<M>) { - for (index, allocator) in self - .freelist_allocators - .iter_mut() - .enumerate() - .filter_map(|(index, allocator)| Some((index, allocator.as_mut()?))) - { - let memory_type = &self.memory_types[index]; - let heap = memory_type.heap; - let heap = &mut self.memory_heaps[heap as usize]; - - allocator.cleanup(device, heap, &mut self.allocations_remains); - } - } -} - -fn host_visible_non_coherent(props: MemoryPropertyFlags) -> bool { - (props & (MemoryPropertyFlags::HOST_COHERENT | MemoryPropertyFlags::HOST_VISIBLE)) - == MemoryPropertyFlags::HOST_VISIBLE -} - -fn with_implicit_usage_flags(usage: UsageFlags) -> UsageFlags { - if usage.is_empty() { - UsageFlags::FAST_DEVICE_ACCESS - } else if usage.intersects(UsageFlags::DOWNLOAD | UsageFlags::UPLOAD) { - usage | UsageFlags::HOST_ACCESS - } else { - usage - } -} diff --git a/third_party/rust/gpu-alloc/src/block.rs b/third_party/rust/gpu-alloc/src/block.rs @@ -1,327 +0,0 @@ -use { - crate::{align_down, align_up, error::MapError}, - alloc::sync::Arc, - core::{ - convert::TryFrom as _, - ptr::{copy_nonoverlapping, NonNull}, - // sync::atomic::{AtomicU8, Ordering::*}, - }, - gpu_alloc_types::{MappedMemoryRange, MemoryDevice, MemoryPropertyFlags}, -}; - -#[derive(Debug)] -struct Relevant; - -impl Drop for Relevant { - fn drop(&mut self) { - report_error_on_drop!("Memory block wasn't deallocated"); - } -} - -/// Memory block allocated by `GpuAllocator`. -#[derive(Debug)] -pub struct MemoryBlock<M> { - memory_type: u32, - props: MemoryPropertyFlags, - offset: u64, - size: u64, - atom_mask: u64, - mapped: bool, - flavor: MemoryBlockFlavor<M>, - relevant: Relevant, -} - -impl<M> MemoryBlock<M> { - pub(crate) fn new( - memory_type: u32, - props: MemoryPropertyFlags, - offset: u64, - size: u64, - atom_mask: u64, - flavor: MemoryBlockFlavor<M>, - ) -> Self { - isize::try_from(atom_mask).expect("`atom_mask` is too large"); - MemoryBlock { - memory_type, - props, - offset, - size, - atom_mask, - flavor, - mapped: false, - relevant: Relevant, - } - } - - pub(crate) fn deallocate(self) -> MemoryBlockFlavor<M> { - core::mem::forget(self.relevant); - self.flavor - } -} - -unsafe impl<M> Sync for MemoryBlock<M> where M: Sync {} -unsafe impl<M> Send for MemoryBlock<M> where M: Send {} - -#[derive(Debug)] -pub(crate) enum MemoryBlockFlavor<M> { - Dedicated { - memory: M, - }, - Buddy { - chunk: usize, - index: usize, - ptr: Option<NonNull<u8>>, - memory: Arc<M>, - }, - FreeList { - chunk: u64, - ptr: Option<NonNull<u8>>, - memory: Arc<M>, - }, -} - -impl<M> MemoryBlock<M> { - /// Returns reference to parent memory object. - #[inline(always)] - pub fn memory(&self) -> &M { - match &self.flavor { - MemoryBlockFlavor::Dedicated { memory } => memory, - MemoryBlockFlavor::Buddy { memory, .. } => memory, - MemoryBlockFlavor::FreeList { memory, .. } => memory, - } - } - - /// Returns offset in bytes from start of memory object to start of this block. - #[inline(always)] - pub fn offset(&self) -> u64 { - self.offset - } - - /// Returns size of this memory block. - #[inline(always)] - pub fn size(&self) -> u64 { - self.size - } - - /// Returns memory property flags for parent memory object. - #[inline(always)] - pub fn props(&self) -> MemoryPropertyFlags { - self.props - } - - /// Returns index of type of parent memory object. - #[inline(always)] - pub fn memory_type(&self) -> u32 { - self.memory_type - } - - /// Returns pointer to mapped memory range of this block. - /// This blocks becomes mapped. - /// - /// The user of returned pointer must guarantee that any previously submitted command that writes to this range has completed - /// before the host reads from or writes to that range, - /// and that any previously submitted command that reads from that range has completed - /// before the host writes to that region. - /// If the device memory was allocated without the `HOST_COHERENT` property flag set, - /// these guarantees must be made for an extended range: - /// the user must round down the start of the range to the nearest multiple of `non_coherent_atom_size`, - /// and round the end of the range up to the nearest multiple of `non_coherent_atom_size`. - /// - /// # Panics - /// - /// This function panics if block is currently mapped. - /// - /// # Safety - /// - /// `block` must have been allocated from specified `device`. - #[inline(always)] - pub unsafe fn map( - &mut self, - device: &impl MemoryDevice<M>, - offset: u64, - size: usize, - ) -> Result<NonNull<u8>, MapError> { - let size_u64 = u64::try_from(size).expect("`size` doesn't fit device address space"); - assert!(offset < self.size, "`offset` is out of memory block bounds"); - assert!( - size_u64 <= self.size - offset, - "`offset + size` is out of memory block bounds" - ); - - let ptr = match &mut self.flavor { - MemoryBlockFlavor::Dedicated { memory } => { - let end = align_up(offset + size_u64, self.atom_mask) - .expect("mapping end doesn't fit device address space"); - let aligned_offset = align_down(offset, self.atom_mask); - - if !acquire_mapping(&mut self.mapped) { - return Err(MapError::AlreadyMapped); - } - let result = - device.map_memory(memory, self.offset + aligned_offset, end - aligned_offset); - - match result { - // the overflow is checked in `Self::new()` - Ok(ptr) => { - let ptr_offset = (offset - aligned_offset) as isize; - ptr.as_ptr().offset(ptr_offset) - } - Err(err) => { - release_mapping(&mut self.mapped); - return Err(err.into()); - } - } - } - MemoryBlockFlavor::FreeList { ptr: Some(ptr), .. } - | MemoryBlockFlavor::Buddy { ptr: Some(ptr), .. } => { - if !acquire_mapping(&mut self.mapped) { - return Err(MapError::AlreadyMapped); - } - let offset_isize = isize::try_from(offset) - .expect("Buddy and linear block should fit host address space"); - ptr.as_ptr().offset(offset_isize) - } - _ => return Err(MapError::NonHostVisible), - }; - - Ok(NonNull::new_unchecked(ptr)) - } - - /// Unmaps memory range of this block that was previously mapped with `Block::map`. - /// This block becomes unmapped. - /// - /// # Panics - /// - /// This function panics if this block is not currently mapped. - /// - /// # Safety - /// - /// `block` must have been allocated from specified `device`. - #[inline(always)] - pub unsafe fn unmap(&mut self, device: &impl MemoryDevice<M>) -> bool { - if !release_mapping(&mut self.mapped) { - return false; - } - match &mut self.flavor { - MemoryBlockFlavor::Dedicated { memory } => { - device.unmap_memory(memory); - } - MemoryBlockFlavor::Buddy { .. } => {} - MemoryBlockFlavor::FreeList { .. } => {} - } - true - } - - /// Transiently maps block memory range and copies specified data - /// to the mapped memory range. - /// - /// # Panics - /// - /// This function panics if block is currently mapped. - /// - /// # Safety - /// - /// `block` must have been allocated from specified `device`. - /// The caller must guarantee that any previously submitted command that reads or writes to this range has completed. - #[inline(always)] - pub unsafe fn write_bytes( - &mut self, - device: &impl MemoryDevice<M>, - offset: u64, - data: &[u8], - ) -> Result<(), MapError> { - let size = data.len(); - let ptr = self.map(device, offset, size)?; - - copy_nonoverlapping(data.as_ptr(), ptr.as_ptr(), size); - let result = if !self.coherent() { - let aligned_offset = align_down(offset, self.atom_mask); - let end = align_up(offset + data.len() as u64, self.atom_mask).unwrap(); - - device.flush_memory_ranges(&[MappedMemoryRange { - memory: self.memory(), - offset: self.offset + aligned_offset, - size: end - aligned_offset, - }]) - } else { - Ok(()) - }; - - self.unmap(device); - result.map_err(Into::into) - } - - /// Transiently maps block memory range and copies specified data - /// from the mapped memory range. - /// - /// # Panics - /// - /// This function panics if block is currently mapped. - /// - /// # Safety - /// - /// `block` must have been allocated from specified `device`. - /// The caller must guarantee that any previously submitted command that reads to this range has completed. - #[inline(always)] - pub unsafe fn read_bytes( - &mut self, - device: &impl MemoryDevice<M>, - offset: u64, - data: &mut [u8], - ) -> Result<(), MapError> { - #[cfg(feature = "tracing")] - { - if !self.cached() { - tracing::warn!("Reading from non-cached memory may be slow. Consider allocating HOST_CACHED memory block for host reads.") - } - } - - let size = data.len(); - let ptr = self.map(device, offset, size)?; - let result = if !self.coherent() { - let aligned_offset = align_down(offset, self.atom_mask); - let end = align_up(offset + data.len() as u64, self.atom_mask).unwrap(); - - device.invalidate_memory_ranges(&[MappedMemoryRange { - memory: self.memory(), - offset: self.offset + aligned_offset, - size: end - aligned_offset, - }]) - } else { - Ok(()) - }; - if result.is_ok() { - copy_nonoverlapping(ptr.as_ptr(), data.as_mut_ptr(), size); - } - - self.unmap(device); - result.map_err(Into::into) - } - - fn coherent(&self) -> bool { - self.props.contains(MemoryPropertyFlags::HOST_COHERENT) - } - - #[cfg(feature = "tracing")] - fn cached(&self) -> bool { - self.props.contains(MemoryPropertyFlags::HOST_CACHED) - } -} - -fn acquire_mapping(mapped: &mut bool) -> bool { - if *mapped { - false - } else { - *mapped = true; - true - } -} - -fn release_mapping(mapped: &mut bool) -> bool { - if *mapped { - *mapped = false; - true - } else { - false - } -} diff --git a/third_party/rust/gpu-alloc/src/buddy.rs b/third_party/rust/gpu-alloc/src/buddy.rs @@ -1,460 +0,0 @@ -use { - crate::{ - align_up, error::AllocationError, heap::Heap, slab::Slab, unreachable_unchecked, - util::try_arc_unwrap, MemoryBounds, - }, - alloc::{sync::Arc, vec::Vec}, - core::{convert::TryFrom as _, mem::replace, ptr::NonNull}, - gpu_alloc_types::{AllocationFlags, DeviceMapError, MemoryDevice, MemoryPropertyFlags}, -}; - -#[derive(Debug)] -pub(crate) struct BuddyBlock<M> { - pub memory: Arc<M>, - pub ptr: Option<NonNull<u8>>, - pub offset: u64, - pub size: u64, - pub chunk: usize, - pub index: usize, -} - -unsafe impl<M> Sync for BuddyBlock<M> where M: Sync {} -unsafe impl<M> Send for BuddyBlock<M> where M: Send {} - -#[derive(Clone, Copy, Debug)] -enum PairState { - Exhausted, - Ready { - ready: Side, - next: usize, - prev: usize, - }, -} - -impl PairState { - unsafe fn replace_next(&mut self, value: usize) -> usize { - match self { - PairState::Exhausted => unreachable_unchecked(), - PairState::Ready { next, .. } => replace(next, value), - } - } - - unsafe fn replace_prev(&mut self, value: usize) -> usize { - match self { - PairState::Exhausted => unreachable_unchecked(), - PairState::Ready { prev, .. } => replace(prev, value), - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum Side { - Left, - Right, -} -use Side::*; - -#[derive(Debug)] -struct PairEntry { - state: PairState, - chunk: usize, - offset: u64, - parent: Option<usize>, -} - -struct SizeBlockEntry { - chunk: usize, - offset: u64, - index: usize, -} - -#[derive(Debug)] -struct Size { - next_ready: usize, - pairs: Slab<PairEntry>, -} -#[derive(Debug)] -enum Release { - None, - Parent(usize), - Chunk(usize), -} - -impl Size { - fn new() -> Self { - Size { - pairs: Slab::new(), - next_ready: 0, - } - } - - unsafe fn add_pair_and_acquire_left( - &mut self, - chunk: usize, - offset: u64, - parent: Option<usize>, - ) -> SizeBlockEntry { - if self.next_ready < self.pairs.len() { - unreachable_unchecked() - } - - let index = self.pairs.insert(PairEntry { - state: PairState::Exhausted, - chunk, - offset, - parent, - }); - - let entry = self.pairs.get_unchecked_mut(index); - entry.state = PairState::Ready { - next: index, - prev: index, - ready: Right, // Left is allocated. - }; - self.next_ready = index; - - SizeBlockEntry { - chunk, - offset, - index: index << 1, - } - } - - fn acquire(&mut self, size: u64) -> Option<SizeBlockEntry> { - if self.next_ready >= self.pairs.len() { - return None; - } - - let ready = self.next_ready; - - let entry = unsafe { self.pairs.get_unchecked_mut(ready) }; - let chunk = entry.chunk; - let offset = entry.offset; - - let bit = match entry.state { - PairState::Exhausted => unsafe { unreachable_unchecked() }, - PairState::Ready { ready, next, prev } => { - entry.state = PairState::Exhausted; - - if prev == self.next_ready { - // The only ready entry. - debug_assert_eq!(next, self.next_ready); - self.next_ready = self.pairs.len(); - } else { - let prev_entry = unsafe { self.pairs.get_unchecked_mut(prev) }; - let prev_next = unsafe { prev_entry.state.replace_next(next) }; - debug_assert_eq!(prev_next, self.next_ready); - - let next_entry = unsafe { self.pairs.get_unchecked_mut(next) }; - let next_prev = unsafe { next_entry.state.replace_prev(prev) }; - debug_assert_eq!(next_prev, self.next_ready); - - self.next_ready = next; - } - - match ready { - Left => 0, - Right => 1, - } - } - }; - - Some(SizeBlockEntry { - chunk, - offset: offset + bit as u64 * size, - index: (ready << 1) | bit as usize, - }) - } - - fn release(&mut self, index: usize) -> Release { - let side = match index & 1 { - 0 => Side::Left, - 1 => Side::Right, - _ => unsafe { unreachable_unchecked() }, - }; - let entry_index = index >> 1; - - let len = self.pairs.len(); - - let entry = self.pairs.get_mut(entry_index); - - let chunk = entry.chunk; - let offset = entry.offset; - let parent = entry.parent; - - match entry.state { - PairState::Exhausted => { - if self.next_ready == len { - entry.state = PairState::Ready { - ready: side, - next: entry_index, - prev: entry_index, - }; - self.next_ready = entry_index; - } else { - debug_assert!(self.next_ready < len); - - let next = self.next_ready; - let next_entry = unsafe { self.pairs.get_unchecked_mut(next) }; - let prev = unsafe { next_entry.state.replace_prev(entry_index) }; - - let prev_entry = unsafe { self.pairs.get_unchecked_mut(prev) }; - let prev_next = unsafe { prev_entry.state.replace_next(entry_index) }; - debug_assert_eq!(prev_next, next); - - let entry = unsafe { self.pairs.get_unchecked_mut(entry_index) }; - entry.state = PairState::Ready { - ready: side, - next, - prev, - }; - } - Release::None - } - - PairState::Ready { ready, .. } if ready == side => { - panic!("Attempt to dealloate already free block") - } - - PairState::Ready { next, prev, .. } => { - unsafe { - self.pairs.remove_unchecked(entry_index); - } - - if prev == entry_index { - debug_assert_eq!(next, entry_index); - self.next_ready = self.pairs.len(); - } else { - let prev_entry = unsafe { self.pairs.get_unchecked_mut(prev) }; - let prev_next = unsafe { prev_entry.state.replace_next(next) }; - debug_assert_eq!(prev_next, entry_index); - - let next_entry = unsafe { self.pairs.get_unchecked_mut(next) }; - let next_prev = unsafe { next_entry.state.replace_prev(prev) }; - debug_assert_eq!(next_prev, entry_index); - - self.next_ready = next; - } - - match parent { - Some(parent) => Release::Parent(parent), - None => { - debug_assert_eq!(offset, 0); - Release::Chunk(chunk) - } - } - } - } - } -} - -#[derive(Debug)] -struct Chunk<M> { - memory: Arc<M>, - ptr: Option<NonNull<u8>>, - size: u64, -} - -#[derive(Debug)] -pub(crate) struct BuddyAllocator<M> { - minimal_size: u64, - chunks: Slab<Chunk<M>>, - sizes: Vec<Size>, - memory_type: u32, - props: MemoryPropertyFlags, - atom_mask: u64, -} - -unsafe impl<M> Sync for BuddyAllocator<M> where M: Sync {} -unsafe impl<M> Send for BuddyAllocator<M> where M: Send {} - -impl<M> BuddyAllocator<M> -where - M: MemoryBounds + 'static, -{ - pub fn new( - minimal_size: u64, - initial_dedicated_size: u64, - memory_type: u32, - props: MemoryPropertyFlags, - atom_mask: u64, - ) -> Self { - assert!( - minimal_size.is_power_of_two(), - "Minimal allocation size of buddy allocator must be power of two" - ); - assert!( - initial_dedicated_size.is_power_of_two(), - "Dedicated allocation size of buddy allocator must be power of two" - ); - - let initial_sizes = (initial_dedicated_size - .trailing_zeros() - .saturating_sub(minimal_size.trailing_zeros())) as usize; - - BuddyAllocator { - minimal_size, - chunks: Slab::new(), - sizes: (0..initial_sizes).map(|_| Size::new()).collect(), - memory_type, - props, - atom_mask: atom_mask | (minimal_size - 1), - } - } - - #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, device)))] - pub unsafe fn alloc( - &mut self, - device: &impl MemoryDevice<M>, - size: u64, - align_mask: u64, - flags: AllocationFlags, - heap: &mut Heap, - allocations_remains: &mut u32, - ) -> Result<BuddyBlock<M>, AllocationError> { - let align_mask = align_mask | self.atom_mask; - - let size = align_up(size, align_mask) - .and_then(|size| size.checked_next_power_of_two()) - .ok_or(AllocationError::OutOfDeviceMemory)?; - - let size = size.max(self.minimal_size); - - let size_index = size.trailing_zeros() - self.minimal_size.trailing_zeros(); - let size_index = - usize::try_from(size_index).map_err(|_| AllocationError::OutOfDeviceMemory)?; - - while self.sizes.len() <= size_index { - self.sizes.push(Size::new()); - } - - let host_visible = self.host_visible(); - - let mut candidate_size_index = size_index; - - let (mut entry, entry_size_index) = loop { - let sizes_len = self.sizes.len(); - - let candidate_size_entry = &mut self.sizes[candidate_size_index]; - let candidate_size = self.minimal_size << candidate_size_index; - - if let Some(entry) = candidate_size_entry.acquire(candidate_size) { - break (entry, candidate_size_index); - } - - if sizes_len == candidate_size_index + 1 { - // That's size of device allocation. - if *allocations_remains == 0 { - return Err(AllocationError::TooManyObjects); - } - - let chunk_size = self.minimal_size << (candidate_size_index + 1); - let mut memory = device.allocate_memory(chunk_size, self.memory_type, flags)?; - *allocations_remains -= 1; - heap.alloc(chunk_size); - - let ptr = if host_visible { - match device.map_memory(&mut memory, 0, chunk_size) { - Ok(ptr) => Some(ptr), - Err(DeviceMapError::OutOfDeviceMemory) => { - return Err(AllocationError::OutOfDeviceMemory) - } - Err(DeviceMapError::MapFailed) | Err(DeviceMapError::OutOfHostMemory) => { - return Err(AllocationError::OutOfHostMemory) - } - } - } else { - None - }; - - let chunk = self.chunks.insert(Chunk { - memory: Arc::new(memory), - ptr, - size: chunk_size, - }); - - let entry = candidate_size_entry.add_pair_and_acquire_left(chunk, 0, None); - - break (entry, candidate_size_index); - } - - candidate_size_index += 1; - }; - - for size_index in (size_index..entry_size_index).rev() { - let size_entry = &mut self.sizes[size_index]; - entry = - size_entry.add_pair_and_acquire_left(entry.chunk, entry.offset, Some(entry.index)); - } - - let chunk_entry = self.chunks.get_unchecked(entry.chunk); - - debug_assert!( - entry - .offset - .checked_add(size) - .map_or(false, |end| end <= chunk_entry.size), - "Offset + size is not in chunk bounds" - ); - - Ok(BuddyBlock { - memory: chunk_entry.memory.clone(), - ptr: chunk_entry - .ptr - .map(|ptr| NonNull::new_unchecked(ptr.as_ptr().add(entry.offset as usize))), - offset: entry.offset, - size, - chunk: entry.chunk, - index: entry.index, - }) - } - - #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, device)))] - pub unsafe fn dealloc( - &mut self, - device: &impl MemoryDevice<M>, - block: BuddyBlock<M>, - heap: &mut Heap, - allocations_remains: &mut u32, - ) { - debug_assert!(block.size.is_power_of_two()); - - let size_index = - (block.size.trailing_zeros() - self.minimal_size.trailing_zeros()) as usize; - - let mut release_index = block.index; - let mut release_size_index = size_index; - - loop { - match self.sizes[release_size_index].release(release_index) { - Release::Parent(parent) => { - release_size_index += 1; - release_index = parent; - } - Release::Chunk(chunk) => { - debug_assert_eq!(chunk, block.chunk); - debug_assert_eq!( - self.chunks.get(chunk).size, - self.minimal_size << (release_size_index + 1) - ); - let chunk = self.chunks.remove(chunk); - drop(block); - - let memory = try_arc_unwrap(chunk.memory) - .expect("Memory shared after last block deallocated"); - - device.deallocate_memory(memory); - *allocations_remains += 1; - heap.dealloc(chunk.size); - - return; - } - Release::None => return, - } - } - } - - fn host_visible(&self) -> bool { - self.props.contains(MemoryPropertyFlags::HOST_VISIBLE) - } -} diff --git a/third_party/rust/gpu-alloc/src/config.rs b/third_party/rust/gpu-alloc/src/config.rs @@ -1,77 +0,0 @@ -/// Configuration for [`GpuAllocator`] -/// -/// [`GpuAllocator`]: type.GpuAllocator -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Config { - /// Size in bytes of request that will be served by dedicated memory object. - /// This value should be large enough to not exhaust memory object limit - /// and not use slow memory object allocation when it is not necessary. - pub dedicated_threshold: u64, - - /// Size in bytes of request that will be served by dedicated memory object if preferred. - /// This value should be large enough to not exhaust memory object limit - /// and not use slow memory object allocation when it is not necessary. - /// - /// This won't make much sense if this value is larger than `dedicated_threshold`. - pub preferred_dedicated_threshold: u64, - - /// Size in bytes of transient memory request that will be served by dedicated memory object. - /// This value should be large enough to not exhaust memory object limit - /// and not use slow memory object allocation when it is not necessary. - /// - /// This won't make much sense if this value is lesser than `dedicated_threshold`. - pub transient_dedicated_threshold: u64, - - /// Size in bytes of first chunk in free-list allocator. - pub starting_free_list_chunk: u64, - - /// Upper limit for size in bytes of chunks in free-list allocator. - pub final_free_list_chunk: u64, - - /// Minimal size for buddy allocator. - pub minimal_buddy_size: u64, - - /// Initial memory object size for buddy allocator. - /// If less than `minimal_buddy_size` then `minimal_buddy_size` is used instead. - pub initial_buddy_dedicated_size: u64, -} - -impl Config { - /// Returns default configuration. - /// - /// This is not `Default` implementation to discourage usage outside of - /// prototyping. - /// - /// Proper configuration should depend on hardware and intended usage.\ - /// But those values can be used as starting point.\ - /// Note that they can simply not work for some platforms with lesser - /// memory capacity than today's "modern" GPU (year 2020). - pub fn i_am_prototyping() -> Self { - // Assume that today's modern GPU is made of 1024 potatoes. - let potato = Config::i_am_potato(); - - Config { - dedicated_threshold: potato.dedicated_threshold * 1024, - preferred_dedicated_threshold: potato.preferred_dedicated_threshold * 1024, - transient_dedicated_threshold: potato.transient_dedicated_threshold * 1024, - starting_free_list_chunk: potato.starting_free_list_chunk * 1024, - final_free_list_chunk: potato.final_free_list_chunk * 1024, - minimal_buddy_size: potato.minimal_buddy_size * 1024, - initial_buddy_dedicated_size: potato.initial_buddy_dedicated_size * 1024, - } - } - - /// Returns default configuration for average sized potato. - pub fn i_am_potato() -> Self { - Config { - dedicated_threshold: 32 * 1024, - preferred_dedicated_threshold: 1024, - transient_dedicated_threshold: 128 * 1024, - starting_free_list_chunk: 8 * 1024, - final_free_list_chunk: 128 * 1024, - minimal_buddy_size: 1, - initial_buddy_dedicated_size: 8 * 1024, - } - } -} diff --git a/third_party/rust/gpu-alloc/src/error.rs b/third_party/rust/gpu-alloc/src/error.rs @@ -1,116 +0,0 @@ -use { - core::fmt::{self, Display}, - gpu_alloc_types::{DeviceMapError, OutOfMemory}, -}; - -/// Enumeration of possible errors that may occur during memory allocation. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum AllocationError { - /// Backend reported that device memory has been exhausted.\ - /// Deallocating device memory from the same heap may increase chance - /// that another allocation would succeed. - OutOfDeviceMemory, - - /// Backend reported that host memory has been exhausted.\ - /// Deallocating host memory may increase chance that another allocation would succeed. - OutOfHostMemory, - - /// Allocation request cannot be fulfilled as no available memory types allowed - /// by `Request.memory_types` mask is compatible with `request.usage`. - NoCompatibleMemoryTypes, - - /// Reached limit on allocated memory objects count.\ - /// Deallocating device memory may increase chance that another allocation would succeed. - /// Especially dedicated memory blocks. - /// - /// If this error is returned when memory heaps are far from exhausted - /// `Config` should be tweaked to allocate larger memory objects. - TooManyObjects, -} - -impl From<OutOfMemory> for AllocationError { - fn from(err: OutOfMemory) -> Self { - match err { - OutOfMemory::OutOfDeviceMemory => AllocationError::OutOfDeviceMemory, - OutOfMemory::OutOfHostMemory => AllocationError::OutOfHostMemory, - } - } -} - -impl Display for AllocationError { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - AllocationError::OutOfDeviceMemory => fmt.write_str("Device memory exhausted"), - AllocationError::OutOfHostMemory => fmt.write_str("Host memory exhausted"), - AllocationError::NoCompatibleMemoryTypes => fmt.write_str( - "No compatible memory types from requested types support requested usage", - ), - AllocationError::TooManyObjects => { - fmt.write_str("Reached limit on allocated memory objects count") - } - } - } -} - -#[cfg(feature = "std")] -impl std::error::Error for AllocationError {} - -/// Enumeration of possible errors that may occur during memory mapping. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum MapError { - /// Backend reported that device memory has been exhausted.\ - /// Deallocating device memory from the same heap may increase chance - /// that another mapping would succeed. - OutOfDeviceMemory, - - /// Backend reported that host memory has been exhausted.\ - /// Deallocating host memory may increase chance that another mapping would succeed. - OutOfHostMemory, - - /// Attempt to map memory block with non-host-visible memory type.\ - /// Ensure to include `UsageFlags::HOST_ACCESS` into allocation request - /// when memory mapping is intended. - NonHostVisible, - - /// Map failed for implementation specific reason.\ - /// For Vulkan backend this includes failed attempt - /// to allocate large enough virtual address space. - MapFailed, - - /// Mapping failed due to block being already mapped. - AlreadyMapped, -} - -impl From<DeviceMapError> for MapError { - fn from(err: DeviceMapError) -> Self { - match err { - DeviceMapError::OutOfDeviceMemory => MapError::OutOfDeviceMemory, - DeviceMapError::OutOfHostMemory => MapError::OutOfHostMemory, - DeviceMapError::MapFailed => MapError::MapFailed, - } - } -} - -impl From<OutOfMemory> for MapError { - fn from(err: OutOfMemory) -> Self { - match err { - OutOfMemory::OutOfDeviceMemory => MapError::OutOfDeviceMemory, - OutOfMemory::OutOfHostMemory => MapError::OutOfHostMemory, - } - } -} - -impl Display for MapError { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - MapError::OutOfDeviceMemory => fmt.write_str("Device memory exhausted"), - MapError::OutOfHostMemory => fmt.write_str("Host memory exhausted"), - MapError::MapFailed => fmt.write_str("Failed to map memory object"), - MapError::NonHostVisible => fmt.write_str("Impossible to map non-host-visible memory"), - MapError::AlreadyMapped => fmt.write_str("Block is already mapped"), - } - } -} - -#[cfg(feature = "std")] -impl std::error::Error for MapError {} diff --git a/third_party/rust/gpu-alloc/src/freelist.rs b/third_party/rust/gpu-alloc/src/freelist.rs @@ -1,528 +0,0 @@ -use { - crate::{ - align_down, align_up, - error::AllocationError, - heap::Heap, - util::{arc_unwrap, is_arc_unique}, - MemoryBounds, - }, - alloc::{sync::Arc, vec::Vec}, - core::{cmp::Ordering, ptr::NonNull}, - gpu_alloc_types::{AllocationFlags, DeviceMapError, MemoryDevice, MemoryPropertyFlags}, -}; - -unsafe fn opt_ptr_add(ptr: Option<NonNull<u8>>, size: u64) -> Option<NonNull<u8>> { - ptr.map(|ptr| { - // Size is within memory region started at `ptr`. - // size is within `chunk_size` that fits `isize`. - NonNull::new_unchecked(ptr.as_ptr().offset(size as isize)) - }) -} - -#[derive(Debug)] -pub(super) struct FreeList<M> { - array: Vec<FreeListRegion<M>>, - counter: u64, -} - -impl<M> FreeList<M> { - pub fn new() -> Self { - FreeList { - array: Vec::new(), - counter: 0, - } - } - - pub fn get_block_from_new_memory( - &mut self, - memory: Arc<M>, - memory_size: u64, - ptr: Option<NonNull<u8>>, - align_mask: u64, - size: u64, - ) -> FreeListBlock<M> { - debug_assert!(size <= memory_size); - - self.counter += 1; - self.array.push(FreeListRegion { - memory, - ptr, - chunk: self.counter, - start: 0, - end: memory_size, - }); - self.get_block_at(self.array.len() - 1, align_mask, size) - } - - pub fn get_block(&mut self, align_mask: u64, size: u64) -> Option<FreeListBlock<M>> { - let (index, _) = self.array.iter().enumerate().rev().find(|(_, region)| { - match region.end.checked_sub(size) { - Some(start) => { - let aligned_start = align_down(start, align_mask); - aligned_start >= region.start - } - None => false, - } - })?; - - Some(self.get_block_at(index, align_mask, size)) - } - - fn get_block_at(&mut self, index: usize, align_mask: u64, size: u64) -> FreeListBlock<M> { - let region = &mut self.array[index]; - - let start = region.end - size; - let aligned_start = align_down(start, align_mask); - - if aligned_start > region.start { - let block = FreeListBlock { - offset: aligned_start, - size: region.end - aligned_start, - chunk: region.chunk, - ptr: unsafe { opt_ptr_add(region.ptr, aligned_start - region.start) }, - memory: region.memory.clone(), - }; - - region.end = aligned_start; - - block - } else { - debug_assert_eq!(aligned_start, region.start); - let region = self.array.remove(index); - region.into_block() - } - } - - pub fn insert_block(&mut self, block: FreeListBlock<M>) { - match self.array.binary_search_by(|b| b.cmp(&block)) { - Ok(_) => { - panic!("Overlapping block found in free list"); - } - Err(index) if self.array.len() > index => match &mut self.array[..=index] { - [] => unreachable!(), - [next] => { - debug_assert!(!next.is_suffix_block(&block)); - - if next.is_prefix_block(&block) { - next.merge_prefix_block(block); - } else { - self.array.insert(0, FreeListRegion::from_block(block)); - } - } - [.., prev, next] => { - debug_assert!(!prev.is_prefix_block(&block)); - debug_assert!(!next.is_suffix_block(&block)); - - if next.is_prefix_block(&block) { - next.merge_prefix_block(block); - - if prev.consecutive(&*next) { - let next = self.array.remove(index); - let prev = &mut self.array[index - 1]; - prev.merge(next); - } - } else if prev.is_suffix_block(&block) { - prev.merge_suffix_block(block); - } else { - self.array.insert(index, FreeListRegion::from_block(block)); - } - } - }, - Err(_) => match &mut self.array[..] { - [] => self.array.push(FreeListRegion::from_block(block)), - [.., prev] => { - debug_assert!(!prev.is_prefix_block(&block)); - if prev.is_suffix_block(&block) { - prev.merge_suffix_block(block); - } else { - self.array.push(FreeListRegion::from_block(block)); - } - } - }, - } - } - - pub fn drain(&mut self, keep_last: bool) -> Option<impl Iterator<Item = (M, u64)> + '_> { - // Time to deallocate - - let len = self.array.len(); - - let mut del = 0; - { - let regions = &mut self.array[..]; - - for i in 0..len { - if (i < len - 1 || !keep_last) && is_arc_unique(&mut regions[i].memory) { - del += 1; - } else if del > 0 { - regions.swap(i - del, i); - } - } - } - - if del > 0 { - Some(self.array.drain(len - del..).map(move |region| { - debug_assert_eq!(region.start, 0); - (unsafe { arc_unwrap(region.memory) }, region.end) - })) - } else { - None - } - } -} - -#[derive(Debug)] -struct FreeListRegion<M> { - memory: Arc<M>, - ptr: Option<NonNull<u8>>, - chunk: u64, - start: u64, - end: u64, -} - -unsafe impl<M> Sync for FreeListRegion<M> where M: Sync {} -unsafe impl<M> Send for FreeListRegion<M> where M: Send {} - -impl<M> FreeListRegion<M> { - pub fn cmp(&self, block: &FreeListBlock<M>) -> Ordering { - debug_assert_eq!( - Arc::ptr_eq(&self.memory, &block.memory), - self.chunk == block.chunk - ); - - if self.chunk == block.chunk { - debug_assert_eq!( - Ord::cmp(&self.start, &block.offset), - Ord::cmp(&self.end, &(block.offset + block.size)), - "Free region {{ start: {}, end: {} }} overlaps with block {{ offset: {}, size: {} }}", - self.start, - self.end, - block.offset, - block.size, - ); - } - - Ord::cmp(&self.chunk, &block.chunk).then(Ord::cmp(&self.start, &block.offset)) - } - - fn from_block(block: FreeListBlock<M>) -> Self { - FreeListRegion { - memory: block.memory, - chunk: block.chunk, - ptr: block.ptr, - start: block.offset, - end: block.offset + block.size, - } - } - - fn into_block(self) -> FreeListBlock<M> { - FreeListBlock { - memory: self.memory, - chunk: self.chunk, - ptr: self.ptr, - offset: self.start, - size: self.end - self.start, - } - } - - fn consecutive(&self, other: &Self) -> bool { - if self.chunk != other.chunk { - return false; - } - - debug_assert!(Arc::ptr_eq(&self.memory, &other.memory)); - - debug_assert_eq!( - Ord::cmp(&self.start, &other.start), - Ord::cmp(&self.end, &other.end) - ); - - self.end == other.start - } - - fn merge(&mut self, next: FreeListRegion<M>) { - debug_assert!(self.consecutive(&next)); - self.end = next.end; - } - - fn is_prefix_block(&self, block: &FreeListBlock<M>) -> bool { - if self.chunk != block.chunk { - return false; - } - - debug_assert!(Arc::ptr_eq(&self.memory, &block.memory)); - - debug_assert_eq!( - Ord::cmp(&self.start, &block.offset), - Ord::cmp(&self.end, &(block.offset + block.size)) - ); - - self.start == (block.offset + block.size) - } - - fn merge_prefix_block(&mut self, block: FreeListBlock<M>) { - debug_assert!(self.is_prefix_block(&block)); - self.start = block.offset; - self.ptr = block.ptr; - } - - fn is_suffix_block(&self, block: &FreeListBlock<M>) -> bool { - if self.chunk != block.chunk { - return false; - } - - debug_assert!(Arc::ptr_eq(&self.memory, &block.memory)); - - debug_assert_eq!( - Ord::cmp(&self.start, &block.offset), - Ord::cmp(&self.end, &(block.offset + block.size)) - ); - - self.end == block.offset - } - - fn merge_suffix_block(&mut self, block: FreeListBlock<M>) { - debug_assert!(self.is_suffix_block(&block)); - self.end += block.size; - } -} - -#[derive(Debug)] -pub struct FreeListBlock<M> { - pub memory: Arc<M>, - pub ptr: Option<NonNull<u8>>, - pub chunk: u64, - pub offset: u64, - pub size: u64, -} - -unsafe impl<M> Sync for FreeListBlock<M> where M: Sync {} -unsafe impl<M> Send for FreeListBlock<M> where M: Send {} - -#[derive(Debug)] -pub(crate) struct FreeListAllocator<M> { - freelist: FreeList<M>, - chunk_size: u64, - final_chunk_size: u64, - memory_type: u32, - props: MemoryPropertyFlags, - atom_mask: u64, - - total_allocations: u64, - total_deallocations: u64, -} - -impl<M> Drop for FreeListAllocator<M> { - fn drop(&mut self) { - match Ord::cmp(&self.total_allocations, &self.total_deallocations) { - Ordering::Equal => {} - Ordering::Greater => { - report_error_on_drop!("Not all blocks were deallocated") - } - Ordering::Less => { - report_error_on_drop!("More blocks deallocated than allocated") - } - } - - if !self.freelist.array.is_empty() { - report_error_on_drop!( - "FreeListAllocator has free blocks on drop. Allocator should be cleaned" - ); - } - } -} - -impl<M> FreeListAllocator<M> -where - M: MemoryBounds + 'static, -{ - pub fn new( - starting_chunk_size: u64, - final_chunk_size: u64, - memory_type: u32, - props: MemoryPropertyFlags, - atom_mask: u64, - ) -> Self { - debug_assert_eq!( - align_down(starting_chunk_size, atom_mask), - starting_chunk_size - ); - - let starting_chunk_size = min(starting_chunk_size, isize::max_value()); - - debug_assert_eq!(align_down(final_chunk_size, atom_mask), final_chunk_size); - let final_chunk_size = min(final_chunk_size, isize::max_value()); - - FreeListAllocator { - freelist: FreeList::new(), - chunk_size: starting_chunk_size, - final_chunk_size, - memory_type, - props, - atom_mask, - - total_allocations: 0, - total_deallocations: 0, - } - } - - #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, device)))] - pub unsafe fn alloc( - &mut self, - device: &impl MemoryDevice<M>, - size: u64, - align_mask: u64, - flags: AllocationFlags, - heap: &mut Heap, - allocations_remains: &mut u32, - ) -> Result<FreeListBlock<M>, AllocationError> { - debug_assert!( - self.final_chunk_size >= size, - "GpuAllocator must not request allocations equal or greater to chunks size" - ); - - let size = align_up(size, self.atom_mask).expect( - "Any value not greater than final chunk size (which is aligned) has to fit for alignment", - ); - - let align_mask = align_mask | self.atom_mask; - let host_visible = self.host_visible(); - - if size <= self.chunk_size { - // Otherwise there can't be any sufficiently large free blocks - if let Some(block) = self.freelist.get_block(align_mask, size) { - self.total_allocations += 1; - return Ok(block); - } - } - - // New allocation is required. - if *allocations_remains == 0 { - return Err(AllocationError::TooManyObjects); - } - - if size > self.chunk_size { - let multiple = (size - 1) / self.chunk_size + 1; - let multiple = multiple.next_power_of_two(); - - self.chunk_size = (self.chunk_size * multiple).min(self.final_chunk_size); - } - - let mut memory = device.allocate_memory(self.chunk_size, self.memory_type, flags)?; - *allocations_remains -= 1; - heap.alloc(self.chunk_size); - - // Map host visible allocations - let ptr = if host_visible { - match device.map_memory(&mut memory, 0, self.chunk_size) { - Ok(ptr) => Some(ptr), - Err(DeviceMapError::MapFailed) => { - #[cfg(feature = "tracing")] - tracing::error!("Failed to map host-visible memory in linear allocator"); - device.deallocate_memory(memory); - *allocations_remains += 1; - heap.dealloc(self.chunk_size); - - return Err(AllocationError::OutOfHostMemory); - } - Err(DeviceMapError::OutOfDeviceMemory) => { - return Err(AllocationError::OutOfDeviceMemory); - } - Err(DeviceMapError::OutOfHostMemory) => { - return Err(AllocationError::OutOfHostMemory); - } - } - } else { - None - }; - - let memory = Arc::new(memory); - let block = - self.freelist - .get_block_from_new_memory(memory, self.chunk_size, ptr, align_mask, size); - - if self.chunk_size < self.final_chunk_size { - // Double next chunk size - // Limit to final value. - self.chunk_size = (self.chunk_size * 2).min(self.final_chunk_size); - } - - self.total_allocations += 1; - Ok(block) - } - - #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, device)))] - pub unsafe fn dealloc( - &mut self, - device: &impl MemoryDevice<M>, - block: FreeListBlock<M>, - heap: &mut Heap, - allocations_remains: &mut u32, - ) { - debug_assert!(block.size < self.chunk_size); - debug_assert_ne!(block.size, 0); - self.freelist.insert_block(block); - self.total_deallocations += 1; - - if let Some(memory) = self.freelist.drain(true) { - memory.for_each(|(memory, size)| { - device.deallocate_memory(memory); - *allocations_remains += 1; - heap.dealloc(size); - }); - } - } - - /// Deallocates leftover memory objects. - /// Should be used before dropping. - /// - /// # Safety - /// - /// * `device` must be one with `DeviceProperties` that were provided to create this `GpuAllocator` instance - /// * Same `device` instance must be used for all interactions with one `GpuAllocator` instance - /// and memory blocks allocated from it - #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, device)))] - pub unsafe fn cleanup( - &mut self, - device: &impl MemoryDevice<M>, - heap: &mut Heap, - allocations_remains: &mut u32, - ) { - if let Some(memory) = self.freelist.drain(false) { - memory.for_each(|(memory, size)| { - device.deallocate_memory(memory); - *allocations_remains += 1; - heap.dealloc(size); - }); - } - - #[cfg(feature = "tracing")] - { - if self.total_allocations == self.total_deallocations && !self.freelist.array.is_empty() - { - tracing::error!( - "Some regions were not deallocated on cleanup, although all blocks are free. - This is a bug in `FreeBlockAllocator`. - See array of free blocks left: - {:#?}", - self.freelist.array, - ); - } - } - } - - fn host_visible(&self) -> bool { - self.props.contains(MemoryPropertyFlags::HOST_VISIBLE) - } -} - -fn min<L, R>(l: L, r: R) -> L -where - R: core::convert::TryInto<L>, - L: Ord, -{ - match r.try_into() { - Ok(r) => core::cmp::min(l, r), - Err(_) => l, - } -} diff --git a/third_party/rust/gpu-alloc/src/heap.rs b/third_party/rust/gpu-alloc/src/heap.rs @@ -1,32 +0,0 @@ -#[derive(Debug)] -pub(crate) struct Heap { - size: u64, - used: u64, - allocated: u128, - deallocated: u128, -} - -impl Heap { - pub(crate) fn new(size: u64) -> Self { - Heap { - size, - used: 0, - allocated: 0, - deallocated: 0, - } - } - - pub(crate) fn size(&mut self) -> u64 { - self.size - } - - pub(crate) fn alloc(&mut self, size: u64) { - self.used += size; - self.allocated += u128::from(size); - } - - pub(crate) fn dealloc(&mut self, size: u64) { - self.used -= size; - self.deallocated += u128::from(size); - } -} diff --git a/third_party/rust/gpu-alloc/src/lib.rs b/third_party/rust/gpu-alloc/src/lib.rs @@ -1,124 +0,0 @@ -//! -//! Implementation agnostic memory allocator for Vulkan like APIs. -//! -//! This crate is intended to be used as part of safe API implementations.\ -//! Use with caution. There are unsafe functions all over the place. -//! -//! # Usage -//! -//! Start with fetching `DeviceProperties` from `gpu-alloc-<backend>` crate for the backend of choice.\ -//! Then create `GpuAllocator` instance and use it for all device memory allocations.\ -//! `GpuAllocator` will take care for all necessary bookkeeping like memory object count limit, -//! heap budget and memory mapping. -//! -//! ### Backends implementations -//! -//! Backend supporting crates should not depend on this crate.\ -//! Instead they should depend on `gpu-alloc-types` which is much more stable, -//! allowing to upgrade `gpu-alloc` version without `gpu-alloc-<backend>` upgrade. -//! - -#![cfg_attr(not(feature = "std"), no_std)] - -extern crate alloc; - -#[cfg(feature = "tracing")] -macro_rules! report_error_on_drop { - ($($tokens:tt)*) => {{ - #[cfg(feature = "std")] - { - if std::thread::panicking() { - return; - } - } - - tracing::error!($($tokens)*) - }}; -} - -#[cfg(all(not(feature = "tracing"), feature = "std"))] -macro_rules! report_error_on_drop { - ($($tokens:tt)*) => {{ - if std::thread::panicking() { - return; - } - eprintln!($($tokens)*) - }}; -} - -#[cfg(all(not(feature = "tracing"), not(feature = "std")))] -macro_rules! report_error_on_drop { - ($($tokens:tt)*) => {{ - panic!($($tokens)*) - }}; -} - -mod allocator; -mod block; -mod buddy; -mod config; -mod error; -mod freelist; -mod heap; -mod slab; -mod usage; -mod util; - -pub use { - self::{allocator::*, block::MemoryBlock, config::*, error::*, usage::*}, - gpu_alloc_types::*, -}; - -/// Memory request for allocator. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct Request { - /// Minimal size of memory block required. - /// Returned block may have larger size, - /// use `MemoryBlock::size` to learn actual size of returned block. - pub size: u64, - - /// Minimal alignment mask required. - /// Returned block may have larger alignment, - /// use `MemoryBlock::align` to learn actual alignment of returned block. - pub align_mask: u64, - - /// Intended memory usage. - /// Returned block may support additional usages, - /// use `MemoryBlock::props` to learn memory properties of returned block. - pub usage: UsageFlags, - - /// Bitset for memory types. - /// Returned block will be from memory type corresponding to one of set bits, - /// use `MemoryBlock::memory_type` to learn memory type index of returned block. - pub memory_types: u32, -} - -/// Aligns `value` up to `align_mask` -/// Returns smallest integer not lesser than `value` aligned by `align_mask`. -/// Returns `None` on overflow. -pub(crate) fn align_up(value: u64, align_mask: u64) -> Option<u64> { - Some(value.checked_add(align_mask)? & !align_mask) -} - -/// Align `value` down to `align_mask` -/// Returns largest integer not bigger than `value` aligned by `align_mask`. -pub(crate) fn align_down(value: u64, align_mask: u64) -> u64 { - value & !align_mask -} - -#[cfg(debug_assertions)] -#[allow(unused_unsafe)] -unsafe fn unreachable_unchecked() -> ! { - unreachable!() -} - -#[cfg(not(debug_assertions))] -unsafe fn unreachable_unchecked() -> ! { - core::hint::unreachable_unchecked() -} - -// #[cfg(feature = "tracing")] -use core::fmt::Debug as MemoryBounds; - -// #[cfg(not(feature = "tracing"))] -// use core::any::Any as MemoryBounds; diff --git a/third_party/rust/gpu-alloc/src/slab.rs b/third_party/rust/gpu-alloc/src/slab.rs @@ -1,97 +0,0 @@ -use {crate::unreachable_unchecked, alloc::vec::Vec, core::mem::replace}; - -#[derive(Debug)] -enum Entry<T> { - Vacant(usize), - Occupied(T), -} -#[derive(Debug)] -pub(crate) struct Slab<T> { - next_vacant: usize, - entries: Vec<Entry<T>>, -} - -impl<T> Slab<T> { - pub fn new() -> Self { - Slab { - next_vacant: !0, - entries: Vec::new(), - } - } - - /// Inserts value into this linked vec and returns index - /// at which value can be accessed in constant time. - pub fn insert(&mut self, value: T) -> usize { - if self.next_vacant >= self.entries.len() { - self.entries.push(Entry::Occupied(value)); - self.entries.len() - 1 - } else { - match *unsafe { self.entries.get_unchecked(self.next_vacant) } { - Entry::Vacant(next_vacant) => { - unsafe { - *self.entries.get_unchecked_mut(self.next_vacant) = Entry::Occupied(value); - } - replace(&mut self.next_vacant, next_vacant) - } - _ => unsafe { unreachable_unchecked() }, - } - } - } - - pub fn len(&self) -> usize { - self.entries.len() - } - - pub unsafe fn get_unchecked(&self, index: usize) -> &T { - debug_assert!(index < self.len()); - - match self.entries.get_unchecked(index) { - Entry::Occupied(value) => value, - _ => unreachable_unchecked(), - } - } - - pub unsafe fn get_unchecked_mut(&mut self, index: usize) -> &mut T { - debug_assert!(index < self.len()); - - match self.entries.get_unchecked_mut(index) { - Entry::Occupied(value) => value, - _ => unreachable_unchecked(), - } - } - - pub fn get(&self, index: usize) -> &T { - match self.entries.get(index) { - Some(Entry::Occupied(value)) => value, - _ => panic!("Invalid index"), - } - } - - pub fn get_mut(&mut self, index: usize) -> &mut T { - match self.entries.get_mut(index) { - Some(Entry::Occupied(value)) => value, - _ => panic!("Invalid index"), - } - } - - pub unsafe fn remove_unchecked(&mut self, index: usize) -> T { - let entry = replace( - self.entries.get_unchecked_mut(index), - Entry::Vacant(self.next_vacant), - ); - - self.next_vacant = index; - - match entry { - Entry::Occupied(value) => value, - _ => unreachable_unchecked(), - } - } - - pub fn remove(&mut self, index: usize) -> T { - match self.entries.get_mut(index) { - Some(Entry::Occupied(_)) => unsafe { self.remove_unchecked(index) }, - _ => panic!("Invalid index"), - } - } -} diff --git a/third_party/rust/gpu-alloc/src/usage.rs b/third_party/rust/gpu-alloc/src/usage.rs @@ -1,176 +0,0 @@ -use { - core::fmt::{self, Debug}, - gpu_alloc_types::{MemoryPropertyFlags, MemoryType}, -}; - -bitflags::bitflags! { - /// Memory usage type. - /// Bits set define intended usage for requested memory. - #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] - #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] - pub struct UsageFlags: u8 { - /// Hints for allocator to find memory with faster device access. - /// If no flags is specified than `FAST_DEVICE_ACCESS` is implied. - const FAST_DEVICE_ACCESS = 0x01; - - /// Memory will be accessed from host. - /// This flags guarantees that host memory operations will be available. - /// Otherwise implementation is encouraged to use non-host-accessible memory. - const HOST_ACCESS = 0x02; - - /// Hints allocator that memory will be used for data downloading. - /// Allocator will strongly prefer host-cached memory. - /// Implies `HOST_ACCESS` flag. - const DOWNLOAD = 0x04; - - /// Hints allocator that memory will be used for data uploading. - /// If `DOWNLOAD` flag is not set then allocator will assume that - /// host will access memory in write-only manner and may - /// pick not host-cached. - /// Implies `HOST_ACCESS` flag. - const UPLOAD = 0x08; - - /// Hints allocator that memory will be used for short duration - /// allowing to use faster algorithm with less memory overhead. - /// If use holds returned memory block for too long then - /// effective memory overhead increases instead. - /// Best use case is for staging buffer for single batch of operations. - const TRANSIENT = 0x10; - - /// Requests memory that can be addressed with `u64`. - /// Allows fetching device address for resources bound to that memory. - const DEVICE_ADDRESS = 0x20; - } -} - -#[derive(Clone, Copy, Debug)] -struct MemoryForOneUsage { - mask: u32, - types: [u32; 32], - types_count: u32, -} - -pub(crate) struct MemoryForUsage { - usages: [MemoryForOneUsage; 64], -} - -impl Debug for MemoryForUsage { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt.debug_struct("MemoryForUsage") - .field("usages", &&self.usages[..]) - .finish() - } -} - -impl MemoryForUsage { - pub fn new(memory_types: &[MemoryType]) -> Self { - assert!( - memory_types.len() <= 32, - "Only up to 32 memory types supported" - ); - - let mut mfu = MemoryForUsage { - usages: [MemoryForOneUsage { - mask: 0, - types: [0; 32], - types_count: 0, - }; 64], - }; - - for usage in 0..64 { - mfu.usages[usage as usize] = - one_usage(UsageFlags::from_bits_truncate(usage), memory_types); - } - - mfu - } - - /// Returns mask with bits set for memory type indices that support the - /// usage. - pub fn mask(&self, usage: UsageFlags) -> u32 { - self.usages[usage.bits() as usize].mask - } - - /// Returns slice of memory type indices that support the usage. - /// Earlier memory type has priority over later. - pub fn types(&self, usage: UsageFlags) -> &[u32] { - let usage = &self.usages[usage.bits() as usize]; - &usage.types[..usage.types_count as usize] - } -} - -fn one_usage(usage: UsageFlags, memory_types: &[MemoryType]) -> MemoryForOneUsage { - let mut types = [0; 32]; - let mut types_count = 0; - - for (index, mt) in memory_types.iter().enumerate() { - if compatible(usage, mt.props) { - types[types_count as usize] = index as u32; - types_count += 1; - } - } - - types[..types_count as usize] - .sort_unstable_by_key(|&index| reverse_priority(usage, memory_types[index as usize].props)); - - let mask = types[..types_count as usize] - .iter() - .fold(0u32, |mask, index| mask | 1u32 << index); - - MemoryForOneUsage { - mask, - types, - types_count, - } -} - -fn compatible(usage: UsageFlags, flags: MemoryPropertyFlags) -> bool { - type Flags = MemoryPropertyFlags; - if flags.contains(Flags::LAZILY_ALLOCATED) || flags.contains(Flags::PROTECTED) { - // Unsupported - false - } else if usage.intersects(UsageFlags::HOST_ACCESS | UsageFlags::UPLOAD | UsageFlags::DOWNLOAD) - { - // Requires HOST_VISIBLE - flags.contains(Flags::HOST_VISIBLE) - } else { - true - } -} - -/// Returns reversed priority of memory with specified flags for specified usage. -/// Lesser value returned = more prioritized. -fn reverse_priority(usage: UsageFlags, flags: MemoryPropertyFlags) -> u32 { - type Flags = MemoryPropertyFlags; - - // Highly prefer device local memory when `FAST_DEVICE_ACCESS` usage is specified - // or usage is empty. - let device_local: bool = flags.contains(Flags::DEVICE_LOCAL) - ^ (usage.is_empty() || usage.contains(UsageFlags::FAST_DEVICE_ACCESS)); - - assert!( - flags.contains(Flags::HOST_VISIBLE) - || !usage - .intersects(UsageFlags::HOST_ACCESS | UsageFlags::UPLOAD | UsageFlags::DOWNLOAD) - ); - - // Prefer non-host-visible memory when host access is not required. - let host_visible: bool = flags.contains(Flags::HOST_VISIBLE) - ^ usage.intersects(UsageFlags::HOST_ACCESS | UsageFlags::UPLOAD | UsageFlags::DOWNLOAD); - - // Prefer cached memory for downloads. - // Or non-cached if downloads are not expected. - let host_cached: bool = - flags.contains(Flags::HOST_CACHED) ^ usage.contains(UsageFlags::DOWNLOAD); - - // Prefer coherent for both uploads and downloads. - // Prefer non-coherent if neither flags is set. - let host_coherent: bool = flags.contains(Flags::HOST_COHERENT) - ^ (usage.intersects(UsageFlags::UPLOAD | UsageFlags::DOWNLOAD)); - - // Each boolean is false if flags are preferred. - device_local as u32 * 8 - + host_visible as u32 * 4 - + host_cached as u32 * 2 - + host_coherent as u32 -} diff --git a/third_party/rust/gpu-alloc/src/util.rs b/third_party/rust/gpu-alloc/src/util.rs @@ -1,44 +0,0 @@ -use alloc::sync::Arc; - -/// Guarantees uniqueness only if `Weak` pointers are never created -/// from this `Arc` or clones. -pub(crate) fn is_arc_unique<M>(arc: &mut Arc<M>) -> bool { - let strong_count = Arc::strong_count(&*arc); - debug_assert_ne!(strong_count, 0, "This Arc should exist"); - - debug_assert!( - strong_count > 1 || Arc::get_mut(arc).is_some(), - "`Weak` pointer exists" - ); - - strong_count == 1 -} - -/// Can be used instead of `Arc::try_unwrap(arc).unwrap()` -/// when it is guaranteed to succeed. -pub(crate) unsafe fn arc_unwrap<M>(mut arc: Arc<M>) -> M { - use core::{mem::ManuallyDrop, ptr::read}; - debug_assert!(is_arc_unique(&mut arc)); - - // Get raw pointer to inner value. - let raw = Arc::into_raw(arc); - - // As `Arc` is unique and no Weak pointers exist - // it won't be dereferenced elsewhere. - let inner = read(raw); - - // Cast to `ManuallyDrop` which guarantees to have same layout - // and will skip dropping. - drop(Arc::from_raw(raw as *const ManuallyDrop<M>)); - inner -} - -/// Can be used instead of `Arc::try_unwrap` -/// only if `Weak` pointers are never created from this `Arc` or clones. -pub(crate) unsafe fn try_arc_unwrap<M>(mut arc: Arc<M>) -> Option<M> { - if is_arc_unique(&mut arc) { - Some(arc_unwrap(arc)) - } else { - None - } -} diff --git a/third_party/rust/metal/.cargo-checksum.json b/third_party/rust/metal/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.lock":"1b40f6331028daf77b51c224fc28791564bdd4ab2c98c6c95784a716bf490f44","Cargo.toml":"2d8b7038928dded0d6c7df8052c5d45052189c7c13b4023b1eb8f38e68e8aea6","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"0621878e61f0d0fda054bcbe02df75192c28bde1ecc8289cbd86aeba2dd72720","Makefile":"99c4c592467981921d1257a4e9e841dce53624448738d0ed52a75431a406f7bc","README.md":"5cc24c3a06efb2be2280529bc0252b39c55bac347b3a558bc64047f9f421b9e2","assets/metal.svg":"3a295bd130785bc2fea2f3a91a4f6c7b4b3c40682c3fcb7a75f9887b66b06264","bors.toml":"c2733ec512a08bf76b6a1ed77270810a0edeb888ba24c2a75679e1eb1bd563a5","examples/argument-buffer/main.rs":"a087db6648a4b092520de29616521c704d892e0d3ace935d16f3f339415c4361","examples/bind/main.rs":"a0c85aad05f08666f9b380a7146a8473a6a6fe0db5d523760373093a0af20e5f","examples/bindless/main.rs":"020fdc78a4cf9196dd80c333d0ddb2569202b573b90ad5df2081d4ff88318372","examples/caps/main.rs":"b7be00c1cc9042140d34ea05051152a7035f316f0bdcd31ac5a660a97e0c4f70","examples/circle/README.md":"e1c97cf5252f0d1f2934ace78b5d839c5f45911f3007dbd2925eeceefb8f0af6","examples/circle/main.rs":"2bf6c3712ad97454a257d97493ab2944c1d367613cd68c9a6d941b5ce727446a","examples/circle/screenshot.png":"97bf07c85bf02367447b9c8a81707c124e4a3b420fa386b95ba08b21938f4f2a","examples/circle/shaders.metal":"5e4f40efca5bb386204a09e1b983cc6c634fdf1ca9dd4974227313adbf50e8b5","examples/circle/shaders.metallib":"666a9491d795ef9c0b9c122c7ada571cc2c0e8774d2d89e5b4b996f3dc47962b","examples/compute/compute-argument-buffer.metal":"6530bbd6a0101d9db2893805436f5dc877959e81ea97a27014c0fc52fc9fa77b","examples/compute/compute-argument-buffer.rs":"8c02647426cda46d2687890eb64a833bf5bcf4740109fb871f56d4aea6984bcf","examples/compute/embedded-lib.rs":"55f701810fa5270c27ca771e713f9f8cf09e124a997b0b03790b38435593a7ea","examples/compute/main.rs":"a9eee4e600887f2dafdd953a899cf1d11ea5fc252297cf5f9747f209454ba173","examples/compute/shaders.metal":"f2b15551bb5247b88a3029c3d8ef37c6fa04a4a6cca9f90f069894ed6822b4bf","examples/compute/shaders.metallib":"fef91643e60c0ec99ad2bd2f3916299bcc3e6a80038ea27bed59681badfea7d1","examples/events/main.rs":"9cb35381b0a3918bd7d530171de8f7cceafe3d4851c0f430b4aff1f5c2aae749","examples/fence/main.rs":"47741327e62db1d8bd344b6a9ec26ef13ffb0b56b0dd7077c5d926d43faaeff7","examples/headless-render/README.md":"b1c97b52701cfb41fc0b9e269ba7a7a454d9161746198e2f5789f2636f60842d","examples/headless-render/main.rs":"814f639b0a919f3bec8e6f9f9928d881824fd0b4a6d36d32006cecbb36ae6599","examples/headless-render/screenshot.png":"01d6ea5791b63b0f01190198756446cf313fc25dc64d0138c1b4f62c9f862dd1","examples/library/main.rs":"42368ae9b630ca87ee8d8e0ced12163fdc86e6c9f2260a4b74689dbd41a60d3b","examples/mesh-shader/main.rs":"ae5182d4e3e4c4ca8ce456697291fceb53a1992ef47d5013ff6ea9de81ac7482","examples/mesh-shader/shaders.metal":"6ba934c3edd3ba0b8f6c4ac37be0fd0fe35eeef004e371521b7bf5a2fae9a223","examples/mesh-shader/shaders.metallib":"0af3b7ab0cd6186a90163550b76fab5bd2ef6ba97e791354d4281ca92d4887ff","examples/mps/main.rs":"e626ce5a4ab7e447e0e4f7b85fb27fb157afc5a0d1620cd53c248b025f7e7fb4","examples/mps/shaders.metal":"155922d6a4184078ae7ee29504a268e1218f07d908f921eef60e5bfa8a793bda","examples/mps/shaders.metallib":"b62451223549b1e7eb90ec3d3534c0ed4cdfdc581c7df3ffcdc4786a5fcacde4","examples/raytracing/README.md":"6f0d683efac74572099c317ce9f65c3e6ff3c5252c6870c0c38c67f08b37bb01","examples/raytracing/camera.rs":"11d631b13b2ba6e14e785d339919e7e13f9f381c39a78d0ab95500d551c58505","examples/raytracing/geometry.rs":"afffd828f071b4adad8d5732c23eb99ebbc4711993e3b682861e571cec2234a2","examples/raytracing/main.rs":"5298881230b17453d5cafc9f114aaa2d7d9800d85d4077a19086eedabb5c85b6","examples/raytracing/renderer.rs":"2eda59b6fdaf2e6f3d911ca21dc410f8376ca981bbd59e59085686aaba4d969a","examples/raytracing/scene.rs":"cd391661a8eea55738a9fa17f9dcd704d0f31d1bad00b7ad95445d9e9605092a","examples/raytracing/screenshot.png":"400bb138f5adb69e4db8626681fb17667e5e112c94864879d9282d5348d970db","examples/raytracing/shaders.metal":"696f6a0ba79d82e2fa0e03eadbff2f6cdeac87acc805c2b7df657b85c1173174","examples/raytracing/shaders.metallib":"249b71998f58ddf8b3de37d79e9cc1f4a3494fba4bd7ba3f5411fb603de9dd5a","examples/reflection/main.rs":"26060dd97ee2175958b89e85635d788cd5d1b496b4e1de799f9d225fab3682f2","examples/shader-dylib/main.rs":"71c8c69c5443bd2335415bc73480829a5c527d1b2736484284b553ef053af92f","examples/shader-dylib/test_dylib.metal":"3469de785c2c0da784e84758fc0da5a81e474ca15588485d9e04398690641cc8","examples/shader-dylib/test_shader.metal":"1a04ff8ab3288b09d14cd35440b2557e92ddedbff9d07c4144a22e9062e6e1e4","examples/window/README.md":"69655cff298e07887fe70e8a13e27d8a87efcd0cc0da4e15485134e064e1aceb","examples/window/main.rs":"097af824822c0724695c51fdeaca0a287f48f6426f876a57d3e97ff93be506c8","examples/window/screenshot.png":"da7369d7cc297c7f7f6bd773c93e7d708d72442c9c5ff5a20c2f2ee6852552dc","examples/window/shaders.metal":"90dee6c752add5c617dfdca93064b2824b44ff8c01ef63986826f6a1531e95d6","examples/window/shaders.metallib":"16fa82beb70bf16c3501970cae0d5028a747a08164337161dc9c2e8965d4c366","src/acceleration_structure.rs":"25533892466369deab8d9024a037c0dd05e7393ac9b3b1815a1b9f6853ace42f","src/acceleration_structure_pass.rs":"892b1fc8360f91d09bbde08251de125bea72b134c529cd3a9e6b9cdc854e403f","src/argument.rs":"6d78fcb7398a893687294bcfb3326df73a1c7292ded166a167bc677f546cc32a","src/blitpass.rs":"e558d69c1b571637007650bf4c9875a020fd6175247c6a4b203f50585d7315a9","src/buffer.rs":"78d9021ab75ef0dad09ff92d126f1ceea241cca606cd7b05553c9351458babed","src/capturedescriptor.rs":"c687c4db298fb83ef640eb34929758c2d7955174a68725e986838e367291e302","src/capturemanager.rs":"3862b2d66e91f7f803b459c1e0302ddf87e98cb5948cfad082533f0f45c33061","src/commandbuffer.rs":"79d2acf43c009fc2e4eb51e1b6b47e2dc3bafafb34b3cb88a8bf6469fbd102aa","src/commandqueue.rs":"a7d6dee5d064093521465e1da30bded851aa9d59f078942d88926d0f38de82fd","src/computepass.rs":"a6ac68f0052efc1b651c9684ed6e90fdaff89748e3d8b67707b6c23018fd8a85","src/constants.rs":"bbfeecf910d4f9ed149a7988d8a79df6e9ad479de81f7fc1294d3434a721b7fd","src/counters.rs":"d23188bde20656d2e7ecdb80c474f4ae645a88d3f1ab688c11d4918609aee26d","src/depthstencil.rs":"71f221640a2031ef40449697297f09cd42d23833854c51759b3006ba55c84de9","src/device.rs":"e17925a8f4fbd72fd4cebb9ee95f6616c59c95522e125403cc6bcd3630c71a2e","src/drawable.rs":"568c6199113724130a1eb3fc108d73735797d86542bde94500be6b5d11573b2c","src/encoder.rs":"b5f6a81353fc5696f4afa7edb8baf19be5dc65bbdbd4910062dc0a577a15e239","src/heap.rs":"f12d519c4417af4365c2d8a12f29124362785ad1b3a17ee80a7bb7f74a24a8be","src/indirect_encoder.rs":"ede986d0f4f2555413605257b58ea54b044ddbf5612f0c922b9f8b0313382d0e","src/lib.rs":"842f34ced178e5539335c947424c3408fc079071f05f27f57785a4c52ea804d5","src/library.rs":"2c21ee81a5426d8793a7bd970d371225f382e064f8c49a52aa23e2fee10b0cc0","src/mps.rs":"607b51a163577364771d8afbb95888ef3603d71be40eddab1e2cdc2ad75cbd5e","src/pipeline/compute.rs":"1d655df7ab78efe5cd8d100acfd90caf4265fc9ddaf859c6a3e9eee8190446e0","src/pipeline/mod.rs":"280916e71b6f3c5fffda6ffee31c616bf4aba6e5a186401ea2febe71c3264145","src/pipeline/render.rs":"8bbe04b5ace6adeb2a2df06d00b3003d32cb5085706e33be31108821acdd67f4","src/renderpass.rs":"03b358a21d1b2399c1a682187a67193e1a25b69999a33e1b128db0c09fa7860b","src/resource.rs":"f239254592d3e14ab9a418105cd51fe923be385cd6983c7fb9cd81b5f5eb90ef","src/sampler.rs":"b82205283e3ba4ff229f6b4fb2e73ac5da9cdc75312a76e5d7e40d9ebf0d051c","src/sync.rs":"8fbff195456c6410d1e787b9b93379dacbdc9ae3e331bc07a9dc3522f1f65c45","src/texture.rs":"8fe3245d1b9b53c09de2eff0d015afcafd6b1bc9e8fec2ddab78f060d418eff1","src/types.rs":"d255f9c1b449acdb971616255e1c98d35b3b1ac54d9c388f7cdff6cfc3a8b944","src/vertexdescriptor.rs":"6a1378f270f7adf631319bcc8c8d6831c9f9be55e7b39a7ccfe151af9a9363c4"},"package":"00c15a6f673ff72ddcc22394663290f870fb224c1bfce55734a75c414150e605"} -\ No newline at end of file +{"files":{"Cargo.lock":"341fa20998f08b817968a85540ce7d81bd142384432c3e2bc1b0c3a699d71e95","Cargo.toml":"620b270dfa7965d4203977b073b3c724409c1a840b0c4143472e4a1d171f688a","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"0621878e61f0d0fda054bcbe02df75192c28bde1ecc8289cbd86aeba2dd72720","Makefile":"99c4c592467981921d1257a4e9e841dce53624448738d0ed52a75431a406f7bc","README.md":"0abbe98a0b7460179c0cfe507e99aed7e0be070473b3d3140d886a519519f73f","assets/metal.svg":"3a295bd130785bc2fea2f3a91a4f6c7b4b3c40682c3fcb7a75f9887b66b06264","bors.toml":"c2733ec512a08bf76b6a1ed77270810a0edeb888ba24c2a75679e1eb1bd563a5","examples/argument-buffer/main.rs":"a087db6648a4b092520de29616521c704d892e0d3ace935d16f3f339415c4361","examples/bind/main.rs":"a0c85aad05f08666f9b380a7146a8473a6a6fe0db5d523760373093a0af20e5f","examples/bindless/main.rs":"020fdc78a4cf9196dd80c333d0ddb2569202b573b90ad5df2081d4ff88318372","examples/caps/main.rs":"b7be00c1cc9042140d34ea05051152a7035f316f0bdcd31ac5a660a97e0c4f70","examples/circle/README.md":"e1c97cf5252f0d1f2934ace78b5d839c5f45911f3007dbd2925eeceefb8f0af6","examples/circle/main.rs":"8690a17b2c59293bd774f7945f386246b4154a67283d2052d4fbb6d3cb74bf76","examples/circle/screenshot.png":"97bf07c85bf02367447b9c8a81707c124e4a3b420fa386b95ba08b21938f4f2a","examples/circle/shaders.metal":"5e4f40efca5bb386204a09e1b983cc6c634fdf1ca9dd4974227313adbf50e8b5","examples/circle/shaders.metallib":"666a9491d795ef9c0b9c122c7ada571cc2c0e8774d2d89e5b4b996f3dc47962b","examples/compute/compute-argument-buffer.metal":"6530bbd6a0101d9db2893805436f5dc877959e81ea97a27014c0fc52fc9fa77b","examples/compute/compute-argument-buffer.rs":"8c02647426cda46d2687890eb64a833bf5bcf4740109fb871f56d4aea6984bcf","examples/compute/embedded-lib.rs":"55f701810fa5270c27ca771e713f9f8cf09e124a997b0b03790b38435593a7ea","examples/compute/main.rs":"a9eee4e600887f2dafdd953a899cf1d11ea5fc252297cf5f9747f209454ba173","examples/compute/shaders.metal":"f2b15551bb5247b88a3029c3d8ef37c6fa04a4a6cca9f90f069894ed6822b4bf","examples/compute/shaders.metallib":"fef91643e60c0ec99ad2bd2f3916299bcc3e6a80038ea27bed59681badfea7d1","examples/events/main.rs":"9cb35381b0a3918bd7d530171de8f7cceafe3d4851c0f430b4aff1f5c2aae749","examples/fence/main.rs":"47741327e62db1d8bd344b6a9ec26ef13ffb0b56b0dd7077c5d926d43faaeff7","examples/headless-render/README.md":"b1c97b52701cfb41fc0b9e269ba7a7a454d9161746198e2f5789f2636f60842d","examples/headless-render/main.rs":"814f639b0a919f3bec8e6f9f9928d881824fd0b4a6d36d32006cecbb36ae6599","examples/headless-render/screenshot.png":"01d6ea5791b63b0f01190198756446cf313fc25dc64d0138c1b4f62c9f862dd1","examples/library/main.rs":"42368ae9b630ca87ee8d8e0ced12163fdc86e6c9f2260a4b74689dbd41a60d3b","examples/mesh-shader/main.rs":"bc7ca72445565015a51a94d3943a622617d13c8982a47224529fe4eed021e37d","examples/mesh-shader/shaders.metal":"6ba934c3edd3ba0b8f6c4ac37be0fd0fe35eeef004e371521b7bf5a2fae9a223","examples/mesh-shader/shaders.metallib":"0af3b7ab0cd6186a90163550b76fab5bd2ef6ba97e791354d4281ca92d4887ff","examples/mps/main.rs":"e626ce5a4ab7e447e0e4f7b85fb27fb157afc5a0d1620cd53c248b025f7e7fb4","examples/mps/shaders.metal":"155922d6a4184078ae7ee29504a268e1218f07d908f921eef60e5bfa8a793bda","examples/mps/shaders.metallib":"b62451223549b1e7eb90ec3d3534c0ed4cdfdc581c7df3ffcdc4786a5fcacde4","examples/raytracing/README.md":"6f0d683efac74572099c317ce9f65c3e6ff3c5252c6870c0c38c67f08b37bb01","examples/raytracing/camera.rs":"11d631b13b2ba6e14e785d339919e7e13f9f381c39a78d0ab95500d551c58505","examples/raytracing/geometry.rs":"afffd828f071b4adad8d5732c23eb99ebbc4711993e3b682861e571cec2234a2","examples/raytracing/main.rs":"51bde3c7e81b18a1914322cc71ed5701289873fac44a88e29c2ae7939dc2d0b4","examples/raytracing/renderer.rs":"590fa078d377cdf5eedec30447f117b5c6c717478fd36b3afe5991cd7d142e25","examples/raytracing/scene.rs":"40fb7b0a9cb68a00cf7d6c9a7f0f75f2175808ac6cb7ead1128ae822a35629ce","examples/raytracing/screenshot.png":"400bb138f5adb69e4db8626681fb17667e5e112c94864879d9282d5348d970db","examples/raytracing/shaders.metal":"696f6a0ba79d82e2fa0e03eadbff2f6cdeac87acc805c2b7df657b85c1173174","examples/raytracing/shaders.metallib":"249b71998f58ddf8b3de37d79e9cc1f4a3494fba4bd7ba3f5411fb603de9dd5a","examples/reflection/main.rs":"26060dd97ee2175958b89e85635d788cd5d1b496b4e1de799f9d225fab3682f2","examples/shader-dylib/main.rs":"bf91ea8c14114a832ab5d45cd4658def678b1c296aefa7841b1d217ee4211212","examples/shader-dylib/test_dylib.metal":"3469de785c2c0da784e84758fc0da5a81e474ca15588485d9e04398690641cc8","examples/shader-dylib/test_shader.metal":"1a04ff8ab3288b09d14cd35440b2557e92ddedbff9d07c4144a22e9062e6e1e4","examples/window/README.md":"69655cff298e07887fe70e8a13e27d8a87efcd0cc0da4e15485134e064e1aceb","examples/window/main.rs":"f562ad4d0c239552f61b8b1cc3d3b8c14d8ff40684e2fa5b1a6004691949a55c","examples/window/screenshot.png":"da7369d7cc297c7f7f6bd773c93e7d708d72442c9c5ff5a20c2f2ee6852552dc","examples/window/shaders.metal":"90dee6c752add5c617dfdca93064b2824b44ff8c01ef63986826f6a1531e95d6","examples/window/shaders.metallib":"16fa82beb70bf16c3501970cae0d5028a747a08164337161dc9c2e8965d4c366","renovate.json":"c9bcf4274fc54533cf2143b1076ad7089b973b9d28433499da682daec1f0d7b0","src/acceleration_structure.rs":"25533892466369deab8d9024a037c0dd05e7393ac9b3b1815a1b9f6853ace42f","src/acceleration_structure_pass.rs":"892b1fc8360f91d09bbde08251de125bea72b134c529cd3a9e6b9cdc854e403f","src/argument.rs":"6d78fcb7398a893687294bcfb3326df73a1c7292ded166a167bc677f546cc32a","src/blitpass.rs":"e558d69c1b571637007650bf4c9875a020fd6175247c6a4b203f50585d7315a9","src/buffer.rs":"71be298ae404bc872086f834405c79f01669f4ca405ac5dd461039257c023f25","src/capturedescriptor.rs":"c687c4db298fb83ef640eb34929758c2d7955174a68725e986838e367291e302","src/capturemanager.rs":"3862b2d66e91f7f803b459c1e0302ddf87e98cb5948cfad082533f0f45c33061","src/commandbuffer.rs":"1e36a3878ecd8c48180e4a0887e1d5067bb8f4cb8b65140a2a38ea01f36f38ba","src/commandqueue.rs":"a7d6dee5d064093521465e1da30bded851aa9d59f078942d88926d0f38de82fd","src/computepass.rs":"a6ac68f0052efc1b651c9684ed6e90fdaff89748e3d8b67707b6c23018fd8a85","src/constants.rs":"bbfeecf910d4f9ed149a7988d8a79df6e9ad479de81f7fc1294d3434a721b7fd","src/counters.rs":"d23188bde20656d2e7ecdb80c474f4ae645a88d3f1ab688c11d4918609aee26d","src/depthstencil.rs":"71f221640a2031ef40449697297f09cd42d23833854c51759b3006ba55c84de9","src/device.rs":"da9b60f6f5d162dd1b1c1595cdc3fffc9b8be5b82e8a3fd9fe51dc41455909d6","src/drawable.rs":"568c6199113724130a1eb3fc108d73735797d86542bde94500be6b5d11573b2c","src/encoder.rs":"8eb26752a2851816097b9615e5f71b37bcfea74f7397c11c9483d9576cbf904f","src/heap.rs":"f12d519c4417af4365c2d8a12f29124362785ad1b3a17ee80a7bb7f74a24a8be","src/indirect_encoder.rs":"ede986d0f4f2555413605257b58ea54b044ddbf5612f0c922b9f8b0313382d0e","src/lib.rs":"7d183d08f53e5dd2970f5afee3195b4c75cefb7f000dce5ed74032373f2df503","src/library.rs":"934eb6c737dc1f40e13458ce205878322e416c0a56d033c6308ca7a33f892351","src/mps.rs":"607b51a163577364771d8afbb95888ef3603d71be40eddab1e2cdc2ad75cbd5e","src/pipeline/compute.rs":"1d655df7ab78efe5cd8d100acfd90caf4265fc9ddaf859c6a3e9eee8190446e0","src/pipeline/mod.rs":"280916e71b6f3c5fffda6ffee31c616bf4aba6e5a186401ea2febe71c3264145","src/pipeline/render.rs":"8bbe04b5ace6adeb2a2df06d00b3003d32cb5085706e33be31108821acdd67f4","src/renderpass.rs":"03b358a21d1b2399c1a682187a67193e1a25b69999a33e1b128db0c09fa7860b","src/resource.rs":"f239254592d3e14ab9a418105cd51fe923be385cd6983c7fb9cd81b5f5eb90ef","src/sampler.rs":"b82205283e3ba4ff229f6b4fb2e73ac5da9cdc75312a76e5d7e40d9ebf0d051c","src/sync.rs":"8fbff195456c6410d1e787b9b93379dacbdc9ae3e331bc07a9dc3522f1f65c45","src/texture.rs":"8fe3245d1b9b53c09de2eff0d015afcafd6b1bc9e8fec2ddab78f060d418eff1","src/types.rs":"d255f9c1b449acdb971616255e1c98d35b3b1ac54d9c388f7cdff6cfc3a8b944","src/vertexdescriptor.rs":"6a1378f270f7adf631319bcc8c8d6831c9f9be55e7b39a7ccfe151af9a9363c4"},"package":"c7047791b5bc903b8cd963014b355f71dc9864a9a0b727057676c1dcae5cbc15"} +\ No newline at end of file diff --git a/third_party/rust/metal/Cargo.lock b/third_party/rust/metal/Cargo.lock @@ -1,12 +1,12 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 4 +version = 3 [[package]] name = "ab_glyph" -version = "0.2.29" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0" +checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", @@ -14,21 +14,21 @@ dependencies = [ [[package]] name = "ab_glyph_rasterizer" -version = "0.1.8" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" +checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", "getrandom", @@ -44,12 +44,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee91c0c2905bae44f84bfa4e044536541df26b7703fd0888deeb9060fcc44289" dependencies = [ "android-properties", - "bitflags 2.8.0", + "bitflags 2.10.0", "cc", "cesu8", "jni", "jni-sys", - "libc 0.2.169", + "libc 0.2.177", "log", "ndk", "ndk-context", @@ -89,12 +89,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -102,9 +96,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.8.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "block" @@ -133,27 +127,21 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytemuck" -version = "1.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" - -[[package]] -name = "byteorder" -version = "1.5.0" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" [[package]] name = "bytes" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "calloop" @@ -161,10 +149,10 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.10.0", "log", "polling", - "rustix", + "rustix 0.38.44", "slab", "thiserror", ] @@ -176,19 +164,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02" dependencies = [ "calloop", - "rustix", + "rustix 0.38.44", "wayland-backend", "wayland-client", ] [[package]] name = "cc" -version = "1.2.9" +version = "1.2.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8293772165d9345bdaaa39b45b2109591e63fe5e6fbc23c6ff930a048aa310b" +checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" dependencies = [ + "find-msvc-tools", "jobserver", - "libc 0.2.169", + "libc 0.2.177", "shlex", ] @@ -200,9 +189,9 @@ checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -212,31 +201,30 @@ checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" [[package]] name = "cocoa" -version = "0.25.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" +checksum = "ad36507aeb7e16159dfe68db81ccc27571c3ccd4b76fb2fb72fc59e7a4b1b64c" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.10.0", "block", "cocoa-foundation", - "core-foundation 0.9.4", - "core-graphics", + "core-foundation 0.10.1", + "core-graphics 0.24.0", "foreign-types", - "libc 0.2.169", + "libc 0.2.177", "objc", ] [[package]] name = "cocoa-foundation" -version = "0.1.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" +checksum = "81411967c50ee9a1fc11365f8c585f863a22a9697c89239c452292c40ba79b0d" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.10.0", "block", - "core-foundation 0.9.4", - "core-graphics-types 0.1.3", - "libc 0.2.169", + "core-foundation 0.10.1", + "core-graphics-types 0.2.0", "objc", ] @@ -266,17 +254,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", - "libc 0.2.169", + "libc 0.2.177", ] [[package]] name = "core-foundation" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ "core-foundation-sys", - "libc 0.2.169", + "libc 0.2.177", ] [[package]] @@ -295,7 +283,20 @@ dependencies = [ "core-foundation 0.9.4", "core-graphics-types 0.1.3", "foreign-types", - "libc 0.2.169", + "libc 0.2.177", +] + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.10.1", + "core-graphics-types 0.2.0", + "foreign-types", + "libc 0.2.177", ] [[package]] @@ -306,7 +307,7 @@ checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", - "libc 0.2.169", + "libc 0.2.177", ] [[package]] @@ -315,16 +316,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "bitflags 2.8.0", - "core-foundation 0.10.0", - "libc 0.2.169", + "bitflags 2.10.0", + "core-foundation 0.10.1", + "libc 0.2.177", ] [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] @@ -337,9 +338,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "cursor-icon" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" [[package]] name = "dispatch" @@ -364,18 +365,18 @@ checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.10" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ - "libc 0.2.169", - "windows-sys 0.59.0", + "libc 0.2.177", + "windows-sys 0.61.2", ] [[package]] @@ -388,10 +389,16 @@ dependencies = [ ] [[package]] +name = "find-msvc-tools" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" + +[[package]] name = "flate2" -version = "1.0.35" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" dependencies = [ "crc32fast", "miniz_oxide", @@ -432,42 +439,43 @@ checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" [[package]] name = "gethostname" -version = "0.4.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" dependencies = [ - "libc 0.2.169", - "windows-targets 0.48.5", + "rustix 1.1.2", + "windows-link", ] [[package]] name = "getrandom" -version = "0.2.15" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", - "libc 0.2.169", - "wasi 0.11.0+wasi-snapshot-preview1", + "libc 0.2.177", + "r-efi", + "wasip2", ] [[package]] name = "glam" -version = "0.27.0" +version = "0.30.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e05e7e6723e3455f4818c7b26e855439f7546cf617ef669d1adedb8669e5cb9" +checksum = "bd47b05dddf0005d850e5644cae7f2b14ac3df487979dbfff3b56f20b1a6ae46" [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" [[package]] name = "hermit-abi" -version = "0.4.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "icrate" @@ -482,9 +490,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", "hashbrown", @@ -514,18 +522,19 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "libc 0.2.169", + "getrandom", + "libc 0.2.177", ] [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" dependencies = [ "once_cell", "wasm-bindgen", @@ -539,29 +548,29 @@ checksum = "e32a70cf75e5846d53a673923498228bbec6a8624708a9ea5645f075d6276122" [[package]] name = "libc" -version = "0.2.169" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libloading" -version = "0.8.6" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-link", ] [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ - "bitflags 2.8.0", - "libc 0.2.169", - "redox_syscall 0.5.8", + "bitflags 2.10.0", + "libc 0.2.177", + "redox_syscall 0.5.18", ] [[package]] @@ -571,10 +580,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] name = "log" -version = "0.4.25" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "malloc_buf" @@ -582,29 +597,29 @@ version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" dependencies = [ - "libc 0.2.169", + "libc 0.2.177", ] [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memmap2" -version = "0.9.5" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" dependencies = [ - "libc 0.2.169", + "libc 0.2.177", ] [[package]] name = "metal" -version = "0.32.0" +version = "0.33.0" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.10.0", "block", "cocoa", "core-graphics-types 0.2.0", @@ -615,16 +630,16 @@ dependencies = [ "objc", "paste", "png", - "rand 0.8.5", + "rand 0.9.2", "sema", "winit", ] [[package]] name = "miniz_oxide" -version = "0.8.3" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", "simd-adler32", @@ -636,7 +651,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.10.0", "jni-sys", "log", "ndk-sys", @@ -662,18 +677,19 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.3" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" dependencies = [ "num_enum_derive", + "rustversion", ] [[package]] name = "num_enum_derive" -version = "0.7.3" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -714,9 +730,9 @@ checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666" [[package]] name = "once_cell" -version = "1.20.2" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "orbclient" @@ -729,9 +745,9 @@ dependencies = [ [[package]] name = "owned_ttf_parser" -version = "0.25.0" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4" +checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" dependencies = [ "ttf-parser", ] @@ -744,9 +760,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project-lite" @@ -756,17 +772,17 @@ checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "png" -version = "0.17.16" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.10.0", "crc32fast", "fdeflate", "flate2", @@ -775,71 +791,76 @@ dependencies = [ [[package]] name = "polling" -version = "3.7.4" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix", - "tracing", - "windows-sys 0.59.0", + "rustix 1.1.2", + "windows-sys 0.61.2", ] [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] [[package]] name = "proc-macro-crate" -version = "3.2.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "quick-xml" -version = "0.36.2" +version = "0.37.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" dependencies = [ "memchr", ] [[package]] name = "quote" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] [[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] name = "rand" version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" dependencies = [ - "libc 0.2.169", + "libc 0.2.177", "rand 0.4.6", ] @@ -850,7 +871,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" dependencies = [ "fuchsia-cprng", - "libc 0.2.169", + "libc 0.2.177", "rand_core 0.3.1", "rdrand", "winapi", @@ -858,23 +879,22 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.5" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ - "libc 0.2.169", "rand_chacha", - "rand_core 0.6.4", + "rand_core 0.9.3", ] [[package]] name = "rand_chacha" -version = "0.3.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", + "rand_core 0.9.3", ] [[package]] @@ -894,9 +914,9 @@ checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" [[package]] name = "rand_core" -version = "0.6.4" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ "getrandom", ] @@ -927,31 +947,44 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.8" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.10.0", ] [[package]] name = "rustix" -version = "0.38.43" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.10.0", "errno", - "libc 0.2.169", - "linux-raw-sys", + "libc 0.2.177", + "linux-raw-sys 0.4.15", "windows-sys 0.59.0", ] [[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc 0.2.177", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.2", +] + +[[package]] name = "rustversion" -version = "1.0.19" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "same-file" @@ -994,18 +1027,27 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.217" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -1026,18 +1068,15 @@ checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "slab" -version = "0.4.9" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" -version = "1.13.2" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "smithay-client-toolkit" @@ -1045,14 +1084,14 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "922fd3eeab3bd820d76537ce8f582b1cf951eceb5475c28500c7457d9d17f53a" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.10.0", "calloop", "calloop-wayland-source", "cursor-icon", - "libc 0.2.169", + "libc 0.2.177", "log", "memmap2", - "rustix", + "rustix 0.38.44", "thiserror", "wayland-backend", "wayland-client", @@ -1081,9 +1120,9 @@ checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" [[package]] name = "syn" -version = "2.0.96" +version = "2.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" dependencies = [ "proc-macro2", "quote", @@ -1116,8 +1155,8 @@ version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" dependencies = [ - "libc 0.2.169", - "wasi 0.10.0+wasi-snapshot-preview1", + "libc 0.2.177", + "wasi", "winapi", ] @@ -1148,38 +1187,35 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +dependencies = [ + "serde_core", +] [[package]] name = "toml_edit" -version = "0.22.22" +version = "0.23.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ "indexmap", "toml_datetime", + "toml_parser", "winnow", ] [[package]] -name = "tracing" -version = "0.1.41" +name = "toml_parser" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" dependencies = [ - "pin-project-lite", - "tracing-core", + "winnow", ] [[package]] -name = "tracing-core" -version = "0.1.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" - -[[package]] name = "ttf-parser" version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1187,9 +1223,9 @@ checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-segmentation" @@ -1220,42 +1256,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" dependencies = [ "cfg-if", "js-sys", @@ -1266,9 +1292,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1276,35 +1302,35 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" dependencies = [ "unicode-ident", ] [[package]] name = "wayland-backend" -version = "0.3.7" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6" +checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" dependencies = [ "cc", "downcast-rs", - "rustix", + "rustix 1.1.2", "scoped-tls", "smallvec", "wayland-sys", @@ -1312,12 +1338,12 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.7" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66249d3fc69f76fd74c82cc319300faa554e9d865dab1f7cd66cc20db10b280" +checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" dependencies = [ - "bitflags 2.8.0", - "rustix", + "bitflags 2.10.0", + "rustix 1.1.2", "wayland-backend", "wayland-scanner", ] @@ -1328,18 +1354,18 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.10.0", "cursor-icon", "wayland-backend", ] [[package]] name = "wayland-cursor" -version = "0.31.7" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b08bc3aafdb0035e7fe0fdf17ba0c09c268732707dca4ae098f60cb28c9e4c" +checksum = "447ccc440a881271b19e9989f75726d60faa09b95b0200a9b7eb5cc47c3eeb29" dependencies = [ - "rustix", + "rustix 1.1.2", "wayland-client", "xcursor", ] @@ -1350,7 +1376,7 @@ version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.10.0", "wayland-backend", "wayland-client", "wayland-scanner", @@ -1362,7 +1388,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.10.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -1375,7 +1401,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.10.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -1384,9 +1410,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.5" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3" +checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3" dependencies = [ "proc-macro2", "quick-xml", @@ -1395,9 +1421,9 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.31.5" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efa8ac0d8e8ed3e3b5c9fc92c7881406a268e11555abe36493efabe649a29e09" +checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142" dependencies = [ "dlib", "log", @@ -1407,9 +1433,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" dependencies = [ "js-sys", "wasm-bindgen", @@ -1443,11 +1469,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1457,6 +1483,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] name = "windows-sys" version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1484,6 +1516,15 @@ dependencies = [ ] [[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] name = "windows-targets" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1670,16 +1711,16 @@ dependencies = [ "ahash", "android-activity", "atomic-waker", - "bitflags 2.8.0", + "bitflags 2.10.0", "bytemuck", "calloop", "cfg_aliases", "core-foundation 0.9.4", - "core-graphics", + "core-graphics 0.23.2", "cursor-icon", "icrate", "js-sys", - "libc 0.2.169", + "libc 0.2.177", "log", "memmap2", "ndk", @@ -1690,7 +1731,7 @@ dependencies = [ "percent-encoding", "raw-window-handle", "redox_syscall 0.3.5", - "rustix", + "rustix 0.38.44", "sctk-adwaita", "smithay-client-toolkit", "smol_str", @@ -1711,50 +1752,56 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.24" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] [[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] name = "x11-dl" version = "2.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" dependencies = [ - "libc 0.2.169", + "libc 0.2.177", "once_cell", "pkg-config", ] [[package]] name = "x11rb" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" dependencies = [ "as-raw-xcb-connection", "gethostname", - "libc 0.2.169", + "libc 0.2.177", "libloading", "once_cell", - "rustix", + "rustix 1.1.2", "x11rb-protocol", ] [[package]] name = "x11rb-protocol" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" [[package]] name = "xcursor" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" +checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" [[package]] name = "xkbcommon-dl" @@ -1762,7 +1809,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.10.0", "dlib", "log", "once_cell", @@ -1777,19 +1824,18 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ - "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", diff --git a/third_party/rust/metal/Cargo.toml b/third_party/rust/metal/Cargo.toml @@ -13,7 +13,7 @@ edition = "2021" rust-version = "1.82" name = "metal" -version = "0.32.0" +version = "0.33.0" authors = ["gfx-rs developers"] build = false exclude = [ @@ -49,6 +49,13 @@ targets = [ "x86_64-apple-ios", ] +[features] +cargo-clippy = [] +default = ["link"] +link = ["core-graphics-types/link"] +mps = [] +private = [] + [lib] name = "metal" path = "src/lib.rs" @@ -154,26 +161,19 @@ version = "0.2.4" version = "1" [dev-dependencies.cocoa] -version = "0.25.0" +version = "0.26.0" [dev-dependencies.glam] -version = "0.27" +version = "0.30" [dev-dependencies.png] -version = "0.17" +version = "0.18" [dev-dependencies.rand] -version = "0.8" +version = "0.9" [dev-dependencies.sema] version = "0.1.4" [dev-dependencies.winit] version = "0.29" - -[features] -cargo-clippy = [] -default = ["link"] -link = ["core-graphics-types/link"] -mps = [] -private = [] diff --git a/third_party/rust/metal/README.md b/third_party/rust/metal/README.md @@ -1,4 +1,20 @@ # metal-rs + +> [!WARNING] +> +> Use of this crate is deprecated as the [`objc`] ecosystem of mac system bindings are unmaintained. +> For new development, please use [`objc2`] and [`objc2-metal`] instead. We will continue to merge basic +> PRs and keep things maintained, at least as long as it takes to migrate [`wgpu`] to the `objc2` ecosystem [PR 5641]. + +[`objc`]: https://crates.io/crates/objc +[`objc2`]: https://crates.io/crates/objc2 +[`objc2-metal`]: https://crates.io/crates/objc2-metal +[`wgpu`]: https://crates.io/crates/wgpu +[PR 5641]: https://github.com/gfx-rs/wgpu/pull/5641 + +----- + + [](https://github.com/gfx-rs/metal-rs/actions) [](https://crates.io/crates/metal) diff --git a/third_party/rust/metal/examples/circle/main.rs b/third_party/rust/metal/examples/circle/main.rs @@ -6,6 +6,7 @@ use winit::{ raw_window_handle::{HasWindowHandle, RawWindowHandle}, }; +#[allow(deprecated)] use cocoa::{appkit::NSView, base::id as cocoa_id}; use core_graphics_types::geometry::CGSize; @@ -97,6 +98,7 @@ fn main() { layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm); layer.set_presents_with_transaction(false); + #[allow(deprecated)] unsafe { if let Ok(RawWindowHandle::AppKit(rw)) = window.window_handle().map(|wh| wh.as_raw()) { let view = rw.ns_view.as_ptr() as cocoa_id; diff --git a/third_party/rust/metal/examples/mesh-shader/main.rs b/third_party/rust/metal/examples/mesh-shader/main.rs @@ -1,5 +1,6 @@ extern crate objc; +#[allow(deprecated)] use cocoa::{appkit::NSView, base::id as cocoa_id}; use core_graphics_types::geometry::CGSize; @@ -38,6 +39,7 @@ fn main() { layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm); layer.set_presents_with_transaction(false); + #[allow(deprecated)] unsafe { if let Ok(RawWindowHandle::AppKit(rw)) = window.window_handle().map(|wh| wh.as_raw()) { let view = rw.ns_view.as_ptr() as cocoa_id; diff --git a/third_party/rust/metal/examples/raytracing/main.rs b/third_party/rust/metal/examples/raytracing/main.rs @@ -1,5 +1,6 @@ extern crate objc; +#[allow(deprecated)] use cocoa::{appkit::NSView, base::id as cocoa_id}; use core_graphics_types::geometry::CGSize; use metal::*; @@ -46,6 +47,7 @@ fn main() { layer.set_pixel_format(MTLPixelFormat::RGBA16Float); layer.set_presents_with_transaction(false); + #[allow(deprecated)] unsafe { if let Ok(RawWindowHandle::AppKit(rw)) = window.window_handle().map(|wh| wh.as_raw()) { let view = rw.ns_view.as_ptr() as cocoa_id; diff --git a/third_party/rust/metal/examples/raytracing/renderer.rs b/third_party/rust/metal/examples/raytracing/renderer.rs @@ -7,7 +7,7 @@ use std::{ use core_graphics_types::{base::CGFloat, geometry::CGSize}; use glam::{Vec3, Vec4, Vec4Swizzles}; use metal::{foreign_types::ForeignType, *}; -use rand::{thread_rng, RngCore}; +use rand::RngCore; use crate::{camera::Camera, geometry::get_managed_buffer_storage_mode, scene::Scene}; @@ -294,7 +294,7 @@ impl Renderer { texture_descriptor.set_usage(MTLTextureUsage::ShaderRead); texture_descriptor.set_storage_mode(MTLStorageMode::Managed); self.random_texture = self.device.new_texture(&texture_descriptor); - let mut rng = thread_rng(); + let mut rng = rand::rng(); let mut random_values = vec![0u32; (size.width * size.height) as usize]; for v in &mut random_values { *v = rng.next_u32(); diff --git a/third_party/rust/metal/examples/raytracing/scene.rs b/third_party/rust/metal/examples/raytracing/scene.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use glam::{Mat4, Vec3, Vec4}; -use rand::{thread_rng, Rng}; +use rand::Rng; use metal::{Buffer, Device, NSRange, NSUInteger}; @@ -76,7 +76,7 @@ impl Scene { let sphere_geometry = Arc::new(sphere_geometry); geometries.push(sphere_geometry.clone()); - let mut rng = thread_rng(); + let mut rng = rand::rng(); let mut geometry_instances = Vec::new(); let mut lights = Vec::new(); for y in -1..2 { @@ -107,9 +107,9 @@ impl Scene { right: Vec4::new(0.25, 0.0, 0.0, 0.0), up: Vec4::new(0.0, 0.0, 0.25, 0.0), colour: Vec4::new( - rng.gen_range(0f32..=1.0), - rng.gen_range(0f32..=1.0), - rng.gen_range(0f32..=1.0), + rng.random_range(0f32..=1.0), + rng.random_range(0f32..=1.0), + rng.random_range(0f32..=1.0), 0.0, ), }); diff --git a/third_party/rust/metal/examples/shader-dylib/main.rs b/third_party/rust/metal/examples/shader-dylib/main.rs @@ -1,3 +1,4 @@ +#[allow(deprecated)] use cocoa::{appkit::NSView, base::id as cocoa_id}; use core_graphics_types::geometry::CGSize; @@ -34,6 +35,7 @@ impl App { layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm); layer.set_presents_with_transaction(false); layer.set_framebuffer_only(false); + #[allow(deprecated)] unsafe { if let Ok(RawWindowHandle::AppKit(rw)) = window.window_handle().map(|wh| wh.as_raw()) { let view = rw.ns_view.as_ptr() as cocoa_id; diff --git a/third_party/rust/metal/examples/window/main.rs b/third_party/rust/metal/examples/window/main.rs @@ -7,6 +7,7 @@ extern crate objc; +#[allow(deprecated)] use cocoa::{appkit::NSView, base::id as cocoa_id}; use core_graphics_types::geometry::CGSize; use metal::*; @@ -99,6 +100,7 @@ fn main() { layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm); layer.set_presents_with_transaction(false); + #[allow(deprecated)] unsafe { if let Ok(RawWindowHandle::AppKit(rw)) = window.window_handle().map(|wh| wh.as_raw()) { let view = rw.ns_view.as_ptr() as cocoa_id; diff --git a/third_party/rust/metal/renovate.json b/third_party/rust/metal/renovate.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:recommended", "schedule:weekly"], + "dependencyDashboard": true, + "prConcurrentLimit": 20, + "prHourlyLimit": 200, + "labels": ["dependencies"], + "packageRules": [ + { + "matchUpdateTypes": ["patch"], + "matchCurrentVersion": "<1.0.0", + "groupName": "Minor Updates", + "description": "Patch updates to 0.x.y crates are treated as compatible by cargo" + }, + { + "matchUpdateTypes": ["minor", "patch"], + "matchCurrentVersion": ">=1.0.0", + "groupName": "Minor Updates", + "description": "Minor and patch updates to x.y.z crates are treated as compatible by cargo" + } + ] +} diff --git a/third_party/rust/metal/src/buffer.rs b/third_party/rust/metal/src/buffer.rs @@ -68,4 +68,8 @@ impl BufferRef { pub fn gpu_address(&self) -> u64 { unsafe { msg_send![self, gpuAddress] } } + + pub fn gpu_resource_id(&self) -> MTLResourceID { + unsafe { msg_send![self, gpuResourceID] } + } } diff --git a/third_party/rust/metal/src/commandbuffer.rs b/third_party/rust/metal/src/commandbuffer.rs @@ -105,6 +105,11 @@ impl CommandBufferRef { unsafe { msg_send![self, addScheduledHandler: block] } } + /// Create a blit command encoder. + /// + /// Although this method is named with a `new_` prefix, the actual Metal + /// method is not, and it returns an object that has been added to the + /// autorelease pool. See <https://github.com/gfx-rs/metal-rs/issues/128>. pub fn new_blit_command_encoder(&self) -> &BlitCommandEncoderRef { unsafe { msg_send![self, blitCommandEncoder] } } @@ -116,6 +121,11 @@ impl CommandBufferRef { unsafe { msg_send![self, blitCommandEncoderWithDescriptor: descriptor] } } + /// Create a compute command encoder. + /// + /// Although this method is named with a `new_` prefix, the actual Metal + /// method is not, and it returns an object that has been added to the + /// autorelease pool. See <https://github.com/gfx-rs/metal-rs/issues/128>. pub fn new_compute_command_encoder(&self) -> &ComputeCommandEncoderRef { unsafe { msg_send![self, computeCommandEncoder] } } @@ -134,6 +144,11 @@ impl CommandBufferRef { unsafe { msg_send![self, computeCommandEncoderWithDescriptor: descriptor] } } + /// Create a render command encoder. + /// + /// Although this method is named with a `new_` prefix, the actual Metal + /// method is not, and it returns an object that has been added to the + /// autorelease pool. See <https://github.com/gfx-rs/metal-rs/issues/128>. pub fn new_render_command_encoder( &self, descriptor: &RenderPassDescriptorRef, @@ -141,6 +156,11 @@ impl CommandBufferRef { unsafe { msg_send![self, renderCommandEncoderWithDescriptor: descriptor] } } + /// Create a parallel render command encoder. + /// + /// Although this method is named with a `new_` prefix, the actual Metal + /// method is not, and it returns an object that has been added to the + /// autorelease pool. See <https://github.com/gfx-rs/metal-rs/issues/128>. pub fn new_parallel_render_command_encoder( &self, descriptor: &RenderPassDescriptorRef, @@ -148,12 +168,22 @@ impl CommandBufferRef { unsafe { msg_send![self, parallelRenderCommandEncoderWithDescriptor: descriptor] } } + /// Create an acceleration structure command encoder. + /// + /// Although this method is named with a `new_` prefix, the actual Metal + /// method is not, and it returns an object that has been added to the + /// autorelease pool. See <https://github.com/gfx-rs/metal-rs/issues/128>. pub fn new_acceleration_structure_command_encoder( &self, ) -> &AccelerationStructureCommandEncoderRef { unsafe { msg_send![self, accelerationStructureCommandEncoder] } } + /// Create an acceleration structure command encoder. + /// + /// Although this method is named with a `new_` prefix, the actual Metal + /// method is not, and it returns an object that has been added to the + /// autorelease pool. See <https://github.com/gfx-rs/metal-rs/issues/128>. pub fn acceleration_structure_command_encoder_with_descriptor( &self, descriptor: &AccelerationStructurePassDescriptorRef, diff --git a/third_party/rust/metal/src/device.rs b/third_party/rust/metal/src/device.rs @@ -85,6 +85,7 @@ pub enum MTLGPUFamily { MacCatalyst1 = 4001, MacCatalyst2 = 4002, Metal3 = 5001, + Metal4 = 5002, } /// See <https://developer.apple.com/documentation/metal/mtldevicelocation> @@ -1610,7 +1611,7 @@ impl DeviceRef { } pub fn has_unified_memory(&self) -> bool { - unsafe { msg_send![self, hasUnifiedMemory] } + unsafe { msg_send_bool![self, hasUnifiedMemory] } } pub fn recommended_max_working_set_size(&self) -> u64 { diff --git a/third_party/rust/metal/src/encoder.rs b/third_party/rust/metal/src/encoder.rs @@ -223,6 +223,10 @@ impl RenderCommandEncoderRef { unsafe { msg_send![self, setViewport: viewport] } } + pub fn set_viewports(&self, viewports: &[MTLViewport]) { + unsafe { msg_send![self, setViewports: viewports.as_ptr() count: viewports.len() as u64] } + } + pub fn set_front_facing_winding(&self, winding: MTLWinding) { unsafe { msg_send![self, setFrontFacingWinding: winding] } } @@ -247,6 +251,10 @@ impl RenderCommandEncoderRef { unsafe { msg_send![self, setScissorRect: rect] } } + pub fn set_scissor_rects(&self, rects: &[MTLScissorRect]) { + unsafe { msg_send![self, setScissorRects: rects.as_ptr() count: rects.len()] } + } + pub fn set_triangle_fill_mode(&self, mode: MTLTriangleFillMode) { unsafe { msg_send![self, setTriangleFillMode: mode] } } diff --git a/third_party/rust/metal/src/lib.rs b/third_party/rust/metal/src/lib.rs @@ -1,3 +1,15 @@ +//! # ⚠️ Deprecated ⚠️ +//! +//! Use of this crate is deprecated as the [`objc`] ecosystem of mac system bindings are unmaintained. +//! For new development, please use [`objc2`] and [`objc2-metal`] instead. We will continue to merge basic +//! PRs and keep things maintained, at least as long as it takes to migrate [`wgpu`] to the `objc2` ecosystem [PR 5641]. +//! +//! [`objc`]: https://crates.io/crates/objc +//! [`objc2`]: https://crates.io/crates/objc2 +//! [`objc2-metal`]: https://crates.io/crates/objc2-metal +//! [`wgpu`]: https://crates.io/crates/wgpu +//! [PR 5641]: https://github.com/gfx-rs/wgpu/pull/5641 + // Copyright 2023 GFX developers // // Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or diff --git a/third_party/rust/metal/src/library.rs b/third_party/rust/metal/src/library.rs @@ -577,6 +577,12 @@ impl LibraryRef { } } + /// Retrieve a function from the library. + /// + /// Although this method is named `get_function`, the underlying Metal + /// method is named `newFunctionWithName`, and it returns a retained object + /// that has not been added to the autorelease pool. + /// See <https://github.com/gfx-rs/metal-rs/issues/128>. // FIXME: should rename to new_function pub fn get_function( &self, diff --git a/third_party/rust/naga/.cargo-checksum.json b/third_party/rust/naga/.cargo-checksum.json @@ -1 +1 @@ -{"files":{".cargo/config.toml":"7248ed3bed246d755d7bf9e5d7842d74b5c270ba6c29ad907872b55a67707ee0","CHANGELOG.md":"e60105d413f857e37dae165f819c47491d0a595183d3c9146b259d811b98b14f","Cargo.toml":"16c70c2205a241af1934fa82693b68dd82b53777e349465be5b79991b8374848","LICENSE.APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE.MIT":"dc0d97139e8205818c703741c7be7cb3b96888bd5917b8d6fc6133731e403c21","README.md":"9550cbc1a518ad0f624aabe12c342c72f670705cb4a6878c0c87d172f1dacea0","build.rs":"e9098f486e87d91710c07d40f1b32716e5debfe94a0b5e53e37075b0ee997eec","src/arena/handle.rs":"897b2b0eebe0d9ae6a65bf2e8c210c8391924da06ef4c9e2a1225ad622400b6c","src/arena/handle_set.rs":"5c2a0bcf41d85c8173ac68b2d439552e79d0c3c0fe1ff3b1e1a48f0c83a4d48f","src/arena/handlevec.rs":"baaed995bcbd2f7d15bd63cede28e579175d408e5a40289f74b32ab65c6850c3","src/arena/mod.rs":"e305d0521233791e181b4b6e7de70175a6bc730811063ea066c3bd3b73d12979","src/arena/range.rs":"b783969dfe32b4937593d871aa5190d561bdd79b6f615da53cb54346e300b9e2","src/arena/unique_arena.rs":"0e2d1c372a7c40b77793dc413b8920e86459e4656775a010f32974b4ad67a7fd","src/back/continue_forward.rs":"8194d238763caa6d5601ec3af56ba39a471c39945f43152b58d582092c99aefa","src/back/dot/mod.rs":"3f63a91f4afde7bedb3163489a347ccfa4b833e1fc7a0f33ccd8817efc7d303a","src/back/glsl/features.rs":"f187af71341bf9f853b019990ca211cdf6b5b57121a4dda33d609f21f8f828ad","src/back/glsl/keywords.rs":"fee8ed132e44bace54869a7c169a1ab7ed55db1bf590b4b1bce9efa4552e1dd7","src/back/glsl/mod.rs":"878db45fd458577b0d4ee55d0c2a5786847386d567cceb35750ecf14d54ab151","src/back/hlsl/conv.rs":"5da58838c166b753f135689a72b2bb46811269303c6104f8f483eeed93332ec3","src/back/hlsl/help.rs":"12ddb9d08fe64eb34b66ae2166edffce5106a04ccef0b8cc0391a6fafd676270","src/back/hlsl/keywords.rs":"f7fc06c44d2f0d620bcf1a02b699c98c32059741b771a0d1ba5645d71b2f433f","src/back/hlsl/mod.rs":"cbba2f7b5a3c7247c16422f0ce61c1ae4ce20edab8f8a4f2f29a219651606a7b","src/back/hlsl/ray.rs":"259db3bc8fd5b8ec343fb8620c7cef50048cbea2b9db1f17dc8813ff848269df","src/back/hlsl/storage.rs":"5083d03b1f25fe767e45e0c507bd30424352fccd48484c081620712d3682aae8","src/back/hlsl/writer.rs":"76667184d1e3f75b4f47ec9754fd5dad2bef7a3e11a3a8296b4adb36d235a5b1","src/back/mod.rs":"e4fdfcd2baba2f805422852753a9892a3d2b6449928265f061902f91bea90463","src/back/msl/keywords.rs":"b3e5d86d2586d553ff0fc6d91a25f0f4726c829d280a65dd2deeeb146cbbaf6d","src/back/msl/mod.rs":"274cdd070e0df4d65f555afac5a6de2c04bffa5fe39699ecead83d4acdbc8890","src/back/msl/sampler.rs":"9be8805063258b0ff6b6db2e6856d59326caa18f6f64eaf413c5abfcbd1ad341","src/back/msl/writer.rs":"cd4cb7524a5802a1217f3438e005db290941fac2e0bdbe706b798b9d03e4cdf6","src/back/pipeline_constants.rs":"2d1ac050e587d23adc785ebcfe7120e743878101a63c4af4e62a88641fde5b43","src/back/spv/block.rs":"8a60f872f700e132e698d744e017b54bddca8cb176ac5e377a646bd639b44124","src/back/spv/f16_polyfill.rs":"44ce07ef497dff13a9152eb9bc5936c486ef53de6d476da6b0a39396b0e6abdb","src/back/spv/helpers.rs":"8b430ceb6722a1542ab95e581f11277eff38d0041e98b97ce9681541491e1a28","src/back/spv/image.rs":"105b5f8286d6afaabbb6fd7a5f86380274bae4011f3ba28c4f860c3694678557","src/back/spv/index.rs":"c410c0596c047c278def6b073a6373d233d53642a4c3f5a9be8cb53ca23d9c7d","src/back/spv/instructions.rs":"52a8b9fcb9bfc41dbfb75a03f3132df110cd33ae4d60ba8e9cf45083b4fe2da2","src/back/spv/layout.rs":"28ba27739d7c9fa4b9e363ffc112cdc39c5e8ec4236d71a7260039d1bd8321d7","src/back/spv/mod.rs":"bb0186f8fa49be2e8f746e591c6153099bd5642ca658667f7f3d3496a8702939","src/back/spv/ray.rs":"064a4e13f55f5ca17876e32d6afb6bf8f58ad79104e81f2a4bee0bc6b731cac1","src/back/spv/recyclable.rs":"8ea397d4d8d3f2cd5fbc8e0be94d136c2d6e0f0e8a4b5eb567dcc1be104c9ae5","src/back/spv/selection.rs":"aea4bb4da7c0fa4e907b8f1e185433a48f2f0eb7ded97fdd3225beb3f6c1f249","src/back/spv/subgroup.rs":"68fc3e3153022a0a8bddec20ad9221820678f02921878318e571d5aa2ca13cee","src/back/spv/writer.rs":"588010f31591015c91558a15c6e53c28cb231f653f8cf70cf6f898bce03e848f","src/back/wgsl/mod.rs":"1b04d66e8dba609513d43431d1f0ee9a209fbfd8453862d6e8a7aa41f8910997","src/back/wgsl/polyfill/inverse/inverse_2x2_f16.wgsl":"9e7635d04724822931c805a8b35e76d6d294d447e4ea8d57b308ce45609bf736","src/back/wgsl/polyfill/inverse/inverse_2x2_f32.wgsl":"340d491abde07f93996391796db65a5f88402663eaf6b9d2d894d11cb8cf8b6d","src/back/wgsl/polyfill/inverse/inverse_3x3_f16.wgsl":"4f13a1a4b3e1b51f0f992d13c55cf854a80917554a4d13c997819fa1fe776ba4","src/back/wgsl/polyfill/inverse/inverse_3x3_f32.wgsl":"9b16d2f4b9e433c8e03a0cb46ab48508f3bf7e185ce1b4e26106c47e81a677cb","src/back/wgsl/polyfill/inverse/inverse_4x4_f16.wgsl":"86d39d1db5d03995b404950279db7f1698ad9622982aa319fdedb7532673235b","src/back/wgsl/polyfill/inverse/inverse_4x4_f32.wgsl":"dc510525ac2dce66389a8c4bf8b2f31f0dedd9e6debdbe4ffd939a0a7fc533d3","src/back/wgsl/polyfill/mod.rs":"f4ab3c9b9cdc36d16dab00d0f7f07d6e6beda0e27a36053e9b5ffeeb7ca18edc","src/back/wgsl/writer.rs":"2607b42f85a7f365f3b3965f60c9b0c0a102dc4135634a1da03ce374767f59da","src/common/diagnostic_debug.rs":"8c73fe605e5b6162d0485e264287ac50c061cf581743feebbffe1474d1d3516d","src/common/diagnostic_display.rs":"46f1ff8a32179703ef0bcdb704db9f6e6e8b4eaad6cadf94577eeab3d8a16cd1","src/common/mod.rs":"289231637b08407fbe2cc976a1bab4eac4c9e66042c6618aff3af44baaff3e26","src/common/predeclared.rs":"a5f42d55f2e13d8f5a8213d4a881e9155c3994c4054d43edcf7bd7bb7c868ccf","src/common/wgsl/diagnostics.rs":"4fec985b4c5cc6dfae4dd78bd7c850adc88a1761d7b6691de0355ea49300e532","src/common/wgsl/mod.rs":"d944915ff692c96aecca67737bccc2d5d9eb68f475166a2744f29a025f4a4c93","src/common/wgsl/to_wgsl.rs":"9055b193376e987d880f4c450465cb1ea34b560df8bf313fed445c901963c14c","src/common/wgsl/types.rs":"390323fecff390100fafcc2cb1e5cf349c7aac9da8065e9aec52a56718ab5534","src/compact/expressions.rs":"12653b34c7c7d68ce7d14a9c15e59c469fda4931425d14600fbaa99226af735f","src/compact/functions.rs":"27a0d33e5a8f02778b518c6e0db5a5d41f77a93b64eadef54b6cf0914067d7ad","src/compact/handle_set_map.rs":"b4688bff174487f2da4a1a946af07c80b6ba59f60dc84184c6a30039354209e8","src/compact/mod.rs":"1ace9291d919b0430e46184788a505513708506a5df40aacc96aece2c1e85cc9","src/compact/statements.rs":"85b2faf6a4caaebc0372e773ca3be2904db5bb0e691ac7ea845720ef0864a22b","src/compact/types.rs":"a955ce5e336afa8d26f750c14d4a6638dcee6b0b5e0fcd7c446d8f88a35d8277","src/diagnostic_filter.rs":"5e3d14a774974148b7d2918617ba3e2c3a07493e0f90485a7de9db86e05a7cd0","src/error.rs":"46180b139b60cca1e46a8848f9eecc5cab8220a022e4c6f8ce297d1d968e87e7","src/front/atomic_upgrade.rs":"86ce9f9628d92a1a09802cb534bb4310236b83f2799c921b81c687f009c589be","src/front/glsl/ast.rs":"15a4f7c56aa44529373c7aa2a266d1582b7775833de6adc6b8f5bfd54d85a669","src/front/glsl/builtins.rs":"76821d82b315ab6812e8411908885f0687ad98abfe0ea9f007e2deefed10cc0a","src/front/glsl/context.rs":"8314e1ed58b509788adbda82a8f4c7fbd2767522d0352ca1ebd11d86b4bfd10d","src/front/glsl/error.rs":"f445297e0357919e2345ae15f2d23c58d36a64c9a666f1cf1b09cbcfd6e4627a","src/front/glsl/functions.rs":"8040564f5429bf4be23e8dbd02c9cd3bbdd7eb8915b1b90ded6bc889db0b88c7","src/front/glsl/lex.rs":"24706628b600b5ce435cef464c84196ac5d58013122a97e7b59d509cc25f85a2","src/front/glsl/mod.rs":"132f2a60812b3910f65e6281562d4dc94efa329fd1eb0f2778ffe67631a35f78","src/front/glsl/offset.rs":"66bd524a2d17dc44f431430dcbbb74a771fdab43c9581e88bb1123e6cfec516b","src/front/glsl/parser.rs":"6a13b4737f53b09d5bbc0add01f8fc1b2633b7957f0318374edfe0b903939912","src/front/glsl/parser/declarations.rs":"9949649fba43636d03eaf7f7560d3bb3743b19c7204fb95859283ee84b5dd239","src/front/glsl/parser/expressions.rs":"e056fbdde3bc7c8473acbd485aecd14120d3dbefbabd813ddbc5cfedaf605889","src/front/glsl/parser/functions.rs":"302e24e06190aff555131c33f9a80b15df6a0390d6c776f888a44d5ef7df697e","src/front/glsl/parser/types.rs":"ee242048a65cd3709e16b70a3882e9296e615327480f2ad779e3d2523778181f","src/front/glsl/parser_tests.rs":"6834f0d595f4077266054e5da43e4f1b60e5c6780611ab0f530d9964cc62fad3","src/front/glsl/token.rs":"83780c0c1954ef216896c9d8a48e412b357783e00ccd4909a7a249935c742629","src/front/glsl/types.rs":"286395d82707a09d28b4c1a8bade917822478e53d8eb277ceec5fa9e71649ba2","src/front/glsl/variables.rs":"320f79e066631431d428b313d85594bf98a5b47009678e67d8ab6daa0e0752ab","src/front/interpolator.rs":"caf8c8c3ddca56fbece33cc18615c4d8c5b3b68112c9de22196d9dd49c84bf96","src/front/mod.rs":"fddd2be54ff44b52743ac8eb4a19e153a8a169af8e65d9061a9b9fc9857f64db","src/front/spv/convert.rs":"2e649c93ed5aff83b2e5d6a0daa8772baffdb5558d2b423bf23bf17fdf9e893e","src/front/spv/error.rs":"d08e1d65716ccc0f2a94a1285a1d034fa4840dc79ca60a5ec7481697bdec74d1","src/front/spv/function.rs":"55a3f0fa17f6acc71feb36ebb29e1254d056f797ec420f8c535f6ef8ddce3790","src/front/spv/image.rs":"c39ffdb19a19861cec009de39431a879be99bdd4d9d08c0ecdef397c2f3f6fa5","src/front/spv/mod.rs":"86141c03f83dfb26cff860eba0dde147da411f888bedbb894390c0d73dfc5e88","src/front/spv/null.rs":"ee20287365e025e8bcc91f29df930ff8b63cb6d7f26db0b1789d54de614a7353","src/front/type_gen.rs":"111832af89a268ae3206d2ba2159b9b9d64224ed09375a29eec142725ea7fb34","src/front/wgsl/error.rs":"0b55d5b881888f3cb7001942012f1a6815aaea7b60b3362583947d5ea19e5680","src/front/wgsl/index.rs":"476e5c4eddb14dfb53eee7976bcf4276c8a3fad81fbed92dc7b5dd31a5ab1593","src/front/wgsl/lower/construction.rs":"24e0eb2181974651ab9d13497cceaa126ee816c38848e9dbbd88f1e7b5f5c53c","src/front/wgsl/lower/conversion.rs":"d4a66519b38caa208b011846cdc3459e8a0b6bae8027235692b30188ae88e110","src/front/wgsl/lower/mod.rs":"4fa491f4c487ce8d43ae623a060179a17685f185ffa51a8f7bf62aaffa117040","src/front/wgsl/mod.rs":"95d38b0a49833638a06ac35c6df30c8fd3dcfa5494871e6d166f7e70f54b8fa3","src/front/wgsl/parse/ast.rs":"acc31dd4d5f3b784db77ff408856cefb9794e6238ca84e59d0483542396cc063","src/front/wgsl/parse/conv.rs":"7292892e3731927d55431c98e86fb532c02a9d334c5e1418701b023d6cdc9d4b","src/front/wgsl/parse/directive.rs":"c96f33cef2c1d8a374fe1b3827538f7db33d6b7811a6e0914d29de80b8963257","src/front/wgsl/parse/directive/enable_extension.rs":"9417a7c5ab332d41d306ade6e141bfca0ab834675862f12679f6a0f302dbba7b","src/front/wgsl/parse/directive/language_extension.rs":"f82ae1c1f1d82e9e27e336b6a6975e21c7c08e5f1700f28f8d351b7f03a1621c","src/front/wgsl/parse/lexer.rs":"2194d38da1dc803ffb850202023350a07b6a3b80af68857d772c76ea49bc6344","src/front/wgsl/parse/mod.rs":"afe2ab904cce536fc2672942f081c1df93896e47d2511388eb1a986c9ffdfb87","src/front/wgsl/parse/number.rs":"7af92c71031e4c4258e9d8d323f7ee99a2fd4be3b6975ab9b8b53b95431845d9","src/front/wgsl/tests.rs":"05bf78f672e54ca335ed8be59ddfb1504289ee7444258cf2d30823687a418864","src/ir/block.rs":"b562a83a4fa53002d2ca21b4553ed8e2fa77f61e687f24fd4bbd90f1597b2a9d","src/ir/mod.rs":"92dccb73b824669d6b37ec8ab1d4e9cf1701c259fd8daa1293e3ea08da3d7b0d","src/keywords/mod.rs":"47a6fde012bf7d1e70f0fac7762f6a8e7dca6b9bbb99e2cada773c61527cfbfe","src/keywords/wgsl.rs":"7236f0e751066712970b4f3dc9942b41d678c6a6e202c7da834f4f398e7cc657","src/lib.rs":"1e40237435eebd4a91fd5c9b2e8a58664ebd1c40a0d27c0c5220047f0221704a","src/non_max_u32.rs":"b2d81efda0e1e5ace9e2fad990a7adf628f1dec63273b069c93d5423eb78350d","src/proc/constant_evaluator.rs":"6fa4c03f8e756ae39b44db9537870663e81537a19beac3599cdce41ffc81c6b1","src/proc/emitter.rs":"39ac886c651e2ad33c06a676a7e4826a0e93de0af660c01e8e4b1f7406742f88","src/proc/index.rs":"17c22571251996583b724af87c7d238f584cd39104256c8e90de7b18e737bb09","src/proc/keyword_set.rs":"928414d2b79ee48735d532e03d3f0a58427c3f27a2a0c6938425749b00943784","src/proc/layouter.rs":"78a91c2c9406c0319a344cc8ec9eda33ed7812ef4a4e73e25f7709afde580381","src/proc/mod.rs":"22c424050903e566546095632fc6277784da35065255bd652fc1b86119f2c79f","src/proc/namer.rs":"f2c11c6d29d4a991fc9a08cf4531acdd309b6a9fef1c06198cc08193dc03a01d","src/proc/overloads/any_overload_set.rs":"877cd637d979abc08caa021dabb9821a79fc9109eb97024a230bcfac82830388","src/proc/overloads/constructor_set.rs":"b702f866ac1472bcc075bd0bede450388123b5899431934fd60a29865498c68b","src/proc/overloads/list.rs":"7cfbf66a3619fdd66f9acf652d56cd2a9451f7905c5d4637cdb9f77f4ef2af51","src/proc/overloads/mathfunction.rs":"d5801d64d1a6fd10e0da30a7c0ac7368954654e5f3d0b022fa806ff9a2ab61b8","src/proc/overloads/mod.rs":"0e96479cbd0ec9fa8200a5e88c16a22ee7ed2021ecf6f80a7e4ded69cad5239f","src/proc/overloads/one_bits_iter.rs":"6b98769fdec777d311248084f13958c5cca44659d0928603ece8618387ea58b2","src/proc/overloads/regular.rs":"73d64fab79019d589cb0595d0ef606fd6af732c42a418c60c81da4c96e113c89","src/proc/overloads/rule.rs":"b7f87d5ca0cffdaa8ee0db0110918f5a726359fd8a72bc638d8ce27a4b0ae3b2","src/proc/overloads/scalar_set.rs":"3729bc754dbf29a2337379ecb46568fdc3149a48074a354244da91e3d9cb5cef","src/proc/overloads/utils.rs":"4b5e02f20611bd24c6849e1f2c01aad4b271388407e8eb866d5a34983538ef8f","src/proc/terminator.rs":"61df2289408be24f69b6c23da469ca3f63f913568f8c314427c3571b826632dd","src/proc/type_methods.rs":"fef829acb2da08a22a2e30dc356e7a8b9149ef13582525bea0143c687a1c57dd","src/proc/typifier.rs":"a31a97013838e43d31cef69b032680187ac709e13db0bf8fc85f4e8e11f236cf","src/racy_lock.rs":"6c541795172660e02bac86c3808cf7346b4791910febc0d289bf93d36271d416","src/span.rs":"e91190a7cd116fb811a295dcf9cd796a25b886cc5d35c3756771e3bc61768455","src/valid/analyzer.rs":"847b9b997a64f36c43ea608066a74246bcbda596e72cd23a4204243b1cf6ecf4","src/valid/compose.rs":"46eeed8c4d5b66fc043ddb701074bd864a9fdd24e38b88e282046230acefb747","src/valid/expression.rs":"f8d9e3e20d21ddb9ade2e793d590631bcf4e7c135804a0130302d57212521f6f","src/valid/function.rs":"4e7133aaa1903b473e45f819b1ac6a4d7789b22ffbf2d25655cb739c88bf774c","src/valid/handles.rs":"65ea8d24307f6eadabd772a9eb95416f64b410ea3150b8e2cf00574ef55d6c3a","src/valid/interface.rs":"9e174ff59621b305bd7202643ae86b50455aea35336fff0ba33f433d4de60b10","src/valid/mod.rs":"763c85d3b0922eaf017c87d247f1914525f86cd41977ac4c035ab306bef9bd6e","src/valid/type.rs":"6f686499f1ab5bd68dd80977d0983006b19be4fafaf302454a9ba7619c623b70"},"package":null} -\ No newline at end of file +{"files":{".cargo/config.toml":"7248ed3bed246d755d7bf9e5d7842d74b5c270ba6c29ad907872b55a67707ee0","CHANGELOG.md":"e60105d413f857e37dae165f819c47491d0a595183d3c9146b259d811b98b14f","Cargo.toml":"7c9b3a811e078f9db2b84cf946b1787048aa87b6f3407f1121809f2592fac45b","LICENSE.APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE.MIT":"dc0d97139e8205818c703741c7be7cb3b96888bd5917b8d6fc6133731e403c21","README.md":"9550cbc1a518ad0f624aabe12c342c72f670705cb4a6878c0c87d172f1dacea0","build.rs":"e9098f486e87d91710c07d40f1b32716e5debfe94a0b5e53e37075b0ee997eec","src/arena/handle.rs":"897b2b0eebe0d9ae6a65bf2e8c210c8391924da06ef4c9e2a1225ad622400b6c","src/arena/handle_set.rs":"5c2a0bcf41d85c8173ac68b2d439552e79d0c3c0fe1ff3b1e1a48f0c83a4d48f","src/arena/handlevec.rs":"baaed995bcbd2f7d15bd63cede28e579175d408e5a40289f74b32ab65c6850c3","src/arena/mod.rs":"e305d0521233791e181b4b6e7de70175a6bc730811063ea066c3bd3b73d12979","src/arena/range.rs":"b783969dfe32b4937593d871aa5190d561bdd79b6f615da53cb54346e300b9e2","src/arena/unique_arena.rs":"0e2d1c372a7c40b77793dc413b8920e86459e4656775a010f32974b4ad67a7fd","src/back/continue_forward.rs":"8194d238763caa6d5601ec3af56ba39a471c39945f43152b58d582092c99aefa","src/back/dot/mod.rs":"3f63a91f4afde7bedb3163489a347ccfa4b833e1fc7a0f33ccd8817efc7d303a","src/back/glsl/conv.rs":"927aa5ec3c3b6c0d8a391373ea7e285a93d4711372e82fad5cb22013830cb868","src/back/glsl/features.rs":"3ca9a7b28327ac400a9fd439f3cc0c944fc938b5d52d0be58f58c0ea3fbf3f15","src/back/glsl/keywords.rs":"fee8ed132e44bace54869a7c169a1ab7ed55db1bf590b4b1bce9efa4552e1dd7","src/back/glsl/mod.rs":"74940fe468a13a4fcdd860b458a31e1b9ca7705ef7e9d265097a543b115ec490","src/back/glsl/writer.rs":"f1c78877a171eee8b1deb8e9505a8b06074dbe8b979e7fa89d267b471d5b3045","src/back/hlsl/conv.rs":"5da58838c166b753f135689a72b2bb46811269303c6104f8f483eeed93332ec3","src/back/hlsl/help.rs":"12ddb9d08fe64eb34b66ae2166edffce5106a04ccef0b8cc0391a6fafd676270","src/back/hlsl/keywords.rs":"f7fc06c44d2f0d620bcf1a02b699c98c32059741b771a0d1ba5645d71b2f433f","src/back/hlsl/mod.rs":"ed900ac6f261fba33c593b8d440f08917f7e65ebf19b8684f39bff7fb6e4f194","src/back/hlsl/ray.rs":"259db3bc8fd5b8ec343fb8620c7cef50048cbea2b9db1f17dc8813ff848269df","src/back/hlsl/storage.rs":"5083d03b1f25fe767e45e0c507bd30424352fccd48484c081620712d3682aae8","src/back/hlsl/writer.rs":"14cf359afd1291a8e9757fc53fd8656b046f8670a95cdec868cdec6aac677efa","src/back/mod.rs":"a561131429ad34cb9ef658e14f7e1b005b99fc11140326f433c8d724a20904ff","src/back/msl/keywords.rs":"762f47eaa5c395def2fd02f49cb387e5aec7a08c65f3377fa1f0408667a6c286","src/back/msl/mod.rs":"24fb03b7e5b8c8ac7e01f6858487619e09620db775a917c451528cc862aa1d9b","src/back/msl/sampler.rs":"db6c01814d939f1f52eb315e39f34494129c942fa114b11213035c5e3736ed18","src/back/msl/writer.rs":"16a01752592ef4147c4e19ddbd3b3130cc15449caf03ce1cfa2dd1fc384d4dd3","src/back/pipeline_constants.rs":"2d1ac050e587d23adc785ebcfe7120e743878101a63c4af4e62a88641fde5b43","src/back/spv/block.rs":"be008dd0083ddab2324c6075be4134c43a376356a50da75c3613a078cda638e2","src/back/spv/f16_polyfill.rs":"44ce07ef497dff13a9152eb9bc5936c486ef53de6d476da6b0a39396b0e6abdb","src/back/spv/helpers.rs":"8f7e158ca104a4f7a360c4019be67859d7c5c7dce3b44d500a45da01c2826e45","src/back/spv/image.rs":"105b5f8286d6afaabbb6fd7a5f86380274bae4011f3ba28c4f860c3694678557","src/back/spv/index.rs":"c410c0596c047c278def6b073a6373d233d53642a4c3f5a9be8cb53ca23d9c7d","src/back/spv/instructions.rs":"09117b61f340b34c4660089fd909e203e651214ae52282c80c784699b2dce6d0","src/back/spv/layout.rs":"28ba27739d7c9fa4b9e363ffc112cdc39c5e8ec4236d71a7260039d1bd8321d7","src/back/spv/mesh_shader.rs":"8687c7e45557fa85d144a68fba5510431a2000cd10d31d36684a4ec908e6a967","src/back/spv/mod.rs":"a1fc88e0ec0773a4dcbd901b404d35e334ccb3a034453c0cb9d09e7d510a6677","src/back/spv/ray.rs":"766b673bdcee4c5665ebd46775d89eebecd3bdfebbe8347d3fecf6a718286bbb","src/back/spv/recyclable.rs":"8ea397d4d8d3f2cd5fbc8e0be94d136c2d6e0f0e8a4b5eb567dcc1be104c9ae5","src/back/spv/selection.rs":"aea4bb4da7c0fa4e907b8f1e185433a48f2f0eb7ded97fdd3225beb3f6c1f249","src/back/spv/subgroup.rs":"68fc3e3153022a0a8bddec20ad9221820678f02921878318e571d5aa2ca13cee","src/back/spv/writer.rs":"b46ae56c44744af21d80cc7f83f4f9c0260bb35d19e1415e6446e40fbe0dbfc5","src/back/wgsl/mod.rs":"1b04d66e8dba609513d43431d1f0ee9a209fbfd8453862d6e8a7aa41f8910997","src/back/wgsl/polyfill/inverse/inverse_2x2_f16.wgsl":"9e7635d04724822931c805a8b35e76d6d294d447e4ea8d57b308ce45609bf736","src/back/wgsl/polyfill/inverse/inverse_2x2_f32.wgsl":"340d491abde07f93996391796db65a5f88402663eaf6b9d2d894d11cb8cf8b6d","src/back/wgsl/polyfill/inverse/inverse_3x3_f16.wgsl":"4f13a1a4b3e1b51f0f992d13c55cf854a80917554a4d13c997819fa1fe776ba4","src/back/wgsl/polyfill/inverse/inverse_3x3_f32.wgsl":"9b16d2f4b9e433c8e03a0cb46ab48508f3bf7e185ce1b4e26106c47e81a677cb","src/back/wgsl/polyfill/inverse/inverse_4x4_f16.wgsl":"86d39d1db5d03995b404950279db7f1698ad9622982aa319fdedb7532673235b","src/back/wgsl/polyfill/inverse/inverse_4x4_f32.wgsl":"dc510525ac2dce66389a8c4bf8b2f31f0dedd9e6debdbe4ffd939a0a7fc533d3","src/back/wgsl/polyfill/mod.rs":"f4ab3c9b9cdc36d16dab00d0f7f07d6e6beda0e27a36053e9b5ffeeb7ca18edc","src/back/wgsl/writer.rs":"a2ccc6024d0bc64e52e6b1e36967250b3bcb46a0ee99e9538ca6b75caa8d8bc7","src/common/diagnostic_debug.rs":"8c73fe605e5b6162d0485e264287ac50c061cf581743feebbffe1474d1d3516d","src/common/diagnostic_display.rs":"46f1ff8a32179703ef0bcdb704db9f6e6e8b4eaad6cadf94577eeab3d8a16cd1","src/common/mod.rs":"0c979fd43068cfbceb7b29ff0bc578327e0770d5403c12f48d8605f77efbe200","src/common/predeclared.rs":"a5f42d55f2e13d8f5a8213d4a881e9155c3994c4054d43edcf7bd7bb7c868ccf","src/common/wgsl/diagnostics.rs":"4fec985b4c5cc6dfae4dd78bd7c850adc88a1761d7b6691de0355ea49300e532","src/common/wgsl/mod.rs":"d944915ff692c96aecca67737bccc2d5d9eb68f475166a2744f29a025f4a4c93","src/common/wgsl/to_wgsl.rs":"4f1856538d15e00f6fe15c21a06298be58f0ddcb5de53b88ef311d056fd16a3c","src/common/wgsl/types.rs":"390323fecff390100fafcc2cb1e5cf349c7aac9da8065e9aec52a56718ab5534","src/compact/expressions.rs":"12653b34c7c7d68ce7d14a9c15e59c469fda4931425d14600fbaa99226af735f","src/compact/functions.rs":"27a0d33e5a8f02778b518c6e0db5a5d41f77a93b64eadef54b6cf0914067d7ad","src/compact/handle_set_map.rs":"b4688bff174487f2da4a1a946af07c80b6ba59f60dc84184c6a30039354209e8","src/compact/mod.rs":"1ace9291d919b0430e46184788a505513708506a5df40aacc96aece2c1e85cc9","src/compact/statements.rs":"85b2faf6a4caaebc0372e773ca3be2904db5bb0e691ac7ea845720ef0864a22b","src/compact/types.rs":"a955ce5e336afa8d26f750c14d4a6638dcee6b0b5e0fcd7c446d8f88a35d8277","src/diagnostic_filter.rs":"5e3d14a774974148b7d2918617ba3e2c3a07493e0f90485a7de9db86e05a7cd0","src/error.rs":"46180b139b60cca1e46a8848f9eecc5cab8220a022e4c6f8ce297d1d968e87e7","src/front/atomic_upgrade.rs":"86ce9f9628d92a1a09802cb534bb4310236b83f2799c921b81c687f009c589be","src/front/glsl/ast.rs":"78cdf7ae13a2d8ffa3397eb114a112f93b3d1a10c1983e008b50ea5a4371a87c","src/front/glsl/builtins.rs":"ffe36464b5758dd8c917c3e9505d4027268e4b019174f0f95a229cd16d4cafa1","src/front/glsl/context.rs":"fb933e2593097a404a7de3ee64966ba3521e32865ce35066485371afba9b17f0","src/front/glsl/error.rs":"f445297e0357919e2345ae15f2d23c58d36a64c9a666f1cf1b09cbcfd6e4627a","src/front/glsl/functions.rs":"8040564f5429bf4be23e8dbd02c9cd3bbdd7eb8915b1b90ded6bc889db0b88c7","src/front/glsl/lex.rs":"24706628b600b5ce435cef464c84196ac5d58013122a97e7b59d509cc25f85a2","src/front/glsl/mod.rs":"132f2a60812b3910f65e6281562d4dc94efa329fd1eb0f2778ffe67631a35f78","src/front/glsl/offset.rs":"66bd524a2d17dc44f431430dcbbb74a771fdab43c9581e88bb1123e6cfec516b","src/front/glsl/parser.rs":"9aedb0bc25f811fd1924f25017725d7ccda6c04163e96508012d96a17ac4b08c","src/front/glsl/parser/declarations.rs":"311212375f0ba39ef77889acadd54b9909cc9d043e424c0b6c9db3220d68d03e","src/front/glsl/parser/expressions.rs":"e056fbdde3bc7c8473acbd485aecd14120d3dbefbabd813ddbc5cfedaf605889","src/front/glsl/parser/functions.rs":"302e24e06190aff555131c33f9a80b15df6a0390d6c776f888a44d5ef7df697e","src/front/glsl/parser/types.rs":"ee242048a65cd3709e16b70a3882e9296e615327480f2ad779e3d2523778181f","src/front/glsl/parser_tests.rs":"6834f0d595f4077266054e5da43e4f1b60e5c6780611ab0f530d9964cc62fad3","src/front/glsl/token.rs":"83780c0c1954ef216896c9d8a48e412b357783e00ccd4909a7a249935c742629","src/front/glsl/types.rs":"286395d82707a09d28b4c1a8bade917822478e53d8eb277ceec5fa9e71649ba2","src/front/glsl/variables.rs":"97dbdfe45ae893bb18f47b1091e1b055e89912e486f8849722bd10b20c3bf782","src/front/interpolator.rs":"caf8c8c3ddca56fbece33cc18615c4d8c5b3b68112c9de22196d9dd49c84bf96","src/front/mod.rs":"fddd2be54ff44b52743ac8eb4a19e153a8a169af8e65d9061a9b9fc9857f64db","src/front/spv/convert.rs":"218d551db20a973763b70142124576ba75d94883b6b4ab99096241ac167fa5bc","src/front/spv/error.rs":"d08e1d65716ccc0f2a94a1285a1d034fa4840dc79ca60a5ec7481697bdec74d1","src/front/spv/function.rs":"55a3f0fa17f6acc71feb36ebb29e1254d056f797ec420f8c535f6ef8ddce3790","src/front/spv/image.rs":"c39ffdb19a19861cec009de39431a879be99bdd4d9d08c0ecdef397c2f3f6fa5","src/front/spv/mod.rs":"87c221527b2374a3e54f9633c8a50610ec8ba6fff40202e7783c041962bffc9a","src/front/spv/next_block.rs":"af6039f0664f500843c8787eaa6262894d9be220ce964cfa4d2152ecdef7d732","src/front/spv/null.rs":"ee20287365e025e8bcc91f29df930ff8b63cb6d7f26db0b1789d54de614a7353","src/front/type_gen.rs":"111832af89a268ae3206d2ba2159b9b9d64224ed09375a29eec142725ea7fb34","src/front/wgsl/error.rs":"d5931cb5e7d31b380e15582639c0e95625358f172db9fe6b2d5777f674e584d7","src/front/wgsl/index.rs":"476e5c4eddb14dfb53eee7976bcf4276c8a3fad81fbed92dc7b5dd31a5ab1593","src/front/wgsl/lower/construction.rs":"24e0eb2181974651ab9d13497cceaa126ee816c38848e9dbbd88f1e7b5f5c53c","src/front/wgsl/lower/conversion.rs":"d4a66519b38caa208b011846cdc3459e8a0b6bae8027235692b30188ae88e110","src/front/wgsl/lower/mod.rs":"c69224d0ec53f49667fedfdaff1b3d85c9b5d10122fdbde8766d54b89eed0658","src/front/wgsl/mod.rs":"95d38b0a49833638a06ac35c6df30c8fd3dcfa5494871e6d166f7e70f54b8fa3","src/front/wgsl/parse/ast.rs":"acc31dd4d5f3b784db77ff408856cefb9794e6238ca84e59d0483542396cc063","src/front/wgsl/parse/conv.rs":"09794cac771778d797476d188908aa5e5a91b1d25901bc1c929e8b3f4d169cc4","src/front/wgsl/parse/directive.rs":"c96f33cef2c1d8a374fe1b3827538f7db33d6b7811a6e0914d29de80b8963257","src/front/wgsl/parse/directive/enable_extension.rs":"7a3ab67784c867c5f62e7cb17c2be23441f24792b7bbb44a77f6b852d2e3fb53","src/front/wgsl/parse/directive/language_extension.rs":"f82ae1c1f1d82e9e27e336b6a6975e21c7c08e5f1700f28f8d351b7f03a1621c","src/front/wgsl/parse/lexer.rs":"2194d38da1dc803ffb850202023350a07b6a3b80af68857d772c76ea49bc6344","src/front/wgsl/parse/mod.rs":"0853849ce2ebf874f136d96c862954796dc56f6fee4e63efbf26c590555eb14b","src/front/wgsl/parse/number.rs":"7af92c71031e4c4258e9d8d323f7ee99a2fd4be3b6975ab9b8b53b95431845d9","src/front/wgsl/tests.rs":"9ce1dc978d64b7c551a07ac683cc9ec77d51f77c7394607ea93c30b22829e5e6","src/ir/block.rs":"b562a83a4fa53002d2ca21b4553ed8e2fa77f61e687f24fd4bbd90f1597b2a9d","src/ir/mod.rs":"e5eeec5613736a3a41bc7c32e7903ea290ac1ea0fc49fafd2160768354451672","src/keywords/mod.rs":"47a6fde012bf7d1e70f0fac7762f6a8e7dca6b9bbb99e2cada773c61527cfbfe","src/keywords/wgsl.rs":"7236f0e751066712970b4f3dc9942b41d678c6a6e202c7da834f4f398e7cc657","src/lib.rs":"1e40237435eebd4a91fd5c9b2e8a58664ebd1c40a0d27c0c5220047f0221704a","src/non_max_u32.rs":"b2d81efda0e1e5ace9e2fad990a7adf628f1dec63273b069c93d5423eb78350d","src/proc/constant_evaluator.rs":"f922147a2d43869d7edde27e90ee588a19f8cc53c4a712e529be60ed5f231e6c","src/proc/emitter.rs":"39ac886c651e2ad33c06a676a7e4826a0e93de0af660c01e8e4b1f7406742f88","src/proc/index.rs":"17c22571251996583b724af87c7d238f584cd39104256c8e90de7b18e737bb09","src/proc/keyword_set.rs":"928414d2b79ee48735d532e03d3f0a58427c3f27a2a0c6938425749b00943784","src/proc/layouter.rs":"3cba1a304e8b485608f7913fda40dd9fe1777dfee3ffb2f8f34bac078edbd800","src/proc/mod.rs":"648c84cc836960242959d966693d344d238360a163377ea67618037517dd0aaf","src/proc/namer.rs":"8f295d03e29f7dab114fd13fe2a5d9271e373bf1c7bec889f1a6899298b9567a","src/proc/overloads/any_overload_set.rs":"877cd637d979abc08caa021dabb9821a79fc9109eb97024a230bcfac82830388","src/proc/overloads/constructor_set.rs":"b702f866ac1472bcc075bd0bede450388123b5899431934fd60a29865498c68b","src/proc/overloads/list.rs":"0a4ff2a43e035da2cf4a321a15afaff676377838095e691d3e5447f8d9180ab7","src/proc/overloads/mathfunction.rs":"305716a575bed3ec456db583e65bd5a972f1448612b6f260b278ae0b79802540","src/proc/overloads/mod.rs":"0e96479cbd0ec9fa8200a5e88c16a22ee7ed2021ecf6f80a7e4ded69cad5239f","src/proc/overloads/one_bits_iter.rs":"6b98769fdec777d311248084f13958c5cca44659d0928603ece8618387ea58b2","src/proc/overloads/regular.rs":"73d64fab79019d589cb0595d0ef606fd6af732c42a418c60c81da4c96e113c89","src/proc/overloads/rule.rs":"b7f87d5ca0cffdaa8ee0db0110918f5a726359fd8a72bc638d8ce27a4b0ae3b2","src/proc/overloads/scalar_set.rs":"3729bc754dbf29a2337379ecb46568fdc3149a48074a354244da91e3d9cb5cef","src/proc/overloads/utils.rs":"d8f661579e917edb892b6a7e8babadbc0db1a110d74558b81b49b3331bd07a5e","src/proc/terminator.rs":"61df2289408be24f69b6c23da469ca3f63f913568f8c314427c3571b826632dd","src/proc/type_methods.rs":"4aa2892ee2f0bfc136a7aa3e62f695533694b35521696b65cc38d8a4404d536a","src/proc/typifier.rs":"a31a97013838e43d31cef69b032680187ac709e13db0bf8fc85f4e8e11f236cf","src/racy_lock.rs":"6c541795172660e02bac86c3808cf7346b4791910febc0d289bf93d36271d416","src/span.rs":"e91190a7cd116fb811a295dcf9cd796a25b886cc5d35c3756771e3bc61768455","src/valid/analyzer.rs":"d72415dd8cb0586062c65e78122ea7882c71719faea37ba54b0040b7ad08e081","src/valid/compose.rs":"46eeed8c4d5b66fc043ddb701074bd864a9fdd24e38b88e282046230acefb747","src/valid/expression.rs":"9d5a0fdaff2b2d4df3c5cab64e3614b24527e0932551d68f5a64ee2ffcf9ff8c","src/valid/function.rs":"b494350c47d77b0c9e5fdede7f13dc89543803c87b647bd51a254980bb8ff946","src/valid/handles.rs":"65ea8d24307f6eadabd772a9eb95416f64b410ea3150b8e2cf00574ef55d6c3a","src/valid/interface.rs":"f9d105f5b4571633cb5955ce40606df370b8c38f90b4aecb0f77fe1c36740fea","src/valid/mod.rs":"1ebb2f98e44c60a5aacd8cbc274adc8eed634709a42918f8adcace98c5b1c641","src/valid/type.rs":"e71d6d4395fb26cf93e587d56a24d106a85dfb6b848b51a137a91e58b0bb10d3"},"package":null} +\ No newline at end of file diff --git a/third_party/rust/naga/Cargo.toml b/third_party/rust/naga/Cargo.toml @@ -13,7 +13,7 @@ edition = "2021" rust-version = "1.82.0" name = "naga" -version = "27.0.0" +version = "28.0.0" authors = ["gfx-rs developers"] build = "build.rs" exclude = [ @@ -213,7 +213,7 @@ version = "0.14" path = "../naga-test" [dev-dependencies.ron] -version = "0.11" +version = "0.12" [dev-dependencies.rspirv] version = "0.12" diff --git a/third_party/rust/naga/src/back/glsl/conv.rs b/third_party/rust/naga/src/back/glsl/conv.rs @@ -0,0 +1,245 @@ +use crate::back::glsl::{BackendResult, Error, VaryingOptions}; + +/// Structure returned by [`glsl_scalar`] +/// +/// It contains both a prefix used in other types and the full type name +pub(in crate::back::glsl) struct ScalarString<'a> { + /// The prefix used to compose other types + pub prefix: &'a str, + /// The name of the scalar type + pub full: &'a str, +} + +/// Helper function that returns scalar related strings +/// +/// Check [`ScalarString`] for the information provided +/// +/// # Errors +/// If a [`Float`](crate::ScalarKind::Float) with an width that isn't 4 or 8 +pub(in crate::back::glsl) const fn glsl_scalar( + scalar: crate::Scalar, +) -> Result<ScalarString<'static>, Error> { + use crate::ScalarKind as Sk; + + Ok(match scalar.kind { + Sk::Sint => ScalarString { + prefix: "i", + full: "int", + }, + Sk::Uint => ScalarString { + prefix: "u", + full: "uint", + }, + Sk::Float => match scalar.width { + 4 => ScalarString { + prefix: "", + full: "float", + }, + 8 => ScalarString { + prefix: "d", + full: "double", + }, + _ => return Err(Error::UnsupportedScalar(scalar)), + }, + Sk::Bool => ScalarString { + prefix: "b", + full: "bool", + }, + Sk::AbstractInt | Sk::AbstractFloat => { + return Err(Error::UnsupportedScalar(scalar)); + } + }) +} + +/// Helper function that returns the glsl variable name for a builtin +pub(in crate::back::glsl) const fn glsl_built_in( + built_in: crate::BuiltIn, + options: VaryingOptions, +) -> &'static str { + use crate::BuiltIn as Bi; + + match built_in { + Bi::Position { .. } => { + if options.output { + "gl_Position" + } else { + "gl_FragCoord" + } + } + Bi::ViewIndex => { + if options.targeting_webgl { + "gl_ViewID_OVR" + } else { + "uint(gl_ViewIndex)" + } + } + // vertex + Bi::BaseInstance => "uint(gl_BaseInstance)", + Bi::BaseVertex => "uint(gl_BaseVertex)", + Bi::ClipDistance => "gl_ClipDistance", + Bi::CullDistance => "gl_CullDistance", + Bi::InstanceIndex => { + if options.draw_parameters { + "(uint(gl_InstanceID) + uint(gl_BaseInstanceARB))" + } else { + // Must match FIRST_INSTANCE_BINDING + "(uint(gl_InstanceID) + naga_vs_first_instance)" + } + } + Bi::PointSize => "gl_PointSize", + Bi::VertexIndex => "uint(gl_VertexID)", + Bi::DrawID => "gl_DrawID", + // fragment + Bi::FragDepth => "gl_FragDepth", + Bi::PointCoord => "gl_PointCoord", + Bi::FrontFacing => "gl_FrontFacing", + Bi::PrimitiveIndex => "uint(gl_PrimitiveID)", + Bi::Barycentric => "gl_BaryCoordEXT", + Bi::SampleIndex => "gl_SampleID", + Bi::SampleMask => { + if options.output { + "gl_SampleMask" + } else { + "gl_SampleMaskIn" + } + } + // compute + Bi::GlobalInvocationId => "gl_GlobalInvocationID", + Bi::LocalInvocationId => "gl_LocalInvocationID", + Bi::LocalInvocationIndex => "gl_LocalInvocationIndex", + Bi::WorkGroupId => "gl_WorkGroupID", + Bi::WorkGroupSize => "gl_WorkGroupSize", + Bi::NumWorkGroups => "gl_NumWorkGroups", + // subgroup + Bi::NumSubgroups => "gl_NumSubgroups", + Bi::SubgroupId => "gl_SubgroupID", + Bi::SubgroupSize => "gl_SubgroupSize", + Bi::SubgroupInvocationId => "gl_SubgroupInvocationID", + // mesh + // TODO: figure out how to map these to glsl things as glsl treats them as arrays + Bi::CullPrimitive + | Bi::PointIndex + | Bi::LineIndices + | Bi::TriangleIndices + | Bi::MeshTaskSize + | Bi::VertexCount + | Bi::PrimitiveCount + | Bi::Vertices + | Bi::Primitives => { + unimplemented!() + } + } +} + +/// Helper function that returns the string corresponding to the address space +pub(in crate::back::glsl) const fn glsl_storage_qualifier( + space: crate::AddressSpace, +) -> Option<&'static str> { + use crate::AddressSpace as As; + + match space { + As::Function => None, + As::Private => None, + As::Storage { .. } => Some("buffer"), + As::Uniform => Some("uniform"), + As::Handle => Some("uniform"), + As::WorkGroup => Some("shared"), + As::Immediate => Some("uniform"), + As::TaskPayload => unreachable!(), + } +} + +/// Helper function that returns the string corresponding to the glsl interpolation qualifier +pub(in crate::back::glsl) const fn glsl_interpolation( + interpolation: crate::Interpolation, +) -> &'static str { + use crate::Interpolation as I; + + match interpolation { + I::Perspective => "smooth", + I::Linear => "noperspective", + I::Flat => "flat", + } +} + +/// Return the GLSL auxiliary qualifier for the given sampling value. +pub(in crate::back::glsl) const fn glsl_sampling( + sampling: crate::Sampling, +) -> BackendResult<Option<&'static str>> { + use crate::Sampling as S; + + Ok(match sampling { + S::First => return Err(Error::FirstSamplingNotSupported), + S::Center | S::Either => None, + S::Centroid => Some("centroid"), + S::Sample => Some("sample"), + }) +} + +/// Helper function that returns the glsl dimension string of [`ImageDimension`](crate::ImageDimension) +pub(in crate::back::glsl) const fn glsl_dimension(dim: crate::ImageDimension) -> &'static str { + use crate::ImageDimension as IDim; + + match dim { + IDim::D1 => "1D", + IDim::D2 => "2D", + IDim::D3 => "3D", + IDim::Cube => "Cube", + } +} + +/// Helper function that returns the glsl storage format string of [`StorageFormat`](crate::StorageFormat) +pub(in crate::back::glsl) fn glsl_storage_format( + format: crate::StorageFormat, +) -> Result<&'static str, Error> { + use crate::StorageFormat as Sf; + + Ok(match format { + Sf::R8Unorm => "r8", + Sf::R8Snorm => "r8_snorm", + Sf::R8Uint => "r8ui", + Sf::R8Sint => "r8i", + Sf::R16Uint => "r16ui", + Sf::R16Sint => "r16i", + Sf::R16Float => "r16f", + Sf::Rg8Unorm => "rg8", + Sf::Rg8Snorm => "rg8_snorm", + Sf::Rg8Uint => "rg8ui", + Sf::Rg8Sint => "rg8i", + Sf::R32Uint => "r32ui", + Sf::R32Sint => "r32i", + Sf::R32Float => "r32f", + Sf::Rg16Uint => "rg16ui", + Sf::Rg16Sint => "rg16i", + Sf::Rg16Float => "rg16f", + Sf::Rgba8Unorm => "rgba8", + Sf::Rgba8Snorm => "rgba8_snorm", + Sf::Rgba8Uint => "rgba8ui", + Sf::Rgba8Sint => "rgba8i", + Sf::Rgb10a2Uint => "rgb10_a2ui", + Sf::Rgb10a2Unorm => "rgb10_a2", + Sf::Rg11b10Ufloat => "r11f_g11f_b10f", + Sf::R64Uint => "r64ui", + Sf::Rg32Uint => "rg32ui", + Sf::Rg32Sint => "rg32i", + Sf::Rg32Float => "rg32f", + Sf::Rgba16Uint => "rgba16ui", + Sf::Rgba16Sint => "rgba16i", + Sf::Rgba16Float => "rgba16f", + Sf::Rgba32Uint => "rgba32ui", + Sf::Rgba32Sint => "rgba32i", + Sf::Rgba32Float => "rgba32f", + Sf::R16Unorm => "r16", + Sf::R16Snorm => "r16_snorm", + Sf::Rg16Unorm => "rg16", + Sf::Rg16Snorm => "rg16_snorm", + Sf::Rgba16Unorm => "rgba16", + Sf::Rgba16Snorm => "rgba16_snorm", + + Sf::Bgra8Unorm => { + return Err(Error::Custom( + "Support format BGRA8 is not implemented".into(), + )) + } + }) +} diff --git a/third_party/rust/naga/src/back/glsl/features.rs b/third_party/rust/naga/src/back/glsl/features.rs @@ -439,7 +439,7 @@ impl<W> Writer<'_, W> { } } - let mut push_constant_used = false; + let mut immediates_used = false; for (handle, global) in self.module.global_variables.iter() { if ep_info[handle].is_empty() { @@ -448,11 +448,11 @@ impl<W> Writer<'_, W> { match global.space { AddressSpace::WorkGroup => self.features.request(Features::COMPUTE_SHADER), AddressSpace::Storage { .. } => self.features.request(Features::BUFFER_STORAGE), - AddressSpace::PushConstant => { - if push_constant_used { - return Err(Error::MultiplePushConstants); + AddressSpace::Immediate => { + if immediates_used { + return Err(Error::MultipleImmediateData); } - push_constant_used = true; + immediates_used = true; } _ => {} } diff --git a/third_party/rust/naga/src/back/glsl/mod.rs b/third_party/rust/naga/src/back/glsl/mod.rs @@ -44,6 +44,7 @@ to output a [`Module`](crate::Module) into glsl // vector, matrices, samplers, image types and functions that provide common shader operations pub use features::Features; +pub use writer::Writer; use alloc::{ borrow::ToOwned, @@ -67,12 +68,17 @@ use crate::{ proc::{self, NameKey}, valid, Handle, ShaderStage, TypeInner, }; +use conv::*; use features::FeaturesManager; +/// Contains simple 1:1 conversion functions. +mod conv; /// Contains the features related code and the features querying method mod features; /// Contains a constant with a slice of all the reserved keywords RESERVED_KEYWORDS mod keywords; +/// Contains the [`Writer`] type. +mod writer; /// List of supported `core` GLSL versions. pub const SUPPORTED_CORE_VERSIONS: &[u16] = &[140, 150, 330, 400, 410, 420, 430, 440, 450, 460]; @@ -139,7 +145,7 @@ impl crate::AddressSpace { | crate::AddressSpace::Uniform | crate::AddressSpace::Storage { .. } | crate::AddressSpace::Handle - | crate::AddressSpace::PushConstant + | crate::AddressSpace::Immediate | crate::AddressSpace::TaskPayload => false, } } @@ -376,8 +382,8 @@ pub struct ReflectionInfo { pub uniforms: crate::FastHashMap<Handle<crate::GlobalVariable>, String>, /// Mapping between names and attribute locations. pub varying: crate::FastHashMap<String, VaryingLocation>, - /// List of push constant items in the shader. - pub push_constant_items: Vec<PushConstantItem>, + /// List of immediate data items in the shader. + pub immediates_items: Vec<ImmediateItem>, /// Number of user-defined clip planes. Only applicable to vertex shaders. pub clip_distance_count: u32, } @@ -401,14 +407,14 @@ pub struct TextureMapping { /// All information to bind a single uniform value to the shader. /// -/// Push constants are emulated using traditional uniforms in OpenGL. +/// Immediates are emulated using traditional uniforms in OpenGL. /// /// These are composed of a set of primitives (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 primitive in the -/// push constant block. +/// immediate data block. #[derive(Debug, Clone)] -pub struct PushConstantItem { +pub struct ImmediateItem { /// GL uniform name for the item. This name is the same as if you were /// to access it directly from a GLSL shader. /// @@ -420,24 +426,24 @@ pub struct PushConstantItem { /// value: f32, /// } /// - /// struct PushConstant { + /// struct ImmediateData { /// InnerStruct inner; /// vec4 array[2]; /// } /// - /// uniform PushConstants _push_constant_binding_cs; + /// uniform ImmediateData _immediates_binding_cs; /// ``` /// /// ```text - /// - _push_constant_binding_cs.inner.value - /// - _push_constant_binding_cs.array[0] - /// - _push_constant_binding_cs.array[1] + /// - _immediates_binding_cs.inner.value + /// - _immediates_binding_cs.array[0] + /// - _immediates_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<crate::Type>, - /// The offset in the push constant memory block this uniform maps to. + /// The offset in the immediate data memory block this uniform maps to. /// /// The size of the uniform can be derived from the type. pub offset: u32, @@ -540,10 +546,10 @@ pub enum Error { /// Contains the missing [`Features`]. #[error("The selected version doesn't support {0:?}")] MissingFeatures(Features), - /// [`AddressSpace::PushConstant`](crate::AddressSpace::PushConstant) was used more than + /// [`AddressSpace::Immediate`](crate::AddressSpace::Immediate) was used more than /// once in the entry point, which isn't supported. - #[error("Multiple push constants aren't supported")] - MultiplePushConstants, + #[error("Multiple immediates aren't supported")] + MultipleImmediateData, /// The specified [`Version`] isn't supported. #[error("The specified version isn't supported")] VersionNotSupported, @@ -582,4804 +588,6 @@ enum BinaryOperation { Other, } -/// Writer responsible for all code generation. -pub struct Writer<'a, W> { - // Inputs - /// The module being written. - module: &'a crate::Module, - /// The module analysis. - info: &'a valid::ModuleInfo, - /// The output writer. - out: W, - /// User defined configuration to be used. - options: &'a Options, - /// The bound checking policies to be used - policies: proc::BoundsCheckPolicies, - - // Internal State - /// Features manager used to store all the needed features and write them. - features: FeaturesManager, - namer: proc::Namer, - /// A map with all the names needed for writing the module - /// (generated by a [`Namer`](crate::proc::Namer)). - names: crate::FastHashMap<NameKey, String>, - /// A map with the names of global variables needed for reflections. - reflection_names_globals: crate::FastHashMap<Handle<crate::GlobalVariable>, String>, - /// The selected entry point. - entry_point: &'a crate::EntryPoint, - /// The index of the selected entry point. - entry_point_idx: proc::EntryPointIndex, - /// A generator for unique block numbers. - block_id: IdGenerator, - /// Set of expressions that have associated temporary variables. - named_expressions: crate::NamedExpressions, - /// Set of expressions that need to be baked to avoid unnecessary repetition in output - need_bake_expressions: back::NeedBakeExpressions, - /// Information about nesting of loops and switches. - /// - /// Used for forwarding continue statements in switches that have been - /// transformed to `do {} while(false);` loops. - continue_ctx: back::continue_forward::ContinueCtx, - /// How many views to render to, if doing multiview rendering. - multiview: Option<core::num::NonZeroU32>, - /// Mapping of varying variables to their location. Needed for reflections. - varying: crate::FastHashMap<String, VaryingLocation>, - /// Number of user-defined clip planes. Only non-zero for vertex shaders. - clip_distance_count: u32, -} - -impl<'a, W: Write> Writer<'a, W> { - /// Creates a new [`Writer`] instance. - /// - /// # Errors - /// - If the version specified is invalid or supported. - /// - If the entry point couldn't be found in the module. - /// - If the version specified doesn't support some used features. - pub fn new( - out: W, - module: &'a crate::Module, - info: &'a valid::ModuleInfo, - options: &'a Options, - pipeline_options: &'a PipelineOptions, - policies: proc::BoundsCheckPolicies, - ) -> Result<Self, Error> { - // Check if the requested version is supported - if !options.version.is_supported() { - log::error!("Version {}", options.version); - return Err(Error::VersionNotSupported); - } - - // Try to find the entry point and corresponding index - let ep_idx = module - .entry_points - .iter() - .position(|ep| { - pipeline_options.shader_stage == ep.stage && pipeline_options.entry_point == ep.name - }) - .ok_or(Error::EntryPointNotFound)?; - - // Generate a map with names required to write the module - let mut names = crate::FastHashMap::default(); - let mut namer = proc::Namer::default(); - namer.reset( - module, - &keywords::RESERVED_KEYWORD_SET, - proc::CaseInsensitiveKeywordSet::empty(), - &[ - "gl_", // all GL built-in variables - "_group", // all normal bindings - "_push_constant_binding_", // all push constant bindings - ], - &mut names, - ); - - // Build the instance - let mut this = Self { - module, - info, - out, - options, - policies, - - namer, - features: FeaturesManager::new(), - names, - reflection_names_globals: crate::FastHashMap::default(), - entry_point: &module.entry_points[ep_idx], - entry_point_idx: ep_idx as u16, - multiview: pipeline_options.multiview, - block_id: IdGenerator::default(), - named_expressions: Default::default(), - need_bake_expressions: Default::default(), - continue_ctx: back::continue_forward::ContinueCtx::default(), - varying: Default::default(), - clip_distance_count: 0, - }; - - // Find all features required to print this module - this.collect_required_features()?; - - Ok(this) - } - - /// Writes the [`Module`](crate::Module) as glsl to the output - /// - /// # Notes - /// If an error occurs while writing, the output might have been written partially - /// - /// # Panics - /// Might panic if the module is invalid - pub fn write(&mut self) -> Result<ReflectionInfo, Error> { - // We use `writeln!(self.out)` throughout the write to add newlines - // to make the output more readable - - let es = self.options.version.is_es(); - - // Write the version (It must be the first thing or it isn't a valid glsl output) - writeln!(self.out, "#version {}", self.options.version)?; - // Write all the needed extensions - // - // This used to be the last thing being written as it allowed to search for features while - // writing the module saving some loops but some older versions (420 or less) required the - // extensions to appear before being used, even though extensions are part of the - // preprocessor not the processor ¯\_(ツ)_/¯ - self.features.write(self.options, &mut self.out)?; - - // glsl es requires a precision to be specified for floats and ints - // TODO: Should this be user configurable? - if es { - writeln!(self.out)?; - writeln!(self.out, "precision highp float;")?; - writeln!(self.out, "precision highp int;")?; - writeln!(self.out)?; - } - - if self.entry_point.stage == ShaderStage::Compute { - let workgroup_size = self.entry_point.workgroup_size; - writeln!( - self.out, - "layout(local_size_x = {}, local_size_y = {}, local_size_z = {}) in;", - workgroup_size[0], workgroup_size[1], workgroup_size[2] - )?; - writeln!(self.out)?; - } - - if self.entry_point.stage == ShaderStage::Vertex - && !self - .options - .writer_flags - .contains(WriterFlags::DRAW_PARAMETERS) - && self.features.contains(Features::INSTANCE_INDEX) - { - writeln!(self.out, "uniform uint {FIRST_INSTANCE_BINDING};")?; - writeln!(self.out)?; - } - - // Enable early depth tests if needed - if let Some(early_depth_test) = self.entry_point.early_depth_test { - // If early depth test is supported for this version of GLSL - if self.options.version.supports_early_depth_test() { - match early_depth_test { - crate::EarlyDepthTest::Force => { - writeln!(self.out, "layout(early_fragment_tests) in;")?; - } - crate::EarlyDepthTest::Allow { conservative, .. } => { - use crate::ConservativeDepth as Cd; - let depth = match conservative { - Cd::GreaterEqual => "greater", - Cd::LessEqual => "less", - Cd::Unchanged => "unchanged", - }; - writeln!(self.out, "layout (depth_{depth}) out float gl_FragDepth;")?; - } - } - } else { - log::warn!( - "Early depth testing is not supported for this version of GLSL: {}", - self.options.version - ); - } - } - - if self.entry_point.stage == ShaderStage::Vertex && self.options.version.is_webgl() { - if let Some(multiview) = self.multiview.as_ref() { - writeln!(self.out, "layout(num_views = {multiview}) in;")?; - writeln!(self.out)?; - } - } - - // Write struct types. - // - // This are always ordered because the IR is structured in a way that - // you can't make a struct without adding all of its members first. - for (handle, ty) in self.module.types.iter() { - if let TypeInner::Struct { ref members, .. } = ty.inner { - let struct_name = &self.names[&NameKey::Type(handle)]; - - // Structures ending with runtime-sized arrays can only be - // rendered as shader storage blocks in GLSL, not stand-alone - // struct types. - if !self.module.types[members.last().unwrap().ty] - .inner - .is_dynamically_sized(&self.module.types) - { - write!(self.out, "struct {struct_name} ")?; - self.write_struct_body(handle, members)?; - writeln!(self.out, ";")?; - } - } - } - - // Write functions for special types. - for (type_key, struct_ty) in self.module.special_types.predeclared_types.iter() { - match type_key { - &crate::PredeclaredType::ModfResult { size, scalar } - | &crate::PredeclaredType::FrexpResult { size, scalar } => { - let struct_name = &self.names[&NameKey::Type(*struct_ty)]; - let arg_type_name_owner; - let arg_type_name = if let Some(size) = size { - arg_type_name_owner = format!( - "{}vec{}", - if scalar.width == 8 { "d" } else { "" }, - size as u8 - ); - &arg_type_name_owner - } else if scalar.width == 8 { - "double" - } else { - "float" - }; - - let other_type_name_owner; - let (defined_func_name, called_func_name, other_type_name) = - if matches!(type_key, &crate::PredeclaredType::ModfResult { .. }) { - (MODF_FUNCTION, "modf", arg_type_name) - } else { - let other_type_name = if let Some(size) = size { - other_type_name_owner = format!("ivec{}", size as u8); - &other_type_name_owner - } else { - "int" - }; - (FREXP_FUNCTION, "frexp", other_type_name) - }; - - writeln!(self.out)?; - if !self.options.version.supports_frexp_function() - && matches!(type_key, &crate::PredeclaredType::FrexpResult { .. }) - { - writeln!( - self.out, - "{struct_name} {defined_func_name}({arg_type_name} arg) {{ - {other_type_name} other = arg == {arg_type_name}(0) ? {other_type_name}(0) : {other_type_name}({arg_type_name}(1) + log2(arg)); - {arg_type_name} fract = arg * exp2({arg_type_name}(-other)); - return {struct_name}(fract, other); -}}", - )?; - } else { - writeln!( - self.out, - "{struct_name} {defined_func_name}({arg_type_name} arg) {{ - {other_type_name} other; - {arg_type_name} fract = {called_func_name}(arg, other); - return {struct_name}(fract, other); -}}", - )?; - } - } - &crate::PredeclaredType::AtomicCompareExchangeWeakResult(_) => { - // Handled by the general struct writing loop earlier. - } - } - } - - // Write all named constants - let mut constants = self - .module - .constants - .iter() - .filter(|&(_, c)| c.name.is_some()) - .peekable(); - while let Some((handle, _)) = constants.next() { - self.write_global_constant(handle)?; - // Add extra newline for readability on last iteration - if constants.peek().is_none() { - writeln!(self.out)?; - } - } - - let ep_info = self.info.get_entry_point(self.entry_point_idx as usize); - - // Write the globals - // - // Unless explicitly disabled with WriterFlags::INCLUDE_UNUSED_ITEMS, - // we filter all globals that aren't used by the selected entry point as they might be - // interfere with each other (i.e. two globals with the same location but different with - // different classes) - let include_unused = self - .options - .writer_flags - .contains(WriterFlags::INCLUDE_UNUSED_ITEMS); - for (handle, global) in self.module.global_variables.iter() { - let is_unused = ep_info[handle].is_empty(); - if !include_unused && is_unused { - continue; - } - - match self.module.types[global.ty].inner { - // We treat images separately because they might require - // writing the storage format - TypeInner::Image { - mut dim, - arrayed, - class, - } => { - // Gather the storage format if needed - let storage_format_access = match self.module.types[global.ty].inner { - TypeInner::Image { - class: crate::ImageClass::Storage { format, access }, - .. - } => Some((format, access)), - _ => None, - }; - - if dim == crate::ImageDimension::D1 && es { - dim = crate::ImageDimension::D2 - } - - // Gether the location if needed - let layout_binding = if self.options.version.supports_explicit_locations() { - let br = global.binding.as_ref().unwrap(); - self.options.binding_map.get(br).cloned() - } else { - None - }; - - // Write all the layout qualifiers - if layout_binding.is_some() || storage_format_access.is_some() { - write!(self.out, "layout(")?; - if let Some(binding) = layout_binding { - write!(self.out, "binding = {binding}")?; - } - if let Some((format, _)) = storage_format_access { - let format_str = glsl_storage_format(format)?; - let separator = match layout_binding { - Some(_) => ",", - None => "", - }; - write!(self.out, "{separator}{format_str}")?; - } - write!(self.out, ") ")?; - } - - if let Some((_, access)) = storage_format_access { - self.write_storage_access(access)?; - } - - // All images in glsl are `uniform` - // The trailing space is important - write!(self.out, "uniform ")?; - - // write the type - // - // This is way we need the leading space because `write_image_type` doesn't add - // any spaces at the beginning or end - self.write_image_type(dim, arrayed, class)?; - - // Finally write the name and end the global with a `;` - // The leading space is important - let global_name = self.get_global_name(handle, global); - writeln!(self.out, " {global_name};")?; - writeln!(self.out)?; - - self.reflection_names_globals.insert(handle, global_name); - } - // glsl has no concept of samplers so we just ignore it - TypeInner::Sampler { .. } => continue, - // All other globals are written by `write_global` - _ => { - self.write_global(handle, global)?; - // Add a newline (only for readability) - writeln!(self.out)?; - } - } - } - - for arg in self.entry_point.function.arguments.iter() { - self.write_varying(arg.binding.as_ref(), arg.ty, false)?; - } - if let Some(ref result) = self.entry_point.function.result { - self.write_varying(result.binding.as_ref(), result.ty, true)?; - } - writeln!(self.out)?; - - // Write all regular functions - for (handle, function) in self.module.functions.iter() { - // Check that the function doesn't use globals that aren't supported - // by the current entry point - if !include_unused && !ep_info.dominates_global_use(&self.info[handle]) { - continue; - } - - let fun_info = &self.info[handle]; - - // Skip functions that that are not compatible with this entry point's stage. - // - // When validation is enabled, it rejects modules whose entry points try to call - // incompatible functions, so if we got this far, then any functions incompatible - // with our selected entry point must not be used. - // - // When validation is disabled, `fun_info.available_stages` is always just - // `ShaderStages::all()`, so this will write all functions in the module, and - // the downstream GLSL compiler will catch any problems. - if !fun_info.available_stages.contains(ep_info.available_stages) { - continue; - } - - // Write the function - self.write_function(back::FunctionType::Function(handle), function, fun_info)?; - - writeln!(self.out)?; - } - - self.write_function( - back::FunctionType::EntryPoint(self.entry_point_idx), - &self.entry_point.function, - ep_info, - )?; - - // Add newline at the end of file - writeln!(self.out)?; - - // Collect all reflection info and return it to the user - self.collect_reflection_info() - } - - fn write_array_size( - &mut self, - base: Handle<crate::Type>, - size: crate::ArraySize, - ) -> BackendResult { - write!(self.out, "[")?; - - // Write the array size - // Writes nothing if `IndexableLength::Dynamic` - match size.resolve(self.module.to_ctx())? { - proc::IndexableLength::Known(size) => { - write!(self.out, "{size}")?; - } - proc::IndexableLength::Dynamic => (), - } - - write!(self.out, "]")?; - - if let TypeInner::Array { - base: next_base, - size: next_size, - .. - } = self.module.types[base].inner - { - self.write_array_size(next_base, next_size)?; - } - - Ok(()) - } - - /// Helper method used to write value types - /// - /// # Notes - /// Adds no trailing or leading whitespace - fn write_value_type(&mut self, inner: &TypeInner) -> BackendResult { - match *inner { - // Scalars are simple we just get the full name from `glsl_scalar` - TypeInner::Scalar(scalar) - | TypeInner::Atomic(scalar) - | TypeInner::ValuePointer { - size: None, - scalar, - space: _, - } => write!(self.out, "{}", glsl_scalar(scalar)?.full)?, - // Vectors are just `gvecN` where `g` is the scalar prefix and `N` is the vector size - TypeInner::Vector { size, scalar } - | TypeInner::ValuePointer { - size: Some(size), - scalar, - space: _, - } => write!(self.out, "{}vec{}", glsl_scalar(scalar)?.prefix, size as u8)?, - // Matrices are written with `gmatMxN` where `g` is the scalar prefix (only floats and - // doubles are allowed), `M` is the columns count and `N` is the rows count - // - // glsl supports a matrix shorthand `gmatN` where `N` = `M` but it doesn't justify the - // extra branch to write matrices this way - TypeInner::Matrix { - columns, - rows, - scalar, - } => write!( - self.out, - "{}mat{}x{}", - glsl_scalar(scalar)?.prefix, - columns as u8, - rows as u8 - )?, - // GLSL arrays are written as `type name[size]` - // Here we only write the size of the array i.e. `[size]` - // Base `type` and `name` should be written outside - TypeInner::Array { base, size, .. } => self.write_array_size(base, size)?, - // Write all variants instead of `_` so that if new variants are added a - // no exhaustiveness error is thrown - TypeInner::Pointer { .. } - | TypeInner::Struct { .. } - | TypeInner::Image { .. } - | TypeInner::Sampler { .. } - | TypeInner::AccelerationStructure { .. } - | TypeInner::RayQuery { .. } - | TypeInner::BindingArray { .. } => { - return Err(Error::Custom(format!("Unable to write type {inner:?}"))) - } - } - - Ok(()) - } - - /// Helper method used to write non image/sampler types - /// - /// # Notes - /// Adds no trailing or leading whitespace - fn write_type(&mut self, ty: Handle<crate::Type>) -> BackendResult { - match self.module.types[ty].inner { - // glsl has no pointer types so just write types as normal and loads are skipped - TypeInner::Pointer { base, .. } => self.write_type(base), - // glsl structs are written as just the struct name - TypeInner::Struct { .. } => { - // Get the struct name - let name = &self.names[&NameKey::Type(ty)]; - write!(self.out, "{name}")?; - Ok(()) - } - // glsl array has the size separated from the base type - TypeInner::Array { base, .. } => self.write_type(base), - ref other => self.write_value_type(other), - } - } - - /// Helper method to write a image type - /// - /// # Notes - /// Adds no leading or trailing whitespace - fn write_image_type( - &mut self, - dim: crate::ImageDimension, - arrayed: bool, - class: crate::ImageClass, - ) -> BackendResult { - // glsl images consist of four parts the scalar prefix, the image "type", the dimensions - // and modifiers - // - // There exists two image types - // - sampler - for sampled images - // - image - for storage images - // - // There are three possible modifiers that can be used together and must be written in - // this order to be valid - // - MS - used if it's a multisampled image - // - Array - used if it's an image array - // - Shadow - used if it's a depth image - use crate::ImageClass as Ic; - use crate::Scalar as S; - let float = S { - kind: crate::ScalarKind::Float, - width: 4, - }; - let (base, scalar, ms, comparison) = match class { - Ic::Sampled { kind, multi: true } => ("sampler", S { kind, width: 4 }, "MS", ""), - Ic::Sampled { kind, multi: false } => ("sampler", S { kind, width: 4 }, "", ""), - Ic::Depth { multi: true } => ("sampler", float, "MS", ""), - Ic::Depth { multi: false } => ("sampler", float, "", "Shadow"), - Ic::Storage { format, .. } => ("image", format.into(), "", ""), - Ic::External => unimplemented!(), - }; - - let precision = if self.options.version.is_es() { - "highp " - } else { - "" - }; - - write!( - self.out, - "{}{}{}{}{}{}{}", - precision, - glsl_scalar(scalar)?.prefix, - base, - glsl_dimension(dim), - ms, - if arrayed { "Array" } else { "" }, - comparison - )?; - - Ok(()) - } - - /// Helper method used by [Self::write_global] to write just the layout part of - /// a non image/sampler global variable, if applicable. - /// - /// # Notes - /// - /// Adds trailing whitespace if any layout qualifier is written - fn write_global_layout(&mut self, global: &crate::GlobalVariable) -> BackendResult { - // Determine which (if any) explicit memory layout to use, and whether we support it - let layout = match global.space { - crate::AddressSpace::Uniform => { - if !self.options.version.supports_std140_layout() { - return Err(Error::Custom( - "Uniform address space requires std140 layout support".to_string(), - )); - } - - Some("std140") - } - crate::AddressSpace::Storage { .. } => { - if !self.options.version.supports_std430_layout() { - return Err(Error::Custom( - "Storage address space requires std430 layout support".to_string(), - )); - } - - Some("std430") - } - _ => None, - }; - - // If our version supports explicit layouts, we can also output the explicit binding - // if we have it - if self.options.version.supports_explicit_locations() { - if let Some(ref br) = global.binding { - match self.options.binding_map.get(br) { - Some(binding) => { - write!(self.out, "layout(")?; - - if let Some(layout) = layout { - write!(self.out, "{layout}, ")?; - } - - write!(self.out, "binding = {binding}) ")?; - - return Ok(()); - } - None => { - log::debug!("unassigned binding for {:?}", global.name); - } - } - } - } - - // Either no explicit bindings are supported or we didn't have any. - // Write just the memory layout. - if let Some(layout) = layout { - write!(self.out, "layout({layout}) ")?; - } - - Ok(()) - } - - /// Helper method used to write non images/sampler globals - /// - /// # Notes - /// Adds a newline - /// - /// # Panics - /// If the global has type sampler - fn write_global( - &mut self, - handle: Handle<crate::GlobalVariable>, - global: &crate::GlobalVariable, - ) -> BackendResult { - self.write_global_layout(global)?; - - if let crate::AddressSpace::Storage { access } = global.space { - self.write_storage_access(access)?; - } - - if let Some(storage_qualifier) = glsl_storage_qualifier(global.space) { - write!(self.out, "{storage_qualifier} ")?; - } - - match global.space { - crate::AddressSpace::Private => { - self.write_simple_global(handle, global)?; - } - crate::AddressSpace::WorkGroup => { - self.write_simple_global(handle, global)?; - } - crate::AddressSpace::PushConstant => { - self.write_simple_global(handle, global)?; - } - crate::AddressSpace::Uniform => { - self.write_interface_block(handle, global)?; - } - crate::AddressSpace::Storage { .. } => { - self.write_interface_block(handle, global)?; - } - crate::AddressSpace::TaskPayload => { - self.write_interface_block(handle, global)?; - } - // A global variable in the `Function` address space is a - // contradiction in terms. - crate::AddressSpace::Function => unreachable!(), - // Textures and samplers are handled directly in `Writer::write`. - crate::AddressSpace::Handle => unreachable!(), - } - - Ok(()) - } - - fn write_simple_global( - &mut self, - handle: Handle<crate::GlobalVariable>, - global: &crate::GlobalVariable, - ) -> BackendResult { - self.write_type(global.ty)?; - write!(self.out, " ")?; - self.write_global_name(handle, global)?; - - if let TypeInner::Array { base, size, .. } = self.module.types[global.ty].inner { - self.write_array_size(base, size)?; - } - - if global.space.initializable() && is_value_init_supported(self.module, global.ty) { - write!(self.out, " = ")?; - if let Some(init) = global.init { - self.write_const_expr(init, &self.module.global_expressions)?; - } else { - self.write_zero_init_value(global.ty)?; - } - } - - writeln!(self.out, ";")?; - - if let crate::AddressSpace::PushConstant = global.space { - let global_name = self.get_global_name(handle, global); - self.reflection_names_globals.insert(handle, global_name); - } - - Ok(()) - } - - /// Write an interface block for a single Naga global. - /// - /// Write `block_name { members }`. Since `block_name` must be unique - /// between blocks and structs, we add `_block_ID` where `ID` is a - /// `IdGenerator` generated number. Write `members` in the same way we write - /// a struct's members. - fn write_interface_block( - &mut self, - handle: Handle<crate::GlobalVariable>, - global: &crate::GlobalVariable, - ) -> BackendResult { - // Write the block name, it's just the struct name appended with `_block_ID` - let ty_name = &self.names[&NameKey::Type(global.ty)]; - let block_name = format!( - "{}_block_{}{:?}", - // avoid double underscores as they are reserved in GLSL - ty_name.trim_end_matches('_'), - self.block_id.generate(), - self.entry_point.stage, - ); - write!(self.out, "{block_name} ")?; - self.reflection_names_globals.insert(handle, block_name); - - match self.module.types[global.ty].inner { - TypeInner::Struct { ref members, .. } - if self.module.types[members.last().unwrap().ty] - .inner - .is_dynamically_sized(&self.module.types) => - { - // Structs with dynamically sized arrays must have their - // members lifted up as members of the interface block. GLSL - // can't write such struct types anyway. - self.write_struct_body(global.ty, members)?; - write!(self.out, " ")?; - self.write_global_name(handle, global)?; - } - _ => { - // A global of any other type is written as the sole member - // of the interface block. Since the interface block is - // anonymous, this becomes visible in the global scope. - write!(self.out, "{{ ")?; - self.write_type(global.ty)?; - write!(self.out, " ")?; - self.write_global_name(handle, global)?; - if let TypeInner::Array { base, size, .. } = self.module.types[global.ty].inner { - self.write_array_size(base, size)?; - } - write!(self.out, "; }}")?; - } - } - - writeln!(self.out, ";")?; - - Ok(()) - } - - /// Helper method used to find which expressions of a given function require baking - /// - /// # Notes - /// Clears `need_bake_expressions` set before adding to it - fn update_expressions_to_bake(&mut self, func: &crate::Function, info: &valid::FunctionInfo) { - use crate::Expression; - self.need_bake_expressions.clear(); - for (fun_handle, expr) in func.expressions.iter() { - let expr_info = &info[fun_handle]; - let min_ref_count = func.expressions[fun_handle].bake_ref_count(); - if min_ref_count <= expr_info.ref_count { - self.need_bake_expressions.insert(fun_handle); - } - - let inner = expr_info.ty.inner_with(&self.module.types); - - if let Expression::Math { - fun, - arg, - arg1, - arg2, - .. - } = *expr - { - match fun { - crate::MathFunction::Dot => { - // if the expression is a Dot product with integer arguments, - // then the args needs baking as well - if let TypeInner::Scalar(crate::Scalar { - kind: crate::ScalarKind::Sint | crate::ScalarKind::Uint, - .. - }) = *inner - { - self.need_bake_expressions.insert(arg); - self.need_bake_expressions.insert(arg1.unwrap()); - } - } - crate::MathFunction::Dot4U8Packed | crate::MathFunction::Dot4I8Packed => { - self.need_bake_expressions.insert(arg); - self.need_bake_expressions.insert(arg1.unwrap()); - } - crate::MathFunction::Pack4xI8 - | crate::MathFunction::Pack4xU8 - | crate::MathFunction::Pack4xI8Clamp - | crate::MathFunction::Pack4xU8Clamp - | crate::MathFunction::Unpack4xI8 - | crate::MathFunction::Unpack4xU8 - | crate::MathFunction::QuantizeToF16 => { - self.need_bake_expressions.insert(arg); - } - /* crate::MathFunction::Pack4x8unorm | */ - crate::MathFunction::Unpack4x8snorm - if !self.options.version.supports_pack_unpack_4x8() => - { - // We have a fallback if the platform doesn't natively support these - self.need_bake_expressions.insert(arg); - } - /* crate::MathFunction::Pack4x8unorm | */ - crate::MathFunction::Unpack4x8unorm - if !self.options.version.supports_pack_unpack_4x8() => - { - self.need_bake_expressions.insert(arg); - } - /* crate::MathFunction::Pack2x16snorm | */ - crate::MathFunction::Unpack2x16snorm - if !self.options.version.supports_pack_unpack_snorm_2x16() => - { - self.need_bake_expressions.insert(arg); - } - /* crate::MathFunction::Pack2x16unorm | */ - crate::MathFunction::Unpack2x16unorm - if !self.options.version.supports_pack_unpack_unorm_2x16() => - { - self.need_bake_expressions.insert(arg); - } - crate::MathFunction::ExtractBits => { - // Only argument 1 is re-used. - self.need_bake_expressions.insert(arg1.unwrap()); - } - crate::MathFunction::InsertBits => { - // Only argument 2 is re-used. - self.need_bake_expressions.insert(arg2.unwrap()); - } - crate::MathFunction::CountLeadingZeros => { - if let Some(crate::ScalarKind::Sint) = inner.scalar_kind() { - self.need_bake_expressions.insert(arg); - } - } - _ => {} - } - } - } - - for statement in func.body.iter() { - match *statement { - crate::Statement::Atomic { - fun: crate::AtomicFunction::Exchange { compare: Some(cmp) }, - .. - } => { - self.need_bake_expressions.insert(cmp); - } - _ => {} - } - } - } - - /// Helper method used to get a name for a global - /// - /// Globals have different naming schemes depending on their binding: - /// - Globals without bindings use the name from the [`Namer`](crate::proc::Namer) - /// - Globals with resource binding are named `_group_X_binding_Y` where `X` - /// is the group and `Y` is the binding - fn get_global_name( - &self, - handle: Handle<crate::GlobalVariable>, - global: &crate::GlobalVariable, - ) -> String { - match (&global.binding, global.space) { - (&Some(ref br), _) => { - format!( - "_group_{}_binding_{}_{}", - br.group, - br.binding, - self.entry_point.stage.to_str() - ) - } - (&None, crate::AddressSpace::PushConstant) => { - format!("_push_constant_binding_{}", self.entry_point.stage.to_str()) - } - (&None, _) => self.names[&NameKey::GlobalVariable(handle)].clone(), - } - } - - /// Helper method used to write a name for a global without additional heap allocation - fn write_global_name( - &mut self, - handle: Handle<crate::GlobalVariable>, - global: &crate::GlobalVariable, - ) -> BackendResult { - match (&global.binding, global.space) { - (&Some(ref br), _) => write!( - self.out, - "_group_{}_binding_{}_{}", - br.group, - br.binding, - self.entry_point.stage.to_str() - )?, - (&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)] - )?, - } - - Ok(()) - } - - /// Write a GLSL global that will carry a Naga entry point's argument or return value. - /// - /// A Naga entry point's arguments and return value are rendered in GLSL as - /// variables at global scope with the `in` and `out` storage qualifiers. - /// The code we generate for `main` loads from all the `in` globals into - /// appropriately named locals. Before it returns, `main` assigns the - /// components of its return value into all the `out` globals. - /// - /// This function writes a declaration for one such GLSL global, - /// representing a value passed into or returned from [`self.entry_point`] - /// that has a [`Location`] binding. The global's name is generated based on - /// the location index and the shader stages being connected; see - /// [`VaryingName`]. This means we don't need to know the names of - /// arguments, just their types and bindings. - /// - /// Emit nothing for entry point arguments or return values with [`BuiltIn`] - /// bindings; `main` will read from or assign to the appropriate GLSL - /// special variable; these are pre-declared. As an exception, we do declare - /// `gl_Position` or `gl_FragCoord` with the `invariant` qualifier if - /// needed. - /// - /// Use `output` together with [`self.entry_point.stage`] to determine which - /// shader stages are being connected, and choose the `in` or `out` storage - /// qualifier. - /// - /// [`self.entry_point`]: Writer::entry_point - /// [`self.entry_point.stage`]: crate::EntryPoint::stage - /// [`Location`]: crate::Binding::Location - /// [`BuiltIn`]: crate::Binding::BuiltIn - fn write_varying( - &mut self, - binding: Option<&crate::Binding>, - ty: Handle<crate::Type>, - output: bool, - ) -> Result<(), Error> { - // For a struct, emit a separate global for each member with a binding. - if let TypeInner::Struct { ref members, .. } = self.module.types[ty].inner { - for member in members { - self.write_varying(member.binding.as_ref(), member.ty, output)?; - } - return Ok(()); - } - - let binding = match binding { - None => return Ok(()), - Some(binding) => binding, - }; - - let (location, interpolation, sampling, blend_src) = match *binding { - crate::Binding::Location { - location, - interpolation, - sampling, - blend_src, - per_primitive: _, - } => (location, interpolation, sampling, blend_src), - crate::Binding::BuiltIn(built_in) => { - match built_in { - crate::BuiltIn::Position { invariant: true } => { - match (self.options.version, self.entry_point.stage) { - ( - Version::Embedded { - version: 300, - is_webgl: true, - }, - ShaderStage::Fragment, - ) => { - // `invariant gl_FragCoord` is not allowed in WebGL2 and possibly - // OpenGL ES in general (waiting on confirmation). - // - // See https://github.com/KhronosGroup/WebGL/issues/3518 - } - _ => { - writeln!( - self.out, - "invariant {};", - glsl_built_in( - built_in, - VaryingOptions::from_writer_options(self.options, output) - ) - )?; - } - } - } - crate::BuiltIn::ClipDistance => { - // Re-declare `gl_ClipDistance` with number of clip planes. - let TypeInner::Array { size, .. } = self.module.types[ty].inner else { - unreachable!(); - }; - let proc::IndexableLength::Known(size) = - size.resolve(self.module.to_ctx())? - else { - unreachable!(); - }; - self.clip_distance_count = size; - writeln!(self.out, "out float gl_ClipDistance[{size}];")?; - } - _ => {} - } - return Ok(()); - } - }; - - // Write the interpolation modifier if needed - // - // We ignore all interpolation and auxiliary modifiers that aren't used in fragment - // shaders' input globals or vertex shaders' output globals. - let emit_interpolation_and_auxiliary = match self.entry_point.stage { - ShaderStage::Vertex => output, - ShaderStage::Fragment => !output, - ShaderStage::Compute => false, - ShaderStage::Task | ShaderStage::Mesh => unreachable!(), - }; - - // Write the I/O locations, if allowed - let io_location = if self.options.version.supports_explicit_locations() - || !emit_interpolation_and_auxiliary - { - if self.options.version.supports_io_locations() { - if let Some(blend_src) = blend_src { - write!( - self.out, - "layout(location = {location}, index = {blend_src}) " - )?; - } else { - write!(self.out, "layout(location = {location}) ")?; - } - None - } else { - Some(VaryingLocation { - location, - index: blend_src.unwrap_or(0), - }) - } - } else { - None - }; - - // Write the interpolation qualifier. - if let Some(interp) = interpolation { - if emit_interpolation_and_auxiliary { - write!(self.out, "{} ", glsl_interpolation(interp))?; - } - } - - // Write the sampling auxiliary qualifier. - // - // Before GLSL 4.2, the `centroid` and `sample` qualifiers were required to appear - // immediately before the `in` / `out` qualifier, so we'll just follow that rule - // here, regardless of the version. - if let Some(sampling) = sampling { - if emit_interpolation_and_auxiliary { - if let Some(qualifier) = glsl_sampling(sampling)? { - write!(self.out, "{qualifier} ")?; - } - } - } - - // Write the input/output qualifier. - write!(self.out, "{} ", if output { "out" } else { "in" })?; - - // Write the type - // `write_type` adds no leading or trailing spaces - self.write_type(ty)?; - - // Finally write the global name and end the global with a `;` and a newline - // Leading space is important - let vname = VaryingName { - binding: &crate::Binding::Location { - location, - interpolation: None, - sampling: None, - blend_src, - per_primitive: false, - }, - stage: self.entry_point.stage, - options: VaryingOptions::from_writer_options(self.options, output), - }; - writeln!(self.out, " {vname};")?; - - if let Some(location) = io_location { - self.varying.insert(vname.to_string(), location); - } - - Ok(()) - } - - /// Helper method used to write functions (both entry points and regular functions) - /// - /// # Notes - /// Adds a newline - fn write_function( - &mut self, - ty: back::FunctionType, - func: &crate::Function, - info: &valid::FunctionInfo, - ) -> BackendResult { - // Create a function context for the function being written - let ctx = back::FunctionCtx { - ty, - info, - expressions: &func.expressions, - named_expressions: &func.named_expressions, - }; - - self.named_expressions.clear(); - self.update_expressions_to_bake(func, info); - - // Write the function header - // - // glsl headers are the same as in c: - // `ret_type name(args)` - // `ret_type` is the return type - // `name` is the function name - // `args` is a comma separated list of `type name` - // | - `type` is the argument type - // | - `name` is the argument name - - // Start by writing the return type if any otherwise write void - // This is the only place where `void` is a valid type - // (though it's more a keyword than a type) - if let back::FunctionType::EntryPoint(_) = ctx.ty { - write!(self.out, "void")?; - } else if let Some(ref result) = func.result { - self.write_type(result.ty)?; - if let TypeInner::Array { base, size, .. } = self.module.types[result.ty].inner { - self.write_array_size(base, size)? - } - } else { - write!(self.out, "void")?; - } - - // Write the function name and open parentheses for the argument list - let function_name = match ctx.ty { - back::FunctionType::Function(handle) => &self.names[&NameKey::Function(handle)], - back::FunctionType::EntryPoint(_) => "main", - }; - write!(self.out, " {function_name}(")?; - - // Write the comma separated argument list - // - // We need access to `Self` here so we use the reference passed to the closure as an - // argument instead of capturing as that would cause a borrow checker error - let arguments = match ctx.ty { - back::FunctionType::EntryPoint(_) => &[][..], - back::FunctionType::Function(_) => &func.arguments, - }; - let arguments: Vec<_> = arguments - .iter() - .enumerate() - .filter(|&(_, arg)| match self.module.types[arg.ty].inner { - TypeInner::Sampler { .. } => false, - _ => true, - }) - .collect(); - self.write_slice(&arguments, |this, _, &(i, arg)| { - // Write the argument type - match this.module.types[arg.ty].inner { - // We treat images separately because they might require - // writing the storage format - TypeInner::Image { - dim, - arrayed, - class, - } => { - // Write the storage format if needed - if let TypeInner::Image { - class: crate::ImageClass::Storage { format, .. }, - .. - } = this.module.types[arg.ty].inner - { - write!(this.out, "layout({}) ", glsl_storage_format(format)?)?; - } - - // write the type - // - // This is way we need the leading space because `write_image_type` doesn't add - // any spaces at the beginning or end - this.write_image_type(dim, arrayed, class)?; - } - TypeInner::Pointer { base, .. } => { - // write parameter qualifiers - write!(this.out, "inout ")?; - this.write_type(base)?; - } - // All other types are written by `write_type` - _ => { - this.write_type(arg.ty)?; - } - } - - // Write the argument name - // The leading space is important - write!(this.out, " {}", &this.names[&ctx.argument_key(i as u32)])?; - - // Write array size - match this.module.types[arg.ty].inner { - TypeInner::Array { base, size, .. } => { - this.write_array_size(base, size)?; - } - TypeInner::Pointer { base, .. } => { - if let TypeInner::Array { base, size, .. } = this.module.types[base].inner { - this.write_array_size(base, size)?; - } - } - _ => {} - } - - Ok(()) - })?; - - // Close the parentheses and open braces to start the function body - writeln!(self.out, ") {{")?; - - if self.options.zero_initialize_workgroup_memory - && ctx.ty.is_compute_like_entry_point(self.module) - { - self.write_workgroup_variables_initialization(&ctx)?; - } - - // Compose the function arguments from globals, in case of an entry point. - if let back::FunctionType::EntryPoint(ep_index) = ctx.ty { - let stage = self.module.entry_points[ep_index as usize].stage; - for (index, arg) in func.arguments.iter().enumerate() { - write!(self.out, "{}", back::INDENT)?; - self.write_type(arg.ty)?; - let name = &self.names[&NameKey::EntryPointArgument(ep_index, index as u32)]; - write!(self.out, " {name}")?; - write!(self.out, " = ")?; - match self.module.types[arg.ty].inner { - TypeInner::Struct { ref members, .. } => { - self.write_type(arg.ty)?; - write!(self.out, "(")?; - for (index, member) in members.iter().enumerate() { - let varying_name = VaryingName { - binding: member.binding.as_ref().unwrap(), - stage, - options: VaryingOptions::from_writer_options(self.options, false), - }; - if index != 0 { - write!(self.out, ", ")?; - } - write!(self.out, "{varying_name}")?; - } - writeln!(self.out, ");")?; - } - _ => { - let varying_name = VaryingName { - binding: arg.binding.as_ref().unwrap(), - stage, - options: VaryingOptions::from_writer_options(self.options, false), - }; - writeln!(self.out, "{varying_name};")?; - } - } - } - } - - // Write all function locals - // Locals are `type name (= init)?;` where the init part (including the =) are optional - // - // Always adds a newline - for (handle, local) in func.local_variables.iter() { - // Write indentation (only for readability) and the type - // `write_type` adds no trailing space - write!(self.out, "{}", back::INDENT)?; - self.write_type(local.ty)?; - - // Write the local name - // The leading space is important - write!(self.out, " {}", self.names[&ctx.name_key(handle)])?; - // Write size for array type - if let TypeInner::Array { base, size, .. } = self.module.types[local.ty].inner { - self.write_array_size(base, size)?; - } - // Write the local initializer if needed - if let Some(init) = local.init { - // Put the equal signal only if there's a initializer - // The leading and trailing spaces aren't needed but help with readability - write!(self.out, " = ")?; - - // Write the constant - // `write_constant` adds no trailing or leading space/newline - self.write_expr(init, &ctx)?; - } else if is_value_init_supported(self.module, local.ty) { - write!(self.out, " = ")?; - self.write_zero_init_value(local.ty)?; - } - - // Finish the local with `;` and add a newline (only for readability) - writeln!(self.out, ";")? - } - - // Write the function body (statement list) - for sta in func.body.iter() { - // Write a statement, the indentation should always be 1 when writing the function body - // `write_stmt` adds a newline - self.write_stmt(sta, &ctx, back::Level(1))?; - } - - // Close braces and add a newline - writeln!(self.out, "}}")?; - - Ok(()) - } - - fn write_workgroup_variables_initialization( - &mut self, - ctx: &back::FunctionCtx, - ) -> BackendResult { - let mut vars = self - .module - .global_variables - .iter() - .filter(|&(handle, var)| { - !ctx.info[handle].is_empty() && var.space == crate::AddressSpace::WorkGroup - }) - .peekable(); - - if vars.peek().is_some() { - let level = back::Level(1); - - writeln!(self.out, "{level}if (gl_LocalInvocationID == uvec3(0u)) {{")?; - - for (handle, var) in vars { - let name = &self.names[&NameKey::GlobalVariable(handle)]; - write!(self.out, "{}{} = ", level.next(), name)?; - self.write_zero_init_value(var.ty)?; - writeln!(self.out, ";")?; - } - - writeln!(self.out, "{level}}}")?; - self.write_control_barrier(crate::Barrier::WORK_GROUP, level)?; - } - - Ok(()) - } - - /// Write a list of comma separated `T` values using a writer function `F`. - /// - /// The writer function `F` receives a mutable reference to `self` that if needed won't cause - /// borrow checker issues (using for example a closure with `self` will cause issues), the - /// second argument is the 0 based index of the element on the list, and the last element is - /// a reference to the element `T` being written - /// - /// # Notes - /// - Adds no newlines or leading/trailing whitespace - /// - The last element won't have a trailing `,` - fn write_slice<T, F: FnMut(&mut Self, u32, &T) -> BackendResult>( - &mut self, - data: &[T], - mut f: F, - ) -> BackendResult { - // Loop through `data` invoking `f` for each element - for (index, item) in data.iter().enumerate() { - if index != 0 { - write!(self.out, ", ")?; - } - f(self, index as u32, item)?; - } - - Ok(()) - } - - /// Helper method used to write global constants - fn write_global_constant(&mut self, handle: Handle<crate::Constant>) -> BackendResult { - write!(self.out, "const ")?; - let constant = &self.module.constants[handle]; - self.write_type(constant.ty)?; - let name = &self.names[&NameKey::Constant(handle)]; - write!(self.out, " {name}")?; - if let TypeInner::Array { base, size, .. } = self.module.types[constant.ty].inner { - self.write_array_size(base, size)?; - } - write!(self.out, " = ")?; - self.write_const_expr(constant.init, &self.module.global_expressions)?; - writeln!(self.out, ";")?; - Ok(()) - } - - /// Helper method used to output a dot product as an arithmetic expression - /// - fn write_dot_product( - &mut self, - arg: Handle<crate::Expression>, - arg1: Handle<crate::Expression>, - size: usize, - ctx: &back::FunctionCtx, - ) -> BackendResult { - // Write parentheses around the dot product expression to prevent operators - // with different precedences from applying earlier. - write!(self.out, "(")?; - - // Cycle through all the components of the vector - for index in 0..size { - let component = back::COMPONENTS[index]; - // Write the addition to the previous product - // This will print an extra '+' at the beginning but that is fine in glsl - write!(self.out, " + ")?; - // Write the first vector expression, this expression is marked to be - // cached so unless it can't be cached (for example, it's a Constant) - // it shouldn't produce large expressions. - self.write_expr(arg, ctx)?; - // Access the current component on the first vector - write!(self.out, ".{component} * ")?; - // Write the second vector expression, this expression is marked to be - // cached so unless it can't be cached (for example, it's a Constant) - // it shouldn't produce large expressions. - self.write_expr(arg1, ctx)?; - // Access the current component on the second vector - write!(self.out, ".{component}")?; - } - - write!(self.out, ")")?; - Ok(()) - } - - /// Helper method used to write structs - /// - /// # Notes - /// Ends in a newline - fn write_struct_body( - &mut self, - handle: Handle<crate::Type>, - members: &[crate::StructMember], - ) -> BackendResult { - // glsl structs are written as in C - // `struct name() { members };` - // | `struct` is a keyword - // | `name` is the struct name - // | `members` is a semicolon separated list of `type name` - // | `type` is the member type - // | `name` is the member name - writeln!(self.out, "{{")?; - - for (idx, member) in members.iter().enumerate() { - // The indentation is only for readability - write!(self.out, "{}", back::INDENT)?; - - match self.module.types[member.ty].inner { - TypeInner::Array { - base, - size, - stride: _, - } => { - self.write_type(base)?; - write!( - self.out, - " {}", - &self.names[&NameKey::StructMember(handle, idx as u32)] - )?; - // Write [size] - self.write_array_size(base, size)?; - // Newline is important - writeln!(self.out, ";")?; - } - _ => { - // Write the member type - // Adds no trailing space - self.write_type(member.ty)?; - - // Write the member name and put a semicolon - // The leading space is important - // All members must have a semicolon even the last one - writeln!( - self.out, - " {};", - &self.names[&NameKey::StructMember(handle, idx as u32)] - )?; - } - } - } - - write!(self.out, "}}")?; - Ok(()) - } - - /// Helper method used to write statements - /// - /// # Notes - /// Always adds a newline - fn write_stmt( - &mut self, - sta: &crate::Statement, - ctx: &back::FunctionCtx, - level: back::Level, - ) -> BackendResult { - use crate::Statement; - - match *sta { - // This is where we can generate intermediate constants for some expression types. - Statement::Emit(ref range) => { - for handle in range.clone() { - let ptr_class = ctx.resolve_type(handle, &self.module.types).pointer_space(); - let expr_name = if ptr_class.is_some() { - // GLSL can't save a pointer-valued expression in a variable, - // but we shouldn't ever need to: they should never be named expressions, - // and none of the expression types flagged by bake_ref_count can be pointer-valued. - None - } else if let Some(name) = ctx.named_expressions.get(&handle) { - // Front end provides names for all variables at the start of writing. - // But we write them to step by step. We need to recache them - // Otherwise, we could accidentally write variable name instead of full expression. - // Also, we use sanitized names! It defense backend from generating variable with name from reserved keywords. - Some(self.namer.call(name)) - } else if self.need_bake_expressions.contains(&handle) { - Some(Baked(handle).to_string()) - } else { - None - }; - - // If we are going to write an `ImageLoad` next and the target image - // is sampled and we are using the `Restrict` policy for bounds - // checking images we need to write a local holding the clamped lod. - if let crate::Expression::ImageLoad { - image, - level: Some(level_expr), - .. - } = ctx.expressions[handle] - { - if let TypeInner::Image { - class: crate::ImageClass::Sampled { .. }, - .. - } = *ctx.resolve_type(image, &self.module.types) - { - if let proc::BoundsCheckPolicy::Restrict = self.policies.image_load { - write!(self.out, "{level}")?; - self.write_clamped_lod(ctx, handle, image, level_expr)? - } - } - } - - if let Some(name) = expr_name { - write!(self.out, "{level}")?; - self.write_named_expr(handle, name, handle, ctx)?; - } - } - } - // Blocks are simple we just need to write the block statements between braces - // We could also just print the statements but this is more readable and maps more - // closely to the IR - Statement::Block(ref block) => { - write!(self.out, "{level}")?; - writeln!(self.out, "{{")?; - for sta in block.iter() { - // Increase the indentation to help with readability - self.write_stmt(sta, ctx, level.next())? - } - writeln!(self.out, "{level}}}")? - } - // Ifs are written as in C: - // ``` - // if(condition) { - // accept - // } else { - // reject - // } - // ``` - Statement::If { - condition, - ref accept, - ref reject, - } => { - write!(self.out, "{level}")?; - write!(self.out, "if (")?; - self.write_expr(condition, ctx)?; - writeln!(self.out, ") {{")?; - - for sta in accept { - // Increase indentation to help with readability - self.write_stmt(sta, ctx, level.next())?; - } - - // If there are no statements in the reject block we skip writing it - // This is only for readability - if !reject.is_empty() { - writeln!(self.out, "{level}}} else {{")?; - - for sta in reject { - // Increase indentation to help with readability - self.write_stmt(sta, ctx, level.next())?; - } - } - - writeln!(self.out, "{level}}}")? - } - // Switch are written as in C: - // ``` - // switch (selector) { - // // Fallthrough - // case label: - // block - // // Non fallthrough - // case label: - // block - // break; - // default: - // block - // } - // ``` - // Where the `default` case happens isn't important but we put it last - // so that we don't need to print a `break` for it - Statement::Switch { - selector, - ref cases, - } => { - let l2 = level.next(); - // Some GLSL consumers may not handle switches with a single - // body correctly: See wgpu#4514. Write such switch statements - // as a `do {} while(false);` loop instead. - // - // Since doing so may inadvertently capture `continue` - // statements in the switch body, we must apply continue - // forwarding. See the `naga::back::continue_forward` module - // docs for details. - let one_body = cases - .iter() - .rev() - .skip(1) - .all(|case| case.fall_through && case.body.is_empty()); - if one_body { - // Unlike HLSL, in GLSL `continue_ctx` only needs to know - // about [`Switch`] statements that are being rendered as - // `do-while` loops. - if let Some(variable) = self.continue_ctx.enter_switch(&mut self.namer) { - writeln!(self.out, "{level}bool {variable} = false;",)?; - }; - writeln!(self.out, "{level}do {{")?; - // Note: Expressions have no side-effects so we don't need to emit selector expression. - - // Body - if let Some(case) = cases.last() { - for sta in case.body.iter() { - self.write_stmt(sta, ctx, l2)?; - } - } - // End do-while - writeln!(self.out, "{level}}} while(false);")?; - - // Handle any forwarded continue statements. - use back::continue_forward::ExitControlFlow; - let op = match self.continue_ctx.exit_switch() { - ExitControlFlow::None => None, - ExitControlFlow::Continue { variable } => Some(("continue", variable)), - ExitControlFlow::Break { variable } => Some(("break", variable)), - }; - if let Some((control_flow, variable)) = op { - writeln!(self.out, "{level}if ({variable}) {{")?; - writeln!(self.out, "{l2}{control_flow};")?; - writeln!(self.out, "{level}}}")?; - } - } else { - // Start the switch - write!(self.out, "{level}")?; - write!(self.out, "switch(")?; - self.write_expr(selector, ctx)?; - writeln!(self.out, ") {{")?; - - // Write all cases - for case in cases { - match case.value { - crate::SwitchValue::I32(value) => { - write!(self.out, "{l2}case {value}:")? - } - crate::SwitchValue::U32(value) => { - write!(self.out, "{l2}case {value}u:")? - } - crate::SwitchValue::Default => write!(self.out, "{l2}default:")?, - } - - let write_block_braces = !(case.fall_through && case.body.is_empty()); - if write_block_braces { - writeln!(self.out, " {{")?; - } else { - writeln!(self.out)?; - } - - for sta in case.body.iter() { - self.write_stmt(sta, ctx, l2.next())?; - } - - if !case.fall_through && case.body.last().is_none_or(|s| !s.is_terminator()) - { - writeln!(self.out, "{}break;", l2.next())?; - } - - if write_block_braces { - writeln!(self.out, "{l2}}}")?; - } - } - - writeln!(self.out, "{level}}}")? - } - } - // Loops in naga IR are based on wgsl loops, glsl can emulate the behaviour by using a - // while true loop and appending the continuing block to the body resulting on: - // ``` - // bool loop_init = true; - // while(true) { - // if (!loop_init) { <continuing> } - // loop_init = false; - // <body> - // } - // ``` - Statement::Loop { - ref body, - ref continuing, - break_if, - } => { - self.continue_ctx.enter_loop(); - if !continuing.is_empty() || break_if.is_some() { - let gate_name = self.namer.call("loop_init"); - writeln!(self.out, "{level}bool {gate_name} = true;")?; - writeln!(self.out, "{level}while(true) {{")?; - let l2 = level.next(); - let l3 = l2.next(); - writeln!(self.out, "{l2}if (!{gate_name}) {{")?; - for sta in continuing { - self.write_stmt(sta, ctx, l3)?; - } - if let Some(condition) = break_if { - write!(self.out, "{l3}if (")?; - self.write_expr(condition, ctx)?; - writeln!(self.out, ") {{")?; - writeln!(self.out, "{}break;", l3.next())?; - writeln!(self.out, "{l3}}}")?; - } - writeln!(self.out, "{l2}}}")?; - writeln!(self.out, "{}{} = false;", level.next(), gate_name)?; - } else { - writeln!(self.out, "{level}while(true) {{")?; - } - for sta in body { - self.write_stmt(sta, ctx, level.next())?; - } - writeln!(self.out, "{level}}}")?; - self.continue_ctx.exit_loop(); - } - // Break, continue and return as written as in C - // `break;` - Statement::Break => { - write!(self.out, "{level}")?; - writeln!(self.out, "break;")? - } - // `continue;` - Statement::Continue => { - // Sometimes we must render a `Continue` statement as a `break`. - // See the docs for the `back::continue_forward` module. - if let Some(variable) = self.continue_ctx.continue_encountered() { - writeln!(self.out, "{level}{variable} = true;",)?; - writeln!(self.out, "{level}break;")? - } else { - writeln!(self.out, "{level}continue;")? - } - } - // `return expr;`, `expr` is optional - Statement::Return { value } => { - write!(self.out, "{level}")?; - match ctx.ty { - back::FunctionType::Function(_) => { - write!(self.out, "return")?; - // Write the expression to be returned if needed - if let Some(expr) = value { - write!(self.out, " ")?; - self.write_expr(expr, ctx)?; - } - writeln!(self.out, ";")?; - } - back::FunctionType::EntryPoint(ep_index) => { - let mut has_point_size = false; - let ep = &self.module.entry_points[ep_index as usize]; - if let Some(ref result) = ep.function.result { - let value = value.unwrap(); - match self.module.types[result.ty].inner { - TypeInner::Struct { ref members, .. } => { - let temp_struct_name = match ctx.expressions[value] { - crate::Expression::Compose { .. } => { - let return_struct = "_tmp_return"; - write!( - self.out, - "{} {} = ", - &self.names[&NameKey::Type(result.ty)], - return_struct - )?; - self.write_expr(value, ctx)?; - writeln!(self.out, ";")?; - write!(self.out, "{level}")?; - Some(return_struct) - } - _ => None, - }; - - for (index, member) in members.iter().enumerate() { - if let Some(crate::Binding::BuiltIn( - crate::BuiltIn::PointSize, - )) = member.binding - { - has_point_size = true; - } - - let varying_name = VaryingName { - binding: member.binding.as_ref().unwrap(), - stage: ep.stage, - options: VaryingOptions::from_writer_options( - self.options, - true, - ), - }; - write!(self.out, "{varying_name} = ")?; - - if let Some(struct_name) = temp_struct_name { - write!(self.out, "{struct_name}")?; - } else { - self.write_expr(value, ctx)?; - } - - // Write field name - writeln!( - self.out, - ".{};", - &self.names - [&NameKey::StructMember(result.ty, index as u32)] - )?; - write!(self.out, "{level}")?; - } - } - _ => { - let name = VaryingName { - binding: result.binding.as_ref().unwrap(), - stage: ep.stage, - options: VaryingOptions::from_writer_options( - self.options, - true, - ), - }; - write!(self.out, "{name} = ")?; - self.write_expr(value, ctx)?; - writeln!(self.out, ";")?; - write!(self.out, "{level}")?; - } - } - } - - let is_vertex_stage = self.module.entry_points[ep_index as usize].stage - == ShaderStage::Vertex; - if is_vertex_stage - && self - .options - .writer_flags - .contains(WriterFlags::ADJUST_COORDINATE_SPACE) - { - writeln!( - self.out, - "gl_Position.yz = vec2(-gl_Position.y, gl_Position.z * 2.0 - gl_Position.w);", - )?; - write!(self.out, "{level}")?; - } - - if is_vertex_stage - && self - .options - .writer_flags - .contains(WriterFlags::FORCE_POINT_SIZE) - && !has_point_size - { - writeln!(self.out, "gl_PointSize = 1.0;")?; - write!(self.out, "{level}")?; - } - writeln!(self.out, "return;")?; - } - } - } - // This is one of the places were glsl adds to the syntax of C in this case the discard - // keyword which ceases all further processing in a fragment shader, it's called OpKill - // in spir-v that's why it's called `Statement::Kill` - Statement::Kill => writeln!(self.out, "{level}discard;")?, - Statement::ControlBarrier(flags) => { - self.write_control_barrier(flags, level)?; - } - Statement::MemoryBarrier(flags) => { - self.write_memory_barrier(flags, level)?; - } - // Stores in glsl are just variable assignments written as `pointer = value;` - Statement::Store { pointer, value } => { - write!(self.out, "{level}")?; - self.write_expr(pointer, ctx)?; - write!(self.out, " = ")?; - self.write_expr(value, ctx)?; - writeln!(self.out, ";")? - } - Statement::WorkGroupUniformLoad { pointer, result } => { - // GLSL doesn't have pointers, which means that this backend needs to ensure that - // the actual "loading" is happening between the two barriers. - // This is done in `Emit` by never emitting a variable name for pointer variables - self.write_control_barrier(crate::Barrier::WORK_GROUP, level)?; - - let result_name = Baked(result).to_string(); - write!(self.out, "{level}")?; - // Expressions cannot have side effects, so just writing the expression here is fine. - self.write_named_expr(pointer, result_name, result, ctx)?; - - self.write_control_barrier(crate::Barrier::WORK_GROUP, level)?; - } - // Stores a value into an image. - Statement::ImageStore { - image, - coordinate, - array_index, - value, - } => { - write!(self.out, "{level}")?; - self.write_image_store(ctx, image, coordinate, array_index, value)? - } - // A `Call` is written `name(arguments)` where `arguments` is a comma separated expressions list - Statement::Call { - function, - ref arguments, - result, - } => { - write!(self.out, "{level}")?; - if let Some(expr) = result { - let name = Baked(expr).to_string(); - let result = self.module.functions[function].result.as_ref().unwrap(); - self.write_type(result.ty)?; - write!(self.out, " {name}")?; - if let TypeInner::Array { base, size, .. } = self.module.types[result.ty].inner - { - self.write_array_size(base, size)? - } - write!(self.out, " = ")?; - self.named_expressions.insert(expr, name); - } - write!(self.out, "{}(", &self.names[&NameKey::Function(function)])?; - let arguments: Vec<_> = arguments - .iter() - .enumerate() - .filter_map(|(i, arg)| { - let arg_ty = self.module.functions[function].arguments[i].ty; - match self.module.types[arg_ty].inner { - TypeInner::Sampler { .. } => None, - _ => Some(*arg), - } - }) - .collect(); - self.write_slice(&arguments, |this, _, arg| this.write_expr(*arg, ctx))?; - writeln!(self.out, ");")? - } - Statement::Atomic { - pointer, - ref fun, - value, - result, - } => { - write!(self.out, "{level}")?; - - match *fun { - crate::AtomicFunction::Exchange { - compare: Some(compare_expr), - } => { - let result_handle = result.expect("CompareExchange must have a result"); - let res_name = Baked(result_handle).to_string(); - self.write_type(ctx.info[result_handle].ty.handle().unwrap())?; - write!(self.out, " {res_name};")?; - write!(self.out, " {res_name}.old_value = atomicCompSwap(")?; - self.write_expr(pointer, ctx)?; - write!(self.out, ", ")?; - self.write_expr(compare_expr, ctx)?; - write!(self.out, ", ")?; - self.write_expr(value, ctx)?; - writeln!(self.out, ");")?; - - write!( - self.out, - "{level}{res_name}.exchanged = ({res_name}.old_value == " - )?; - self.write_expr(compare_expr, ctx)?; - writeln!(self.out, ");")?; - self.named_expressions.insert(result_handle, res_name); - } - _ => { - if let Some(result) = result { - let res_name = Baked(result).to_string(); - self.write_type(ctx.info[result].ty.handle().unwrap())?; - write!(self.out, " {res_name} = ")?; - self.named_expressions.insert(result, res_name); - } - let fun_str = fun.to_glsl(); - write!(self.out, "atomic{fun_str}(")?; - self.write_expr(pointer, ctx)?; - write!(self.out, ", ")?; - if let crate::AtomicFunction::Subtract = *fun { - // Emulate `atomicSub` with `atomicAdd` by negating the value. - write!(self.out, "-")?; - } - self.write_expr(value, ctx)?; - writeln!(self.out, ");")?; - } - } - } - // Stores a value into an image. - Statement::ImageAtomic { - image, - coordinate, - array_index, - fun, - value, - } => { - write!(self.out, "{level}")?; - self.write_image_atomic(ctx, image, coordinate, array_index, fun, value)? - } - Statement::RayQuery { .. } => unreachable!(), - Statement::SubgroupBallot { result, predicate } => { - write!(self.out, "{level}")?; - let res_name = Baked(result).to_string(); - let res_ty = ctx.info[result].ty.inner_with(&self.module.types); - self.write_value_type(res_ty)?; - write!(self.out, " {res_name} = ")?; - self.named_expressions.insert(result, res_name); - - write!(self.out, "subgroupBallot(")?; - match predicate { - Some(predicate) => self.write_expr(predicate, ctx)?, - None => write!(self.out, "true")?, - } - writeln!(self.out, ");")?; - } - Statement::SubgroupCollectiveOperation { - op, - collective_op, - argument, - result, - } => { - write!(self.out, "{level}")?; - let res_name = Baked(result).to_string(); - let res_ty = ctx.info[result].ty.inner_with(&self.module.types); - self.write_value_type(res_ty)?; - write!(self.out, " {res_name} = ")?; - self.named_expressions.insert(result, res_name); - - match (collective_op, op) { - (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::All) => { - write!(self.out, "subgroupAll(")? - } - (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Any) => { - write!(self.out, "subgroupAny(")? - } - (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Add) => { - write!(self.out, "subgroupAdd(")? - } - (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Mul) => { - write!(self.out, "subgroupMul(")? - } - (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Max) => { - write!(self.out, "subgroupMax(")? - } - (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Min) => { - write!(self.out, "subgroupMin(")? - } - (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::And) => { - write!(self.out, "subgroupAnd(")? - } - (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Or) => { - write!(self.out, "subgroupOr(")? - } - (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Xor) => { - write!(self.out, "subgroupXor(")? - } - (crate::CollectiveOperation::ExclusiveScan, crate::SubgroupOperation::Add) => { - write!(self.out, "subgroupExclusiveAdd(")? - } - (crate::CollectiveOperation::ExclusiveScan, crate::SubgroupOperation::Mul) => { - write!(self.out, "subgroupExclusiveMul(")? - } - (crate::CollectiveOperation::InclusiveScan, crate::SubgroupOperation::Add) => { - write!(self.out, "subgroupInclusiveAdd(")? - } - (crate::CollectiveOperation::InclusiveScan, crate::SubgroupOperation::Mul) => { - write!(self.out, "subgroupInclusiveMul(")? - } - _ => unimplemented!(), - } - self.write_expr(argument, ctx)?; - writeln!(self.out, ");")?; - } - Statement::SubgroupGather { - mode, - argument, - result, - } => { - write!(self.out, "{level}")?; - let res_name = Baked(result).to_string(); - let res_ty = ctx.info[result].ty.inner_with(&self.module.types); - self.write_value_type(res_ty)?; - write!(self.out, " {res_name} = ")?; - self.named_expressions.insert(result, res_name); - - match mode { - crate::GatherMode::BroadcastFirst => { - write!(self.out, "subgroupBroadcastFirst(")?; - } - crate::GatherMode::Broadcast(_) => { - write!(self.out, "subgroupBroadcast(")?; - } - crate::GatherMode::Shuffle(_) => { - write!(self.out, "subgroupShuffle(")?; - } - crate::GatherMode::ShuffleDown(_) => { - write!(self.out, "subgroupShuffleDown(")?; - } - crate::GatherMode::ShuffleUp(_) => { - write!(self.out, "subgroupShuffleUp(")?; - } - crate::GatherMode::ShuffleXor(_) => { - write!(self.out, "subgroupShuffleXor(")?; - } - crate::GatherMode::QuadBroadcast(_) => { - write!(self.out, "subgroupQuadBroadcast(")?; - } - crate::GatherMode::QuadSwap(direction) => match direction { - crate::Direction::X => { - write!(self.out, "subgroupQuadSwapHorizontal(")?; - } - crate::Direction::Y => { - write!(self.out, "subgroupQuadSwapVertical(")?; - } - crate::Direction::Diagonal => { - write!(self.out, "subgroupQuadSwapDiagonal(")?; - } - }, - } - self.write_expr(argument, ctx)?; - match mode { - crate::GatherMode::BroadcastFirst => {} - crate::GatherMode::Broadcast(index) - | crate::GatherMode::Shuffle(index) - | crate::GatherMode::ShuffleDown(index) - | crate::GatherMode::ShuffleUp(index) - | crate::GatherMode::ShuffleXor(index) - | crate::GatherMode::QuadBroadcast(index) => { - write!(self.out, ", ")?; - self.write_expr(index, ctx)?; - } - crate::GatherMode::QuadSwap(_) => {} - } - writeln!(self.out, ");")?; - } - } - - Ok(()) - } - - /// Write a const expression. - /// - /// Write `expr`, a handle to an [`Expression`] in the current [`Module`]'s - /// constant expression arena, as GLSL expression. - /// - /// # Notes - /// Adds no newlines or leading/trailing whitespace - /// - /// [`Expression`]: crate::Expression - /// [`Module`]: crate::Module - fn write_const_expr( - &mut self, - expr: Handle<crate::Expression>, - arena: &crate::Arena<crate::Expression>, - ) -> BackendResult { - self.write_possibly_const_expr( - expr, - arena, - |expr| &self.info[expr], - |writer, expr| writer.write_const_expr(expr, arena), - ) - } - - /// Write [`Expression`] variants that can occur in both runtime and const expressions. - /// - /// Write `expr`, a handle to an [`Expression`] in the arena `expressions`, - /// as as GLSL expression. This must be one of the [`Expression`] variants - /// that is allowed to occur in constant expressions. - /// - /// Use `write_expression` to write subexpressions. - /// - /// This is the common code for `write_expr`, which handles arbitrary - /// runtime expressions, and `write_const_expr`, which only handles - /// const-expressions. Each of those callers passes itself (essentially) as - /// the `write_expression` callback, so that subexpressions are restricted - /// to the appropriate variants. - /// - /// # Notes - /// Adds no newlines or leading/trailing whitespace - /// - /// [`Expression`]: crate::Expression - fn write_possibly_const_expr<'w, I, E>( - &'w mut self, - expr: Handle<crate::Expression>, - expressions: &crate::Arena<crate::Expression>, - info: I, - write_expression: E, - ) -> BackendResult - where - I: Fn(Handle<crate::Expression>) -> &'w proc::TypeResolution, - E: Fn(&mut Self, Handle<crate::Expression>) -> BackendResult, - { - use crate::Expression; - - match expressions[expr] { - Expression::Literal(literal) => { - match literal { - // Floats are written using `Debug` instead of `Display` because it always appends the - // decimal part even it's zero which is needed for a valid glsl float constant - crate::Literal::F64(value) => write!(self.out, "{value:?}LF")?, - crate::Literal::F32(value) => write!(self.out, "{value:?}")?, - crate::Literal::F16(_) => { - return Err(Error::Custom("GLSL has no 16-bit float type".into())); - } - // Unsigned integers need a `u` at the end - // - // While `core` doesn't necessarily need it, it's allowed and since `es` needs it we - // always write it as the extra branch wouldn't have any benefit in readability - crate::Literal::U32(value) => write!(self.out, "{value}u")?, - crate::Literal::I32(value) => write!(self.out, "{value}")?, - crate::Literal::Bool(value) => write!(self.out, "{value}")?, - crate::Literal::I64(_) => { - return Err(Error::Custom("GLSL has no 64-bit integer type".into())); - } - crate::Literal::U64(_) => { - return Err(Error::Custom("GLSL has no 64-bit integer type".into())); - } - crate::Literal::AbstractInt(_) | crate::Literal::AbstractFloat(_) => { - return Err(Error::Custom( - "Abstract types should not appear in IR presented to backends".into(), - )); - } - } - } - Expression::Constant(handle) => { - let constant = &self.module.constants[handle]; - if constant.name.is_some() { - write!(self.out, "{}", self.names[&NameKey::Constant(handle)])?; - } else { - self.write_const_expr(constant.init, &self.module.global_expressions)?; - } - } - Expression::ZeroValue(ty) => { - self.write_zero_init_value(ty)?; - } - Expression::Compose { ty, ref components } => { - self.write_type(ty)?; - - if let TypeInner::Array { base, size, .. } = self.module.types[ty].inner { - self.write_array_size(base, size)?; - } - - write!(self.out, "(")?; - for (index, component) in components.iter().enumerate() { - if index != 0 { - write!(self.out, ", ")?; - } - write_expression(self, *component)?; - } - write!(self.out, ")")? - } - // `Splat` needs to actually write down a vector, it's not always inferred in GLSL. - Expression::Splat { size: _, value } => { - let resolved = info(expr).inner_with(&self.module.types); - self.write_value_type(resolved)?; - write!(self.out, "(")?; - write_expression(self, value)?; - write!(self.out, ")")? - } - _ => { - return Err(Error::Override); - } - } - - Ok(()) - } - - /// Helper method to write expressions - /// - /// # Notes - /// Doesn't add any newlines or leading/trailing spaces - fn write_expr( - &mut self, - expr: Handle<crate::Expression>, - ctx: &back::FunctionCtx, - ) -> BackendResult { - use crate::Expression; - - if let Some(name) = self.named_expressions.get(&expr) { - write!(self.out, "{name}")?; - return Ok(()); - } - - match ctx.expressions[expr] { - Expression::Literal(_) - | Expression::Constant(_) - | Expression::ZeroValue(_) - | Expression::Compose { .. } - | Expression::Splat { .. } => { - self.write_possibly_const_expr( - expr, - ctx.expressions, - |expr| &ctx.info[expr].ty, - |writer, expr| writer.write_expr(expr, ctx), - )?; - } - Expression::Override(_) => return Err(Error::Override), - // `Access` is applied to arrays, vectors and matrices and is written as indexing - Expression::Access { base, index } => { - self.write_expr(base, ctx)?; - write!(self.out, "[")?; - self.write_expr(index, ctx)?; - write!(self.out, "]")? - } - // `AccessIndex` is the same as `Access` except that the index is a constant and it can - // be applied to structs, in this case we need to find the name of the field at that - // index and write `base.field_name` - Expression::AccessIndex { base, index } => { - self.write_expr(base, ctx)?; - - let base_ty_res = &ctx.info[base].ty; - let mut resolved = base_ty_res.inner_with(&self.module.types); - let base_ty_handle = match *resolved { - TypeInner::Pointer { base, space: _ } => { - resolved = &self.module.types[base].inner; - Some(base) - } - _ => base_ty_res.handle(), - }; - - match *resolved { - TypeInner::Vector { .. } => { - // Write vector access as a swizzle - write!(self.out, ".{}", back::COMPONENTS[index as usize])? - } - TypeInner::Matrix { .. } - | TypeInner::Array { .. } - | TypeInner::ValuePointer { .. } => write!(self.out, "[{index}]")?, - TypeInner::Struct { .. } => { - // This will never panic in case the type is a `Struct`, this is not true - // for other types so we can only check while inside this match arm - let ty = base_ty_handle.unwrap(); - - write!( - self.out, - ".{}", - &self.names[&NameKey::StructMember(ty, index)] - )? - } - ref other => return Err(Error::Custom(format!("Cannot index {other:?}"))), - } - } - // `Swizzle` adds a few letters behind the dot. - Expression::Swizzle { - size, - vector, - pattern, - } => { - self.write_expr(vector, ctx)?; - write!(self.out, ".")?; - for &sc in pattern[..size as usize].iter() { - self.out.write_char(back::COMPONENTS[sc as usize])?; - } - } - // Function arguments are written as the argument name - Expression::FunctionArgument(pos) => { - write!(self.out, "{}", &self.names[&ctx.argument_key(pos)])? - } - // Global variables need some special work for their name but - // `get_global_name` does the work for us - Expression::GlobalVariable(handle) => { - let global = &self.module.global_variables[handle]; - self.write_global_name(handle, global)? - } - // A local is written as it's name - Expression::LocalVariable(handle) => { - write!(self.out, "{}", self.names[&ctx.name_key(handle)])? - } - // glsl has no pointers so there's no load operation, just write the pointer expression - Expression::Load { pointer } => self.write_expr(pointer, ctx)?, - // `ImageSample` is a bit complicated compared to the rest of the IR. - // - // First there are three variations depending whether the sample level is explicitly set, - // if it's automatic or it it's bias: - // `texture(image, coordinate)` - Automatic sample level - // `texture(image, coordinate, bias)` - Bias sample level - // `textureLod(image, coordinate, level)` - Zero or Exact sample level - // - // Furthermore if `depth_ref` is some we need to append it to the coordinate vector - Expression::ImageSample { - image, - sampler: _, //TODO? - gather, - coordinate, - array_index, - offset, - level, - depth_ref, - clamp_to_edge: _, - } => { - let (dim, class, arrayed) = match *ctx.resolve_type(image, &self.module.types) { - TypeInner::Image { - dim, - class, - arrayed, - .. - } => (dim, class, arrayed), - _ => unreachable!(), - }; - let mut err = None; - if dim == crate::ImageDimension::Cube { - if offset.is_some() { - err = Some("gsamplerCube[Array][Shadow] doesn't support texture sampling with offsets"); - } - if arrayed - && matches!(class, crate::ImageClass::Depth { .. }) - && matches!(level, crate::SampleLevel::Gradient { .. }) - { - err = Some("samplerCubeArrayShadow don't support textureGrad"); - } - } - if gather.is_some() && level != crate::SampleLevel::Zero { - err = Some("textureGather doesn't support LOD parameters"); - } - if let Some(err) = err { - return Err(Error::Custom(String::from(err))); - } - - // `textureLod[Offset]` on `sampler2DArrayShadow` and `samplerCubeShadow` does not exist in GLSL, - // unless `GL_EXT_texture_shadow_lod` is present. - // But if the target LOD is zero, we can emulate that by using `textureGrad[Offset]` with a constant gradient of 0. - let workaround_lod_with_grad = ((dim == crate::ImageDimension::Cube && !arrayed) - || (dim == crate::ImageDimension::D2 && arrayed)) - && level == crate::SampleLevel::Zero - && matches!(class, crate::ImageClass::Depth { .. }) - && !self.features.contains(Features::TEXTURE_SHADOW_LOD); - - // Write the function to be used depending on the sample level - let fun_name = match level { - crate::SampleLevel::Zero if gather.is_some() => "textureGather", - crate::SampleLevel::Zero if workaround_lod_with_grad => "textureGrad", - crate::SampleLevel::Auto | crate::SampleLevel::Bias(_) => "texture", - crate::SampleLevel::Zero | crate::SampleLevel::Exact(_) => "textureLod", - crate::SampleLevel::Gradient { .. } => "textureGrad", - }; - let offset_name = match offset { - Some(_) => "Offset", - None => "", - }; - - write!(self.out, "{fun_name}{offset_name}(")?; - - // Write the image that will be used - self.write_expr(image, ctx)?; - // The space here isn't required but it helps with readability - write!(self.out, ", ")?; - - // TODO: handle clamp_to_edge - // https://github.com/gfx-rs/wgpu/issues/7791 - - // We need to get the coordinates vector size to later build a vector that's `size + 1` - // if `depth_ref` is some, if it isn't a vector we panic as that's not a valid expression - let mut coord_dim = match *ctx.resolve_type(coordinate, &self.module.types) { - TypeInner::Vector { size, .. } => size as u8, - TypeInner::Scalar { .. } => 1, - _ => unreachable!(), - }; - - if array_index.is_some() { - coord_dim += 1; - } - let merge_depth_ref = depth_ref.is_some() && gather.is_none() && coord_dim < 4; - if merge_depth_ref { - coord_dim += 1; - } - - let tex_1d_hack = dim == crate::ImageDimension::D1 && self.options.version.is_es(); - let is_vec = tex_1d_hack || coord_dim != 1; - // Compose a new texture coordinates vector - if is_vec { - write!(self.out, "vec{}(", coord_dim + tex_1d_hack as u8)?; - } - self.write_expr(coordinate, ctx)?; - if tex_1d_hack { - write!(self.out, ", 0.0")?; - } - if let Some(expr) = array_index { - write!(self.out, ", ")?; - self.write_expr(expr, ctx)?; - } - if merge_depth_ref { - write!(self.out, ", ")?; - self.write_expr(depth_ref.unwrap(), ctx)?; - } - if is_vec { - write!(self.out, ")")?; - } - - if let (Some(expr), false) = (depth_ref, merge_depth_ref) { - write!(self.out, ", ")?; - self.write_expr(expr, ctx)?; - } - - match level { - // Auto needs no more arguments - crate::SampleLevel::Auto => (), - // Zero needs level set to 0 - crate::SampleLevel::Zero => { - if workaround_lod_with_grad { - let vec_dim = match dim { - crate::ImageDimension::Cube => 3, - _ => 2, - }; - write!(self.out, ", vec{vec_dim}(0.0), vec{vec_dim}(0.0)")?; - } else if gather.is_none() { - write!(self.out, ", 0.0")?; - } - } - // Exact and bias require another argument - crate::SampleLevel::Exact(expr) => { - write!(self.out, ", ")?; - self.write_expr(expr, ctx)?; - } - crate::SampleLevel::Bias(_) => { - // This needs to be done after the offset writing - } - crate::SampleLevel::Gradient { x, y } => { - // If we are using sampler2D to replace sampler1D, we also - // need to make sure to use vec2 gradients - if tex_1d_hack { - write!(self.out, ", vec2(")?; - self.write_expr(x, ctx)?; - write!(self.out, ", 0.0)")?; - write!(self.out, ", vec2(")?; - self.write_expr(y, ctx)?; - write!(self.out, ", 0.0)")?; - } else { - write!(self.out, ", ")?; - self.write_expr(x, ctx)?; - write!(self.out, ", ")?; - self.write_expr(y, ctx)?; - } - } - } - - if let Some(constant) = offset { - write!(self.out, ", ")?; - if tex_1d_hack { - write!(self.out, "ivec2(")?; - } - self.write_const_expr(constant, ctx.expressions)?; - if tex_1d_hack { - write!(self.out, ", 0)")?; - } - } - - // Bias is always the last argument - if let crate::SampleLevel::Bias(expr) = level { - write!(self.out, ", ")?; - self.write_expr(expr, ctx)?; - } - - if let (Some(component), None) = (gather, depth_ref) { - write!(self.out, ", {}", component as usize)?; - } - - // End the function - write!(self.out, ")")? - } - Expression::ImageLoad { - image, - coordinate, - array_index, - sample, - level, - } => self.write_image_load(expr, ctx, image, coordinate, array_index, sample, level)?, - // Query translates into one of the: - // - textureSize/imageSize - // - textureQueryLevels - // - textureSamples/imageSamples - Expression::ImageQuery { image, query } => { - use crate::ImageClass; - - // This will only panic if the module is invalid - let (dim, class) = match *ctx.resolve_type(image, &self.module.types) { - TypeInner::Image { - dim, - arrayed: _, - class, - } => (dim, class), - _ => unreachable!(), - }; - let components = match dim { - crate::ImageDimension::D1 => 1, - crate::ImageDimension::D2 => 2, - crate::ImageDimension::D3 => 3, - crate::ImageDimension::Cube => 2, - }; - - if let crate::ImageQuery::Size { .. } = query { - match components { - 1 => write!(self.out, "uint(")?, - _ => write!(self.out, "uvec{components}(")?, - } - } else { - write!(self.out, "uint(")?; - } - - match query { - crate::ImageQuery::Size { level } => { - match class { - ImageClass::Sampled { multi, .. } | ImageClass::Depth { multi } => { - write!(self.out, "textureSize(")?; - self.write_expr(image, ctx)?; - if let Some(expr) = level { - let cast_to_int = matches!( - *ctx.resolve_type(expr, &self.module.types), - TypeInner::Scalar(crate::Scalar { - kind: crate::ScalarKind::Uint, - .. - }) - ); - - write!(self.out, ", ")?; - - if cast_to_int { - write!(self.out, "int(")?; - } - - self.write_expr(expr, ctx)?; - - if cast_to_int { - write!(self.out, ")")?; - } - } else if !multi { - // All textureSize calls requires an lod argument - // except for multisampled samplers - write!(self.out, ", 0")?; - } - } - ImageClass::Storage { .. } => { - write!(self.out, "imageSize(")?; - self.write_expr(image, ctx)?; - } - ImageClass::External => unimplemented!(), - } - write!(self.out, ")")?; - if components != 1 || self.options.version.is_es() { - write!(self.out, ".{}", &"xyz"[..components])?; - } - } - crate::ImageQuery::NumLevels => { - write!(self.out, "textureQueryLevels(",)?; - self.write_expr(image, ctx)?; - write!(self.out, ")",)?; - } - crate::ImageQuery::NumLayers => { - let fun_name = match class { - ImageClass::Sampled { .. } | ImageClass::Depth { .. } => "textureSize", - ImageClass::Storage { .. } => "imageSize", - ImageClass::External => unimplemented!(), - }; - write!(self.out, "{fun_name}(")?; - self.write_expr(image, ctx)?; - // All textureSize calls requires an lod argument - // except for multisampled samplers - if !class.is_multisampled() { - write!(self.out, ", 0")?; - } - write!(self.out, ")")?; - if components != 1 || self.options.version.is_es() { - write!(self.out, ".{}", back::COMPONENTS[components])?; - } - } - crate::ImageQuery::NumSamples => { - let fun_name = match class { - ImageClass::Sampled { .. } | ImageClass::Depth { .. } => { - "textureSamples" - } - ImageClass::Storage { .. } => "imageSamples", - ImageClass::External => unimplemented!(), - }; - write!(self.out, "{fun_name}(")?; - self.write_expr(image, ctx)?; - write!(self.out, ")",)?; - } - } - - write!(self.out, ")")?; - } - Expression::Unary { op, expr } => { - let operator_or_fn = match op { - crate::UnaryOperator::Negate => "-", - crate::UnaryOperator::LogicalNot => { - match *ctx.resolve_type(expr, &self.module.types) { - TypeInner::Vector { .. } => "not", - _ => "!", - } - } - crate::UnaryOperator::BitwiseNot => "~", - }; - write!(self.out, "{operator_or_fn}(")?; - - self.write_expr(expr, ctx)?; - - write!(self.out, ")")? - } - // `Binary` we just write `left op right`, except when dealing with - // comparison operations on vectors as they are implemented with - // builtin functions. - // Once again we wrap everything in parentheses to avoid precedence issues - Expression::Binary { - mut op, - left, - right, - } => { - // Holds `Some(function_name)` if the binary operation is - // implemented as a function call - use crate::{BinaryOperator as Bo, ScalarKind as Sk, TypeInner as Ti}; - - let left_inner = ctx.resolve_type(left, &self.module.types); - let right_inner = ctx.resolve_type(right, &self.module.types); - - let function = match (left_inner, right_inner) { - (&Ti::Vector { scalar, .. }, &Ti::Vector { .. }) => match op { - Bo::Less - | Bo::LessEqual - | Bo::Greater - | Bo::GreaterEqual - | Bo::Equal - | Bo::NotEqual => BinaryOperation::VectorCompare, - Bo::Modulo if scalar.kind == Sk::Float => BinaryOperation::Modulo, - Bo::And if scalar.kind == Sk::Bool => { - op = crate::BinaryOperator::LogicalAnd; - BinaryOperation::VectorComponentWise - } - Bo::InclusiveOr if scalar.kind == Sk::Bool => { - op = crate::BinaryOperator::LogicalOr; - BinaryOperation::VectorComponentWise - } - _ => BinaryOperation::Other, - }, - _ => match (left_inner.scalar_kind(), right_inner.scalar_kind()) { - (Some(Sk::Float), _) | (_, Some(Sk::Float)) => match op { - Bo::Modulo => BinaryOperation::Modulo, - _ => BinaryOperation::Other, - }, - (Some(Sk::Bool), Some(Sk::Bool)) => match op { - Bo::InclusiveOr => { - op = crate::BinaryOperator::LogicalOr; - BinaryOperation::Other - } - Bo::And => { - op = crate::BinaryOperator::LogicalAnd; - BinaryOperation::Other - } - _ => BinaryOperation::Other, - }, - _ => BinaryOperation::Other, - }, - }; - - match function { - BinaryOperation::VectorCompare => { - let op_str = match op { - Bo::Less => "lessThan(", - Bo::LessEqual => "lessThanEqual(", - Bo::Greater => "greaterThan(", - Bo::GreaterEqual => "greaterThanEqual(", - Bo::Equal => "equal(", - Bo::NotEqual => "notEqual(", - _ => unreachable!(), - }; - write!(self.out, "{op_str}")?; - self.write_expr(left, ctx)?; - write!(self.out, ", ")?; - self.write_expr(right, ctx)?; - write!(self.out, ")")?; - } - BinaryOperation::VectorComponentWise => { - self.write_value_type(left_inner)?; - write!(self.out, "(")?; - - let size = match *left_inner { - Ti::Vector { size, .. } => size, - _ => unreachable!(), - }; - - for i in 0..size as usize { - if i != 0 { - write!(self.out, ", ")?; - } - - self.write_expr(left, ctx)?; - write!(self.out, ".{}", back::COMPONENTS[i])?; - - write!(self.out, " {} ", back::binary_operation_str(op))?; - - self.write_expr(right, ctx)?; - write!(self.out, ".{}", back::COMPONENTS[i])?; - } - - write!(self.out, ")")?; - } - // TODO: handle undefined behavior of BinaryOperator::Modulo - // - // sint: - // if right == 0 return 0 - // if left == min(type_of(left)) && right == -1 return 0 - // if sign(left) == -1 || sign(right) == -1 return result as defined by WGSL - // - // uint: - // if right == 0 return 0 - // - // float: - // if right == 0 return ? see https://github.com/gpuweb/gpuweb/issues/2798 - BinaryOperation::Modulo => { - write!(self.out, "(")?; - - // write `e1 - e2 * trunc(e1 / e2)` - self.write_expr(left, ctx)?; - write!(self.out, " - ")?; - self.write_expr(right, ctx)?; - write!(self.out, " * ")?; - write!(self.out, "trunc(")?; - self.write_expr(left, ctx)?; - write!(self.out, " / ")?; - self.write_expr(right, ctx)?; - write!(self.out, ")")?; - - write!(self.out, ")")?; - } - BinaryOperation::Other => { - write!(self.out, "(")?; - - self.write_expr(left, ctx)?; - write!(self.out, " {} ", back::binary_operation_str(op))?; - self.write_expr(right, ctx)?; - - write!(self.out, ")")?; - } - } - } - // `Select` is written as `condition ? accept : reject` - // We wrap everything in parentheses to avoid precedence issues - Expression::Select { - condition, - accept, - reject, - } => { - let cond_ty = ctx.resolve_type(condition, &self.module.types); - let vec_select = if let TypeInner::Vector { .. } = *cond_ty { - true - } else { - false - }; - - // TODO: Boolean mix on desktop required GL_EXT_shader_integer_mix - if vec_select { - // Glsl defines that for mix when the condition is a boolean the first element - // is picked if condition is false and the second if condition is true - write!(self.out, "mix(")?; - self.write_expr(reject, ctx)?; - write!(self.out, ", ")?; - self.write_expr(accept, ctx)?; - write!(self.out, ", ")?; - self.write_expr(condition, ctx)?; - } else { - write!(self.out, "(")?; - self.write_expr(condition, ctx)?; - write!(self.out, " ? ")?; - self.write_expr(accept, ctx)?; - write!(self.out, " : ")?; - self.write_expr(reject, ctx)?; - } - - write!(self.out, ")")? - } - // `Derivative` is a function call to a glsl provided function - Expression::Derivative { axis, ctrl, expr } => { - use crate::{DerivativeAxis as Axis, DerivativeControl as Ctrl}; - let fun_name = if self.options.version.supports_derivative_control() { - match (axis, ctrl) { - (Axis::X, Ctrl::Coarse) => "dFdxCoarse", - (Axis::X, Ctrl::Fine) => "dFdxFine", - (Axis::X, Ctrl::None) => "dFdx", - (Axis::Y, Ctrl::Coarse) => "dFdyCoarse", - (Axis::Y, Ctrl::Fine) => "dFdyFine", - (Axis::Y, Ctrl::None) => "dFdy", - (Axis::Width, Ctrl::Coarse) => "fwidthCoarse", - (Axis::Width, Ctrl::Fine) => "fwidthFine", - (Axis::Width, Ctrl::None) => "fwidth", - } - } else { - match axis { - Axis::X => "dFdx", - Axis::Y => "dFdy", - Axis::Width => "fwidth", - } - }; - write!(self.out, "{fun_name}(")?; - self.write_expr(expr, ctx)?; - write!(self.out, ")")? - } - // `Relational` is a normal function call to some glsl provided functions - Expression::Relational { fun, argument } => { - use crate::RelationalFunction as Rf; - - let fun_name = match fun { - Rf::IsInf => "isinf", - Rf::IsNan => "isnan", - Rf::All => "all", - Rf::Any => "any", - }; - write!(self.out, "{fun_name}(")?; - - self.write_expr(argument, ctx)?; - - write!(self.out, ")")? - } - Expression::Math { - fun, - arg, - arg1, - arg2, - arg3, - } => { - use crate::MathFunction as Mf; - - let fun_name = match fun { - // comparison - Mf::Abs => "abs", - Mf::Min => "min", - Mf::Max => "max", - Mf::Clamp => { - let scalar_kind = ctx - .resolve_type(arg, &self.module.types) - .scalar_kind() - .unwrap(); - match scalar_kind { - crate::ScalarKind::Float => "clamp", - // Clamp is undefined if min > max. In practice this means it can use a median-of-three - // instruction to determine the value. This is fine according to the WGSL spec for float - // clamp, but integer clamp _must_ use min-max. As such we write out min/max. - _ => { - write!(self.out, "min(max(")?; - self.write_expr(arg, ctx)?; - write!(self.out, ", ")?; - self.write_expr(arg1.unwrap(), ctx)?; - write!(self.out, "), ")?; - self.write_expr(arg2.unwrap(), ctx)?; - write!(self.out, ")")?; - - return Ok(()); - } - } - } - Mf::Saturate => { - write!(self.out, "clamp(")?; - - self.write_expr(arg, ctx)?; - - match *ctx.resolve_type(arg, &self.module.types) { - TypeInner::Vector { size, .. } => write!( - self.out, - ", vec{}(0.0), vec{0}(1.0)", - common::vector_size_str(size) - )?, - _ => write!(self.out, ", 0.0, 1.0")?, - } - - write!(self.out, ")")?; - - return Ok(()); - } - // trigonometry - Mf::Cos => "cos", - Mf::Cosh => "cosh", - Mf::Sin => "sin", - Mf::Sinh => "sinh", - Mf::Tan => "tan", - Mf::Tanh => "tanh", - Mf::Acos => "acos", - Mf::Asin => "asin", - Mf::Atan => "atan", - Mf::Asinh => "asinh", - Mf::Acosh => "acosh", - Mf::Atanh => "atanh", - Mf::Radians => "radians", - Mf::Degrees => "degrees", - // glsl doesn't have atan2 function - // use two-argument variation of the atan function - Mf::Atan2 => "atan", - // decomposition - Mf::Ceil => "ceil", - Mf::Floor => "floor", - Mf::Round => "roundEven", - Mf::Fract => "fract", - Mf::Trunc => "trunc", - Mf::Modf => MODF_FUNCTION, - Mf::Frexp => FREXP_FUNCTION, - Mf::Ldexp => "ldexp", - // exponent - Mf::Exp => "exp", - Mf::Exp2 => "exp2", - Mf::Log => "log", - Mf::Log2 => "log2", - Mf::Pow => "pow", - // geometry - Mf::Dot => match *ctx.resolve_type(arg, &self.module.types) { - TypeInner::Vector { - scalar: - crate::Scalar { - kind: crate::ScalarKind::Float, - .. - }, - .. - } => "dot", - TypeInner::Vector { size, .. } => { - return self.write_dot_product(arg, arg1.unwrap(), size as usize, ctx) - } - _ => unreachable!( - "Correct TypeInner for dot product should be already validated" - ), - }, - fun @ (Mf::Dot4I8Packed | Mf::Dot4U8Packed) => { - let conversion = match fun { - Mf::Dot4I8Packed => "int", - Mf::Dot4U8Packed => "", - _ => unreachable!(), - }; - - let arg1 = arg1.unwrap(); - - // Write parentheses around the dot product expression to prevent operators - // with different precedences from applying earlier. - write!(self.out, "(")?; - for i in 0..4 { - // Since `bitfieldExtract` only sign extends if the value is signed, we - // need to convert the inputs to `int` in case of `Dot4I8Packed`. For - // `Dot4U8Packed`, the code below only introduces parenthesis around - // each factor, which aren't strictly needed because both operands are - // baked, but which don't hurt either. - write!(self.out, "bitfieldExtract({conversion}(")?; - self.write_expr(arg, ctx)?; - write!(self.out, "), {}, 8)", i * 8)?; - - write!(self.out, " * bitfieldExtract({conversion}(")?; - self.write_expr(arg1, ctx)?; - write!(self.out, "), {}, 8)", i * 8)?; - - if i != 3 { - write!(self.out, " + ")?; - } - } - write!(self.out, ")")?; - - return Ok(()); - } - Mf::Outer => "outerProduct", - Mf::Cross => "cross", - Mf::Distance => "distance", - Mf::Length => "length", - Mf::Normalize => "normalize", - Mf::FaceForward => "faceforward", - Mf::Reflect => "reflect", - Mf::Refract => "refract", - // computational - Mf::Sign => "sign", - Mf::Fma => { - if self.options.version.supports_fma_function() { - // Use the fma function when available - "fma" - } else { - // No fma support. Transform the function call into an arithmetic expression - write!(self.out, "(")?; - - self.write_expr(arg, ctx)?; - write!(self.out, " * ")?; - - let arg1 = - arg1.ok_or_else(|| Error::Custom("Missing fma arg1".to_owned()))?; - self.write_expr(arg1, ctx)?; - write!(self.out, " + ")?; - - let arg2 = - arg2.ok_or_else(|| Error::Custom("Missing fma arg2".to_owned()))?; - self.write_expr(arg2, ctx)?; - write!(self.out, ")")?; - - return Ok(()); - } - } - Mf::Mix => "mix", - Mf::Step => "step", - Mf::SmoothStep => "smoothstep", - Mf::Sqrt => "sqrt", - Mf::InverseSqrt => "inversesqrt", - Mf::Inverse => "inverse", - Mf::Transpose => "transpose", - Mf::Determinant => "determinant", - Mf::QuantizeToF16 => match *ctx.resolve_type(arg, &self.module.types) { - TypeInner::Scalar { .. } => { - write!(self.out, "unpackHalf2x16(packHalf2x16(vec2(")?; - self.write_expr(arg, ctx)?; - write!(self.out, "))).x")?; - return Ok(()); - } - TypeInner::Vector { - size: crate::VectorSize::Bi, - .. - } => { - write!(self.out, "unpackHalf2x16(packHalf2x16(")?; - self.write_expr(arg, ctx)?; - write!(self.out, "))")?; - return Ok(()); - } - TypeInner::Vector { - size: crate::VectorSize::Tri, - .. - } => { - write!(self.out, "vec3(unpackHalf2x16(packHalf2x16(")?; - self.write_expr(arg, ctx)?; - write!(self.out, ".xy)), unpackHalf2x16(packHalf2x16(")?; - self.write_expr(arg, ctx)?; - write!(self.out, ".zz)).x)")?; - return Ok(()); - } - TypeInner::Vector { - size: crate::VectorSize::Quad, - .. - } => { - write!(self.out, "vec4(unpackHalf2x16(packHalf2x16(")?; - self.write_expr(arg, ctx)?; - write!(self.out, ".xy)), unpackHalf2x16(packHalf2x16(")?; - self.write_expr(arg, ctx)?; - write!(self.out, ".zw)))")?; - return Ok(()); - } - _ => unreachable!( - "Correct TypeInner for QuantizeToF16 should be already validated" - ), - }, - // bits - Mf::CountTrailingZeros => { - match *ctx.resolve_type(arg, &self.module.types) { - TypeInner::Vector { size, scalar, .. } => { - let s = common::vector_size_str(size); - if let crate::ScalarKind::Uint = scalar.kind { - write!(self.out, "min(uvec{s}(findLSB(")?; - self.write_expr(arg, ctx)?; - write!(self.out, ")), uvec{s}(32u))")?; - } else { - write!(self.out, "ivec{s}(min(uvec{s}(findLSB(")?; - self.write_expr(arg, ctx)?; - write!(self.out, ")), uvec{s}(32u)))")?; - } - } - TypeInner::Scalar(scalar) => { - if let crate::ScalarKind::Uint = scalar.kind { - write!(self.out, "min(uint(findLSB(")?; - self.write_expr(arg, ctx)?; - write!(self.out, ")), 32u)")?; - } else { - write!(self.out, "int(min(uint(findLSB(")?; - self.write_expr(arg, ctx)?; - write!(self.out, ")), 32u))")?; - } - } - _ => unreachable!(), - }; - return Ok(()); - } - Mf::CountLeadingZeros => { - if self.options.version.supports_integer_functions() { - match *ctx.resolve_type(arg, &self.module.types) { - TypeInner::Vector { size, scalar } => { - let s = common::vector_size_str(size); - - if let crate::ScalarKind::Uint = scalar.kind { - write!(self.out, "uvec{s}(ivec{s}(31) - findMSB(")?; - self.write_expr(arg, ctx)?; - write!(self.out, "))")?; - } else { - write!(self.out, "mix(ivec{s}(31) - findMSB(")?; - self.write_expr(arg, ctx)?; - write!(self.out, "), ivec{s}(0), lessThan(")?; - self.write_expr(arg, ctx)?; - write!(self.out, ", ivec{s}(0)))")?; - } - } - TypeInner::Scalar(scalar) => { - if let crate::ScalarKind::Uint = scalar.kind { - write!(self.out, "uint(31 - findMSB(")?; - } else { - write!(self.out, "(")?; - self.write_expr(arg, ctx)?; - write!(self.out, " < 0 ? 0 : 31 - findMSB(")?; - } - - self.write_expr(arg, ctx)?; - write!(self.out, "))")?; - } - _ => unreachable!(), - }; - } else { - match *ctx.resolve_type(arg, &self.module.types) { - TypeInner::Vector { size, scalar } => { - let s = common::vector_size_str(size); - - if let crate::ScalarKind::Uint = scalar.kind { - write!(self.out, "uvec{s}(")?; - write!(self.out, "vec{s}(31.0) - floor(log2(vec{s}(")?; - self.write_expr(arg, ctx)?; - write!(self.out, ") + 0.5)))")?; - } else { - write!(self.out, "ivec{s}(")?; - write!(self.out, "mix(vec{s}(31.0) - floor(log2(vec{s}(")?; - self.write_expr(arg, ctx)?; - write!(self.out, ") + 0.5)), ")?; - write!(self.out, "vec{s}(0.0), lessThan(")?; - self.write_expr(arg, ctx)?; - write!(self.out, ", ivec{s}(0u))))")?; - } - } - TypeInner::Scalar(scalar) => { - if let crate::ScalarKind::Uint = scalar.kind { - write!(self.out, "uint(31.0 - floor(log2(float(")?; - self.write_expr(arg, ctx)?; - write!(self.out, ") + 0.5)))")?; - } else { - write!(self.out, "(")?; - self.write_expr(arg, ctx)?; - write!(self.out, " < 0 ? 0 : int(")?; - write!(self.out, "31.0 - floor(log2(float(")?; - self.write_expr(arg, ctx)?; - write!(self.out, ") + 0.5))))")?; - } - } - _ => unreachable!(), - }; - } - - return Ok(()); - } - Mf::CountOneBits => "bitCount", - Mf::ReverseBits => "bitfieldReverse", - Mf::ExtractBits => { - // The behavior of ExtractBits is undefined when offset + count > bit_width. We need - // to first sanitize the offset and count first. If we don't do this, AMD and Intel chips - // will return out-of-spec values if the extracted range is not within the bit width. - // - // This encodes the exact formula specified by the wgsl spec, without temporary values: - // https://gpuweb.github.io/gpuweb/wgsl/#extractBits-unsigned-builtin - // - // w = sizeof(x) * 8 - // o = min(offset, w) - // c = min(count, w - o) - // - // bitfieldExtract(x, o, c) - // - // extract_bits(e, min(offset, w), min(count, w - min(offset, w)))) - let scalar_bits = ctx - .resolve_type(arg, &self.module.types) - .scalar_width() - .unwrap() - * 8; - - write!(self.out, "bitfieldExtract(")?; - self.write_expr(arg, ctx)?; - write!(self.out, ", int(min(")?; - self.write_expr(arg1.unwrap(), ctx)?; - write!(self.out, ", {scalar_bits}u)), int(min(",)?; - self.write_expr(arg2.unwrap(), ctx)?; - write!(self.out, ", {scalar_bits}u - min(")?; - self.write_expr(arg1.unwrap(), ctx)?; - write!(self.out, ", {scalar_bits}u))))")?; - - return Ok(()); - } - Mf::InsertBits => { - // InsertBits has the same considerations as ExtractBits above - let scalar_bits = ctx - .resolve_type(arg, &self.module.types) - .scalar_width() - .unwrap() - * 8; - - write!(self.out, "bitfieldInsert(")?; - self.write_expr(arg, ctx)?; - write!(self.out, ", ")?; - self.write_expr(arg1.unwrap(), ctx)?; - write!(self.out, ", int(min(")?; - self.write_expr(arg2.unwrap(), ctx)?; - write!(self.out, ", {scalar_bits}u)), int(min(",)?; - self.write_expr(arg3.unwrap(), ctx)?; - write!(self.out, ", {scalar_bits}u - min(")?; - self.write_expr(arg2.unwrap(), ctx)?; - write!(self.out, ", {scalar_bits}u))))")?; - - return Ok(()); - } - Mf::FirstTrailingBit => "findLSB", - Mf::FirstLeadingBit => "findMSB", - // data packing - Mf::Pack4x8snorm => { - if self.options.version.supports_pack_unpack_4x8() { - "packSnorm4x8" - } else { - // polyfill should go here. Needs a corresponding entry in `need_bake_expression` - return Err(Error::UnsupportedExternal("packSnorm4x8".into())); - } - } - Mf::Pack4x8unorm => { - if self.options.version.supports_pack_unpack_4x8() { - "packUnorm4x8" - } else { - return Err(Error::UnsupportedExternal("packUnorm4x8".to_owned())); - } - } - Mf::Pack2x16snorm => { - if self.options.version.supports_pack_unpack_snorm_2x16() { - "packSnorm2x16" - } else { - return Err(Error::UnsupportedExternal("packSnorm2x16".to_owned())); - } - } - Mf::Pack2x16unorm => { - if self.options.version.supports_pack_unpack_unorm_2x16() { - "packUnorm2x16" - } else { - return Err(Error::UnsupportedExternal("packUnorm2x16".to_owned())); - } - } - Mf::Pack2x16float => { - if self.options.version.supports_pack_unpack_half_2x16() { - "packHalf2x16" - } else { - return Err(Error::UnsupportedExternal("packHalf2x16".to_owned())); - } - } - - fun @ (Mf::Pack4xI8 | Mf::Pack4xU8 | Mf::Pack4xI8Clamp | Mf::Pack4xU8Clamp) => { - let was_signed = matches!(fun, Mf::Pack4xI8 | Mf::Pack4xI8Clamp); - let clamp_bounds = match fun { - Mf::Pack4xI8Clamp => Some(("-128", "127")), - Mf::Pack4xU8Clamp => Some(("0", "255")), - _ => None, - }; - let const_suffix = if was_signed { "" } else { "u" }; - if was_signed { - write!(self.out, "uint(")?; - } - let write_arg = |this: &mut Self| -> BackendResult { - if let Some((min, max)) = clamp_bounds { - write!(this.out, "clamp(")?; - this.write_expr(arg, ctx)?; - write!(this.out, ", {min}{const_suffix}, {max}{const_suffix})")?; - } else { - this.write_expr(arg, ctx)?; - } - Ok(()) - }; - write!(self.out, "(")?; - write_arg(self)?; - write!(self.out, "[0] & 0xFF{const_suffix}) | ((")?; - write_arg(self)?; - write!(self.out, "[1] & 0xFF{const_suffix}) << 8) | ((")?; - write_arg(self)?; - write!(self.out, "[2] & 0xFF{const_suffix}) << 16) | ((")?; - write_arg(self)?; - write!(self.out, "[3] & 0xFF{const_suffix}) << 24)")?; - if was_signed { - write!(self.out, ")")?; - } - - return Ok(()); - } - // data unpacking - Mf::Unpack2x16float => { - if self.options.version.supports_pack_unpack_half_2x16() { - "unpackHalf2x16" - } else { - return Err(Error::UnsupportedExternal("unpackHalf2x16".into())); - } - } - Mf::Unpack2x16snorm => { - if self.options.version.supports_pack_unpack_snorm_2x16() { - "unpackSnorm2x16" - } else { - let scale = 32767; - - write!(self.out, "(vec2(ivec2(")?; - self.write_expr(arg, ctx)?; - write!(self.out, " << 16, ")?; - self.write_expr(arg, ctx)?; - write!(self.out, ") >> 16) / {scale}.0)")?; - return Ok(()); - } - } - Mf::Unpack2x16unorm => { - if self.options.version.supports_pack_unpack_unorm_2x16() { - "unpackUnorm2x16" - } else { - let scale = 65535; - - write!(self.out, "(vec2(")?; - self.write_expr(arg, ctx)?; - write!(self.out, " & 0xFFFFu, ")?; - self.write_expr(arg, ctx)?; - write!(self.out, " >> 16) / {scale}.0)")?; - return Ok(()); - } - } - Mf::Unpack4x8snorm => { - if self.options.version.supports_pack_unpack_4x8() { - "unpackSnorm4x8" - } else { - let scale = 127; - - write!(self.out, "(vec4(ivec4(")?; - self.write_expr(arg, ctx)?; - write!(self.out, " << 24, ")?; - self.write_expr(arg, ctx)?; - write!(self.out, " << 16, ")?; - self.write_expr(arg, ctx)?; - write!(self.out, " << 8, ")?; - self.write_expr(arg, ctx)?; - write!(self.out, ") >> 24) / {scale}.0)")?; - return Ok(()); - } - } - Mf::Unpack4x8unorm => { - if self.options.version.supports_pack_unpack_4x8() { - "unpackUnorm4x8" - } else { - let scale = 255; - - write!(self.out, "(vec4(")?; - self.write_expr(arg, ctx)?; - write!(self.out, " & 0xFFu, ")?; - self.write_expr(arg, ctx)?; - write!(self.out, " >> 8 & 0xFFu, ")?; - self.write_expr(arg, ctx)?; - write!(self.out, " >> 16 & 0xFFu, ")?; - self.write_expr(arg, ctx)?; - write!(self.out, " >> 24) / {scale}.0)")?; - return Ok(()); - } - } - fun @ (Mf::Unpack4xI8 | Mf::Unpack4xU8) => { - let sign_prefix = match fun { - Mf::Unpack4xI8 => 'i', - Mf::Unpack4xU8 => 'u', - _ => unreachable!(), - }; - write!(self.out, "{sign_prefix}vec4(")?; - for i in 0..4 { - write!(self.out, "bitfieldExtract(")?; - // Since bitfieldExtract only sign extends if the value is signed, this - // cast is needed - match fun { - Mf::Unpack4xI8 => { - write!(self.out, "int(")?; - self.write_expr(arg, ctx)?; - write!(self.out, ")")?; - } - Mf::Unpack4xU8 => self.write_expr(arg, ctx)?, - _ => unreachable!(), - }; - write!(self.out, ", {}, 8)", i * 8)?; - if i != 3 { - write!(self.out, ", ")?; - } - } - write!(self.out, ")")?; - - return Ok(()); - } - }; - - let extract_bits = fun == Mf::ExtractBits; - let insert_bits = fun == Mf::InsertBits; - - // Some GLSL functions always return signed integers (like findMSB), - // so they need to be cast to uint if the argument is also an uint. - let ret_might_need_int_to_uint = matches!( - fun, - Mf::FirstTrailingBit | Mf::FirstLeadingBit | Mf::CountOneBits | Mf::Abs - ); - - // Some GLSL functions only accept signed integers (like abs), - // so they need their argument cast from uint to int. - let arg_might_need_uint_to_int = matches!(fun, Mf::Abs); - - // Check if the argument is an unsigned integer and return the vector size - // in case it's a vector - let maybe_uint_size = match *ctx.resolve_type(arg, &self.module.types) { - TypeInner::Scalar(crate::Scalar { - kind: crate::ScalarKind::Uint, - .. - }) => Some(None), - TypeInner::Vector { - scalar: - crate::Scalar { - kind: crate::ScalarKind::Uint, - .. - }, - size, - } => Some(Some(size)), - _ => None, - }; - - // Cast to uint if the function needs it - if ret_might_need_int_to_uint { - if let Some(maybe_size) = maybe_uint_size { - match maybe_size { - Some(size) => write!(self.out, "uvec{}(", size as u8)?, - None => write!(self.out, "uint(")?, - } - } - } - - write!(self.out, "{fun_name}(")?; - - // Cast to int if the function needs it - if arg_might_need_uint_to_int { - if let Some(maybe_size) = maybe_uint_size { - match maybe_size { - Some(size) => write!(self.out, "ivec{}(", size as u8)?, - None => write!(self.out, "int(")?, - } - } - } - - self.write_expr(arg, ctx)?; - - // Close the cast from uint to int - if arg_might_need_uint_to_int && maybe_uint_size.is_some() { - write!(self.out, ")")? - } - - if let Some(arg) = arg1 { - write!(self.out, ", ")?; - if extract_bits { - write!(self.out, "int(")?; - self.write_expr(arg, ctx)?; - write!(self.out, ")")?; - } else { - self.write_expr(arg, ctx)?; - } - } - if let Some(arg) = arg2 { - write!(self.out, ", ")?; - if extract_bits || insert_bits { - write!(self.out, "int(")?; - self.write_expr(arg, ctx)?; - write!(self.out, ")")?; - } else { - self.write_expr(arg, ctx)?; - } - } - if let Some(arg) = arg3 { - write!(self.out, ", ")?; - if insert_bits { - write!(self.out, "int(")?; - self.write_expr(arg, ctx)?; - write!(self.out, ")")?; - } else { - self.write_expr(arg, ctx)?; - } - } - write!(self.out, ")")?; - - // Close the cast from int to uint - if ret_might_need_int_to_uint && maybe_uint_size.is_some() { - write!(self.out, ")")? - } - } - // `As` is always a call. - // If `convert` is true the function name is the type - // Else the function name is one of the glsl provided bitcast functions - Expression::As { - expr, - kind: target_kind, - convert, - } => { - let inner = ctx.resolve_type(expr, &self.module.types); - match convert { - Some(width) => { - // this is similar to `write_type`, but with the target kind - let scalar = glsl_scalar(crate::Scalar { - kind: target_kind, - width, - })?; - match *inner { - TypeInner::Matrix { columns, rows, .. } => write!( - self.out, - "{}mat{}x{}", - scalar.prefix, columns as u8, rows as u8 - )?, - TypeInner::Vector { size, .. } => { - write!(self.out, "{}vec{}", scalar.prefix, size as u8)? - } - _ => write!(self.out, "{}", scalar.full)?, - } - - write!(self.out, "(")?; - self.write_expr(expr, ctx)?; - write!(self.out, ")")? - } - None => { - use crate::ScalarKind as Sk; - - let target_vector_type = match *inner { - TypeInner::Vector { size, scalar } => Some(TypeInner::Vector { - size, - scalar: crate::Scalar { - kind: target_kind, - width: scalar.width, - }, - }), - _ => None, - }; - - let source_kind = inner.scalar_kind().unwrap(); - - match (source_kind, target_kind, target_vector_type) { - // No conversion needed - (Sk::Sint, Sk::Sint, _) - | (Sk::Uint, Sk::Uint, _) - | (Sk::Float, Sk::Float, _) - | (Sk::Bool, Sk::Bool, _) => { - self.write_expr(expr, ctx)?; - return Ok(()); - } - - // Cast to/from floats - (Sk::Float, Sk::Sint, _) => write!(self.out, "floatBitsToInt")?, - (Sk::Float, Sk::Uint, _) => write!(self.out, "floatBitsToUint")?, - (Sk::Sint, Sk::Float, _) => write!(self.out, "intBitsToFloat")?, - (Sk::Uint, Sk::Float, _) => write!(self.out, "uintBitsToFloat")?, - - // Cast between vector types - (_, _, Some(vector)) => { - self.write_value_type(&vector)?; - } - - // There is no way to bitcast between Uint/Sint in glsl. Use constructor conversion - (Sk::Uint | Sk::Bool, Sk::Sint, None) => write!(self.out, "int")?, - (Sk::Sint | Sk::Bool, Sk::Uint, None) => write!(self.out, "uint")?, - (Sk::Bool, Sk::Float, None) => write!(self.out, "float")?, - (Sk::Sint | Sk::Uint | Sk::Float, Sk::Bool, None) => { - write!(self.out, "bool")? - } - - (Sk::AbstractInt | Sk::AbstractFloat, _, _) - | (_, Sk::AbstractInt | Sk::AbstractFloat, _) => unreachable!(), - }; - - write!(self.out, "(")?; - self.write_expr(expr, ctx)?; - write!(self.out, ")")?; - } - } - } - // These expressions never show up in `Emit`. - Expression::CallResult(_) - | Expression::AtomicResult { .. } - | Expression::RayQueryProceedResult - | Expression::WorkGroupUniformLoadResult { .. } - | Expression::SubgroupOperationResult { .. } - | Expression::SubgroupBallotResult => unreachable!(), - // `ArrayLength` is written as `expr.length()` and we convert it to a uint - Expression::ArrayLength(expr) => { - write!(self.out, "uint(")?; - self.write_expr(expr, ctx)?; - write!(self.out, ".length())")? - } - // not supported yet - Expression::RayQueryGetIntersection { .. } - | Expression::RayQueryVertexPositions { .. } => unreachable!(), - } - - Ok(()) - } - - /// Helper function to write the local holding the clamped lod - fn write_clamped_lod( - &mut self, - ctx: &back::FunctionCtx, - expr: Handle<crate::Expression>, - image: Handle<crate::Expression>, - level_expr: Handle<crate::Expression>, - ) -> Result<(), Error> { - // Define our local and start a call to `clamp` - write!( - self.out, - "int {}{} = clamp(", - Baked(expr), - CLAMPED_LOD_SUFFIX - )?; - // Write the lod that will be clamped - self.write_expr(level_expr, ctx)?; - // Set the min value to 0 and start a call to `textureQueryLevels` to get - // the maximum value - write!(self.out, ", 0, textureQueryLevels(")?; - // Write the target image as an argument to `textureQueryLevels` - self.write_expr(image, ctx)?; - // Close the call to `textureQueryLevels` subtract 1 from it since - // the lod argument is 0 based, close the `clamp` call and end the - // local declaration statement. - writeln!(self.out, ") - 1);")?; - - Ok(()) - } - - // Helper method used to retrieve how many elements a coordinate vector - // for the images operations need. - fn get_coordinate_vector_size(&self, dim: crate::ImageDimension, arrayed: bool) -> u8 { - // openGL es doesn't have 1D images so we need workaround it - let tex_1d_hack = dim == crate::ImageDimension::D1 && self.options.version.is_es(); - // Get how many components the coordinate vector needs for the dimensions only - let tex_coord_size = match dim { - crate::ImageDimension::D1 => 1, - crate::ImageDimension::D2 => 2, - crate::ImageDimension::D3 => 3, - crate::ImageDimension::Cube => 2, - }; - // Calculate the true size of the coordinate vector by adding 1 for arrayed images - // and another 1 if we need to workaround 1D images by making them 2D - tex_coord_size + tex_1d_hack as u8 + arrayed as u8 - } - - /// Helper method to write the coordinate vector for image operations - fn write_texture_coord( - &mut self, - ctx: &back::FunctionCtx, - vector_size: u8, - coordinate: Handle<crate::Expression>, - array_index: Option<Handle<crate::Expression>>, - // Emulate 1D images as 2D for profiles that don't support it (glsl es) - tex_1d_hack: bool, - ) -> Result<(), Error> { - match array_index { - // If the image needs an array indice we need to add it to the end of our - // coordinate vector, to do so we will use the `ivec(ivec, scalar)` - // constructor notation (NOTE: the inner `ivec` can also be a scalar, this - // is important for 1D arrayed images). - Some(layer_expr) => { - write!(self.out, "ivec{vector_size}(")?; - self.write_expr(coordinate, ctx)?; - write!(self.out, ", ")?; - // If we are replacing sampler1D with sampler2D we also need - // to add another zero to the coordinates vector for the y component - if tex_1d_hack { - write!(self.out, "0, ")?; - } - self.write_expr(layer_expr, ctx)?; - write!(self.out, ")")?; - } - // Otherwise write just the expression (and the 1D hack if needed) - None => { - let uvec_size = match *ctx.resolve_type(coordinate, &self.module.types) { - TypeInner::Scalar(crate::Scalar { - kind: crate::ScalarKind::Uint, - .. - }) => Some(None), - TypeInner::Vector { - size, - scalar: - crate::Scalar { - kind: crate::ScalarKind::Uint, - .. - }, - } => Some(Some(size as u32)), - _ => None, - }; - if tex_1d_hack { - write!(self.out, "ivec2(")?; - } else if uvec_size.is_some() { - match uvec_size { - Some(None) => write!(self.out, "int(")?, - Some(Some(size)) => write!(self.out, "ivec{size}(")?, - _ => {} - } - } - self.write_expr(coordinate, ctx)?; - if tex_1d_hack { - write!(self.out, ", 0)")?; - } else if uvec_size.is_some() { - write!(self.out, ")")?; - } - } - } - - Ok(()) - } - - /// Helper method to write the `ImageStore` statement - fn write_image_store( - &mut self, - ctx: &back::FunctionCtx, - image: Handle<crate::Expression>, - coordinate: Handle<crate::Expression>, - array_index: Option<Handle<crate::Expression>>, - value: Handle<crate::Expression>, - ) -> Result<(), Error> { - use crate::ImageDimension as IDim; - - // NOTE: openGL requires that `imageStore`s have no effects when the texel is invalid - // so we don't need to generate bounds checks (OpenGL 4.2 Core §3.9.20) - - // This will only panic if the module is invalid - let dim = match *ctx.resolve_type(image, &self.module.types) { - TypeInner::Image { dim, .. } => dim, - _ => unreachable!(), - }; - - // Begin our call to `imageStore` - write!(self.out, "imageStore(")?; - self.write_expr(image, ctx)?; - // Separate the image argument from the coordinates - write!(self.out, ", ")?; - - // openGL es doesn't have 1D images so we need workaround it - let tex_1d_hack = dim == IDim::D1 && self.options.version.is_es(); - // Write the coordinate vector - self.write_texture_coord( - ctx, - // Get the size of the coordinate vector - self.get_coordinate_vector_size(dim, array_index.is_some()), - coordinate, - array_index, - tex_1d_hack, - )?; - - // Separate the coordinate from the value to write and write the expression - // of the value to write. - write!(self.out, ", ")?; - self.write_expr(value, ctx)?; - // End the call to `imageStore` and the statement. - writeln!(self.out, ");")?; - - Ok(()) - } - - /// Helper method to write the `ImageAtomic` statement - fn write_image_atomic( - &mut self, - ctx: &back::FunctionCtx, - image: Handle<crate::Expression>, - coordinate: Handle<crate::Expression>, - array_index: Option<Handle<crate::Expression>>, - fun: crate::AtomicFunction, - value: Handle<crate::Expression>, - ) -> Result<(), Error> { - use crate::ImageDimension as IDim; - - // NOTE: openGL requires that `imageAtomic`s have no effects when the texel is invalid - // so we don't need to generate bounds checks (OpenGL 4.2 Core §3.9.20) - - // This will only panic if the module is invalid - let dim = match *ctx.resolve_type(image, &self.module.types) { - TypeInner::Image { dim, .. } => dim, - _ => unreachable!(), - }; - - // Begin our call to `imageAtomic` - let fun_str = fun.to_glsl(); - write!(self.out, "imageAtomic{fun_str}(")?; - self.write_expr(image, ctx)?; - // Separate the image argument from the coordinates - write!(self.out, ", ")?; - - // openGL es doesn't have 1D images so we need workaround it - let tex_1d_hack = dim == IDim::D1 && self.options.version.is_es(); - // Write the coordinate vector - self.write_texture_coord( - ctx, - // Get the size of the coordinate vector - self.get_coordinate_vector_size(dim, false), - coordinate, - array_index, - tex_1d_hack, - )?; - - // Separate the coordinate from the value to write and write the expression - // of the value to write. - write!(self.out, ", ")?; - self.write_expr(value, ctx)?; - // End the call to `imageAtomic` and the statement. - writeln!(self.out, ");")?; - - Ok(()) - } - - /// Helper method for writing an `ImageLoad` expression. - #[allow(clippy::too_many_arguments)] - fn write_image_load( - &mut self, - handle: Handle<crate::Expression>, - ctx: &back::FunctionCtx, - image: Handle<crate::Expression>, - coordinate: Handle<crate::Expression>, - array_index: Option<Handle<crate::Expression>>, - sample: Option<Handle<crate::Expression>>, - level: Option<Handle<crate::Expression>>, - ) -> Result<(), Error> { - use crate::ImageDimension as IDim; - - // `ImageLoad` is a bit complicated. - // There are two functions one for sampled - // images another for storage images, the former uses `texelFetch` and the - // latter uses `imageLoad`. - // - // Furthermore we have `level` which is always `Some` for sampled images - // and `None` for storage images, so we end up with two functions: - // - `texelFetch(image, coordinate, level)` for sampled images - // - `imageLoad(image, coordinate)` for storage images - // - // Finally we also have to consider bounds checking, for storage images - // this is easy since openGL requires that invalid texels always return - // 0, for sampled images we need to either verify that all arguments are - // in bounds (`ReadZeroSkipWrite`) or make them a valid texel (`Restrict`). - - // This will only panic if the module is invalid - let (dim, class) = match *ctx.resolve_type(image, &self.module.types) { - TypeInner::Image { - dim, - arrayed: _, - class, - } => (dim, class), - _ => unreachable!(), - }; - - // Get the name of the function to be used for the load operation - // and the policy to be used with it. - let (fun_name, policy) = match class { - // Sampled images inherit the policy from the user passed policies - crate::ImageClass::Sampled { .. } => ("texelFetch", self.policies.image_load), - crate::ImageClass::Storage { .. } => { - // OpenGL ES 3.1 mentions in Chapter "8.22 Texture Image Loads and Stores" that: - // "Invalid image loads will return a vector where the value of R, G, and B components - // is 0 and the value of the A component is undefined." - // - // OpenGL 4.2 Core mentions in Chapter "3.9.20 Texture Image Loads and Stores" that: - // "Invalid image loads will return zero." - // - // So, we only inject bounds checks for ES - let policy = if self.options.version.is_es() { - self.policies.image_load - } else { - proc::BoundsCheckPolicy::Unchecked - }; - ("imageLoad", policy) - } - // TODO: Is there even a function for this? - crate::ImageClass::Depth { multi: _ } => { - return Err(Error::Custom( - "WGSL `textureLoad` from depth textures is not supported in GLSL".to_string(), - )) - } - crate::ImageClass::External => unimplemented!(), - }; - - // openGL es doesn't have 1D images so we need workaround it - let tex_1d_hack = dim == IDim::D1 && self.options.version.is_es(); - // Get the size of the coordinate vector - let vector_size = self.get_coordinate_vector_size(dim, array_index.is_some()); - - if let proc::BoundsCheckPolicy::ReadZeroSkipWrite = policy { - // To write the bounds checks for `ReadZeroSkipWrite` we will use a - // ternary operator since we are in the middle of an expression and - // need to return a value. - // - // NOTE: glsl does short circuit when evaluating logical - // expressions so we can be sure that after we test a - // condition it will be true for the next ones - - // Write parentheses around the ternary operator to prevent problems with - // expressions emitted before or after it having more precedence - write!(self.out, "(",)?; - - // The lod check needs to precede the size check since we need - // to use the lod to get the size of the image at that level. - if let Some(level_expr) = level { - self.write_expr(level_expr, ctx)?; - write!(self.out, " < textureQueryLevels(",)?; - self.write_expr(image, ctx)?; - // Chain the next check - write!(self.out, ") && ")?; - } - - // Check that the sample arguments doesn't exceed the number of samples - if let Some(sample_expr) = sample { - self.write_expr(sample_expr, ctx)?; - write!(self.out, " < textureSamples(",)?; - self.write_expr(image, ctx)?; - // Chain the next check - write!(self.out, ") && ")?; - } - - // We now need to write the size checks for the coordinates and array index - // first we write the comparison function in case the image is 1D non arrayed - // (and no 1D to 2D hack was needed) we are comparing scalars so the less than - // operator will suffice, but otherwise we'll be comparing two vectors so we'll - // need to use the `lessThan` function but it returns a vector of booleans (one - // for each comparison) so we need to fold it all in one scalar boolean, since - // we want all comparisons to pass we use the `all` function which will only - // return `true` if all the elements of the boolean vector are also `true`. - // - // So we'll end with one of the following forms - // - `coord < textureSize(image, lod)` for 1D images - // - `all(lessThan(coord, textureSize(image, lod)))` for normal images - // - `all(lessThan(ivec(coord, array_index), textureSize(image, lod)))` - // for arrayed images - // - `all(lessThan(coord, textureSize(image)))` for multi sampled images - - if vector_size != 1 { - write!(self.out, "all(lessThan(")?; - } - - // Write the coordinate vector - self.write_texture_coord(ctx, vector_size, coordinate, array_index, tex_1d_hack)?; - - if vector_size != 1 { - // If we used the `lessThan` function we need to separate the - // coordinates from the image size. - write!(self.out, ", ")?; - } else { - // If we didn't use it (ie. 1D images) we perform the comparison - // using the less than operator. - write!(self.out, " < ")?; - } - - // Call `textureSize` to get our image size - write!(self.out, "textureSize(")?; - self.write_expr(image, ctx)?; - // `textureSize` uses the lod as a second argument for mipmapped images - if let Some(level_expr) = level { - // Separate the image from the lod - write!(self.out, ", ")?; - self.write_expr(level_expr, ctx)?; - } - // Close the `textureSize` call - write!(self.out, ")")?; - - if vector_size != 1 { - // Close the `all` and `lessThan` calls - write!(self.out, "))")?; - } - - // Finally end the condition part of the ternary operator - write!(self.out, " ? ")?; - } - - // Begin the call to the function used to load the texel - write!(self.out, "{fun_name}(")?; - self.write_expr(image, ctx)?; - write!(self.out, ", ")?; - - // If we are using `Restrict` bounds checking we need to pass valid texel - // coordinates, to do so we use the `clamp` function to get a value between - // 0 and the image size - 1 (indexing begins at 0) - if let proc::BoundsCheckPolicy::Restrict = policy { - write!(self.out, "clamp(")?; - } - - // Write the coordinate vector - self.write_texture_coord(ctx, vector_size, coordinate, array_index, tex_1d_hack)?; - - // If we are using `Restrict` bounds checking we need to write the rest of the - // clamp we initiated before writing the coordinates. - if let proc::BoundsCheckPolicy::Restrict = policy { - // Write the min value 0 - if vector_size == 1 { - write!(self.out, ", 0")?; - } else { - write!(self.out, ", ivec{vector_size}(0)")?; - } - // Start the `textureSize` call to use as the max value. - write!(self.out, ", textureSize(")?; - self.write_expr(image, ctx)?; - // If the image is mipmapped we need to add the lod argument to the - // `textureSize` call, but this needs to be the clamped lod, this should - // have been generated earlier and put in a local. - if class.is_mipmapped() { - write!(self.out, ", {}{}", Baked(handle), CLAMPED_LOD_SUFFIX)?; - } - // Close the `textureSize` call - write!(self.out, ")")?; - - // Subtract 1 from the `textureSize` call since the coordinates are zero based. - if vector_size == 1 { - write!(self.out, " - 1")?; - } else { - write!(self.out, " - ivec{vector_size}(1)")?; - } - - // Close the `clamp` call - write!(self.out, ")")?; - - // Add the clamped lod (if present) as the second argument to the - // image load function. - if level.is_some() { - write!(self.out, ", {}{}", Baked(handle), CLAMPED_LOD_SUFFIX)?; - } - - // If a sample argument is needed we need to clamp it between 0 and - // the number of samples the image has. - if let Some(sample_expr) = sample { - write!(self.out, ", clamp(")?; - self.write_expr(sample_expr, ctx)?; - // Set the min value to 0 and start the call to `textureSamples` - write!(self.out, ", 0, textureSamples(")?; - self.write_expr(image, ctx)?; - // Close the `textureSamples` call, subtract 1 from it since the sample - // argument is zero based, and close the `clamp` call - writeln!(self.out, ") - 1)")?; - } - } else if let Some(sample_or_level) = sample.or(level) { - // GLSL only support SInt on this field while WGSL support also UInt - let cast_to_int = matches!( - *ctx.resolve_type(sample_or_level, &self.module.types), - TypeInner::Scalar(crate::Scalar { - kind: crate::ScalarKind::Uint, - .. - }) - ); - - // If no bounds checking is need just add the sample or level argument - // after the coordinates - write!(self.out, ", ")?; - - if cast_to_int { - write!(self.out, "int(")?; - } - - self.write_expr(sample_or_level, ctx)?; - - if cast_to_int { - write!(self.out, ")")?; - } - } - - // Close the image load function. - write!(self.out, ")")?; - - // If we were using the `ReadZeroSkipWrite` policy we need to end the first branch - // (which is taken if the condition is `true`) with a colon (`:`) and write the - // second branch which is just a 0 value. - if let proc::BoundsCheckPolicy::ReadZeroSkipWrite = policy { - // Get the kind of the output value. - let kind = match class { - // Only sampled images can reach here since storage images - // don't need bounds checks and depth images aren't implemented - crate::ImageClass::Sampled { kind, .. } => kind, - _ => unreachable!(), - }; - - // End the first branch - write!(self.out, " : ")?; - // Write the 0 value - write!( - self.out, - "{}vec4(", - glsl_scalar(crate::Scalar { kind, width: 4 })?.prefix, - )?; - self.write_zero_init_scalar(kind)?; - // Close the zero value constructor - write!(self.out, ")")?; - // Close the parentheses surrounding our ternary - write!(self.out, ")")?; - } - - Ok(()) - } - - fn write_named_expr( - &mut self, - handle: Handle<crate::Expression>, - name: String, - // The expression which is being named. - // Generally, this is the same as handle, except in WorkGroupUniformLoad - named: Handle<crate::Expression>, - ctx: &back::FunctionCtx, - ) -> BackendResult { - match ctx.info[named].ty { - proc::TypeResolution::Handle(ty_handle) => match self.module.types[ty_handle].inner { - TypeInner::Struct { .. } => { - let ty_name = &self.names[&NameKey::Type(ty_handle)]; - write!(self.out, "{ty_name}")?; - } - _ => { - self.write_type(ty_handle)?; - } - }, - proc::TypeResolution::Value(ref inner) => { - self.write_value_type(inner)?; - } - } - - let resolved = ctx.resolve_type(named, &self.module.types); - - write!(self.out, " {name}")?; - if let TypeInner::Array { base, size, .. } = *resolved { - self.write_array_size(base, size)?; - } - write!(self.out, " = ")?; - self.write_expr(handle, ctx)?; - writeln!(self.out, ";")?; - self.named_expressions.insert(named, name); - - Ok(()) - } - - /// Helper function that write string with default zero initialization for supported types - fn write_zero_init_value(&mut self, ty: Handle<crate::Type>) -> BackendResult { - let inner = &self.module.types[ty].inner; - match *inner { - TypeInner::Scalar(scalar) | TypeInner::Atomic(scalar) => { - self.write_zero_init_scalar(scalar.kind)?; - } - TypeInner::Vector { scalar, .. } => { - self.write_value_type(inner)?; - write!(self.out, "(")?; - self.write_zero_init_scalar(scalar.kind)?; - write!(self.out, ")")?; - } - TypeInner::Matrix { .. } => { - self.write_value_type(inner)?; - write!(self.out, "(")?; - self.write_zero_init_scalar(crate::ScalarKind::Float)?; - write!(self.out, ")")?; - } - TypeInner::Array { base, size, .. } => { - let count = match size.resolve(self.module.to_ctx())? { - proc::IndexableLength::Known(count) => count, - proc::IndexableLength::Dynamic => return Ok(()), - }; - self.write_type(base)?; - self.write_array_size(base, size)?; - write!(self.out, "(")?; - for _ in 1..count { - self.write_zero_init_value(base)?; - write!(self.out, ", ")?; - } - // write last parameter without comma and space - self.write_zero_init_value(base)?; - write!(self.out, ")")?; - } - TypeInner::Struct { ref members, .. } => { - let name = &self.names[&NameKey::Type(ty)]; - write!(self.out, "{name}(")?; - for (index, member) in members.iter().enumerate() { - if index != 0 { - write!(self.out, ", ")?; - } - self.write_zero_init_value(member.ty)?; - } - write!(self.out, ")")?; - } - _ => unreachable!(), - } - - Ok(()) - } - - /// Helper function that write string with zero initialization for scalar - fn write_zero_init_scalar(&mut self, kind: crate::ScalarKind) -> BackendResult { - match kind { - crate::ScalarKind::Bool => write!(self.out, "false")?, - crate::ScalarKind::Uint => write!(self.out, "0u")?, - crate::ScalarKind::Float => write!(self.out, "0.0")?, - crate::ScalarKind::Sint => write!(self.out, "0")?, - crate::ScalarKind::AbstractInt | crate::ScalarKind::AbstractFloat => { - return Err(Error::Custom( - "Abstract types should not appear in IR presented to backends".to_string(), - )) - } - } - - Ok(()) - } - - /// Issue a control barrier. - fn write_control_barrier( - &mut self, - flags: crate::Barrier, - level: back::Level, - ) -> BackendResult { - self.write_memory_barrier(flags, level)?; - writeln!(self.out, "{level}barrier();")?; - Ok(()) - } - - /// Issue a memory barrier. - fn write_memory_barrier(&mut self, flags: crate::Barrier, level: back::Level) -> BackendResult { - if flags.contains(crate::Barrier::STORAGE) { - writeln!(self.out, "{level}memoryBarrierBuffer();")?; - } - if flags.contains(crate::Barrier::WORK_GROUP) { - writeln!(self.out, "{level}memoryBarrierShared();")?; - } - if flags.contains(crate::Barrier::SUB_GROUP) { - writeln!(self.out, "{level}subgroupMemoryBarrier();")?; - } - if flags.contains(crate::Barrier::TEXTURE) { - writeln!(self.out, "{level}memoryBarrierImage();")?; - } - Ok(()) - } - - /// Helper function that return the glsl storage access string of [`StorageAccess`](crate::StorageAccess) - /// - /// glsl allows adding both `readonly` and `writeonly` but this means that - /// they can only be used to query information about the resource which isn't what - /// we want here so when storage access is both `LOAD` and `STORE` add no modifiers - fn write_storage_access(&mut self, storage_access: crate::StorageAccess) -> BackendResult { - if storage_access.contains(crate::StorageAccess::ATOMIC) { - return Ok(()); - } - if !storage_access.contains(crate::StorageAccess::STORE) { - write!(self.out, "readonly ")?; - } - if !storage_access.contains(crate::StorageAccess::LOAD) { - write!(self.out, "writeonly ")?; - } - Ok(()) - } - - /// Helper method used to produce the reflection info that's returned to the user - fn collect_reflection_info(&mut self) -> Result<ReflectionInfo, Error> { - let info = self.info.get_entry_point(self.entry_point_idx as usize); - let mut texture_mapping = crate::FastHashMap::default(); - let mut uniforms = crate::FastHashMap::default(); - - for sampling in info.sampling_set.iter() { - let tex_name = self.reflection_names_globals[&sampling.image].clone(); - - match texture_mapping.entry(tex_name) { - hash_map::Entry::Vacant(v) => { - v.insert(TextureMapping { - texture: sampling.image, - sampler: Some(sampling.sampler), - }); - } - hash_map::Entry::Occupied(e) => { - if e.get().sampler != Some(sampling.sampler) { - log::error!("Conflicting samplers for {}", e.key()); - return Err(Error::ImageMultipleSamplers); - } - } - } - } - - let mut push_constant_info = None; - for (handle, var) in self.module.global_variables.iter() { - if info[handle].is_empty() { - continue; - } - match self.module.types[var.ty].inner { - TypeInner::Image { .. } => { - let tex_name = self.reflection_names_globals[&handle].clone(); - match texture_mapping.entry(tex_name) { - hash_map::Entry::Vacant(v) => { - v.insert(TextureMapping { - texture: handle, - sampler: None, - }); - } - hash_map::Entry::Occupied(_) => { - // already used with a sampler, do nothing - } - } - } - _ => match var.space { - crate::AddressSpace::Uniform | crate::AddressSpace::Storage { .. } => { - 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 = 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, - clip_distance_count: self.clip_distance_count, - }) - } - - fn collect_push_constant_items( - &mut self, - ty: Handle<crate::Type>, - segments: &mut Vec<String>, - layouter: &proc::Layouter, - offset: &mut u32, - items: &mut Vec<PushConstantItem>, - ) { - // 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`] -/// -/// It contains both a prefix used in other types and the full type name -struct ScalarString<'a> { - /// The prefix used to compose other types - prefix: &'a str, - /// The name of the scalar type - full: &'a str, -} - -/// Helper function that returns scalar related strings -/// -/// Check [`ScalarString`] for the information provided -/// -/// # Errors -/// If a [`Float`](crate::ScalarKind::Float) with an width that isn't 4 or 8 -const fn glsl_scalar(scalar: crate::Scalar) -> Result<ScalarString<'static>, Error> { - use crate::ScalarKind as Sk; - - Ok(match scalar.kind { - Sk::Sint => ScalarString { - prefix: "i", - full: "int", - }, - Sk::Uint => ScalarString { - prefix: "u", - full: "uint", - }, - Sk::Float => match scalar.width { - 4 => ScalarString { - prefix: "", - full: "float", - }, - 8 => ScalarString { - prefix: "d", - full: "double", - }, - _ => return Err(Error::UnsupportedScalar(scalar)), - }, - Sk::Bool => ScalarString { - prefix: "b", - full: "bool", - }, - Sk::AbstractInt | Sk::AbstractFloat => { - return Err(Error::UnsupportedScalar(scalar)); - } - }) -} - -/// Helper function that returns the glsl variable name for a builtin -const fn glsl_built_in(built_in: crate::BuiltIn, options: VaryingOptions) -> &'static str { - use crate::BuiltIn as Bi; - - match built_in { - Bi::Position { .. } => { - if options.output { - "gl_Position" - } else { - "gl_FragCoord" - } - } - Bi::ViewIndex => { - if options.targeting_webgl { - "gl_ViewID_OVR" - } else { - "uint(gl_ViewIndex)" - } - } - // vertex - Bi::BaseInstance => "uint(gl_BaseInstance)", - Bi::BaseVertex => "uint(gl_BaseVertex)", - Bi::ClipDistance => "gl_ClipDistance", - Bi::CullDistance => "gl_CullDistance", - Bi::InstanceIndex => { - if options.draw_parameters { - "(uint(gl_InstanceID) + uint(gl_BaseInstanceARB))" - } else { - // Must match FIRST_INSTANCE_BINDING - "(uint(gl_InstanceID) + naga_vs_first_instance)" - } - } - Bi::PointSize => "gl_PointSize", - Bi::VertexIndex => "uint(gl_VertexID)", - Bi::DrawID => "gl_DrawID", - // fragment - Bi::FragDepth => "gl_FragDepth", - Bi::PointCoord => "gl_PointCoord", - Bi::FrontFacing => "gl_FrontFacing", - Bi::PrimitiveIndex => "uint(gl_PrimitiveID)", - Bi::Barycentric => "gl_BaryCoordEXT", - Bi::SampleIndex => "gl_SampleID", - Bi::SampleMask => { - if options.output { - "gl_SampleMask" - } else { - "gl_SampleMaskIn" - } - } - // compute - Bi::GlobalInvocationId => "gl_GlobalInvocationID", - Bi::LocalInvocationId => "gl_LocalInvocationID", - Bi::LocalInvocationIndex => "gl_LocalInvocationIndex", - Bi::WorkGroupId => "gl_WorkGroupID", - Bi::WorkGroupSize => "gl_WorkGroupSize", - Bi::NumWorkGroups => "gl_NumWorkGroups", - // subgroup - Bi::NumSubgroups => "gl_NumSubgroups", - Bi::SubgroupId => "gl_SubgroupID", - Bi::SubgroupSize => "gl_SubgroupSize", - Bi::SubgroupInvocationId => "gl_SubgroupInvocationID", - // mesh - // TODO: figure out how to map these to glsl things as glsl treats them as arrays - Bi::CullPrimitive - | Bi::PointIndex - | Bi::LineIndices - | Bi::TriangleIndices - | Bi::MeshTaskSize - | Bi::VertexCount - | Bi::PrimitiveCount - | Bi::Vertices - | Bi::Primitives => { - unimplemented!() - } - } -} - -/// Helper function that returns the string corresponding to the address space -const fn glsl_storage_qualifier(space: crate::AddressSpace) -> Option<&'static str> { - use crate::AddressSpace as As; - - match space { - As::Function => None, - As::Private => None, - As::Storage { .. } => Some("buffer"), - As::Uniform => Some("uniform"), - As::Handle => Some("uniform"), - As::WorkGroup => Some("shared"), - As::PushConstant => Some("uniform"), - As::TaskPayload => unreachable!(), - } -} - -/// Helper function that returns the string corresponding to the glsl interpolation qualifier -const fn glsl_interpolation(interpolation: crate::Interpolation) -> &'static str { - use crate::Interpolation as I; - - match interpolation { - I::Perspective => "smooth", - I::Linear => "noperspective", - I::Flat => "flat", - } -} - -/// Return the GLSL auxiliary qualifier for the given sampling value. -const fn glsl_sampling(sampling: crate::Sampling) -> BackendResult<Option<&'static str>> { - use crate::Sampling as S; - - Ok(match sampling { - S::First => return Err(Error::FirstSamplingNotSupported), - S::Center | S::Either => None, - S::Centroid => Some("centroid"), - S::Sample => Some("sample"), - }) -} - -/// Helper function that returns the glsl dimension string of [`ImageDimension`](crate::ImageDimension) -const fn glsl_dimension(dim: crate::ImageDimension) -> &'static str { - use crate::ImageDimension as IDim; - - match dim { - IDim::D1 => "1D", - IDim::D2 => "2D", - IDim::D3 => "3D", - IDim::Cube => "Cube", - } -} - -/// Helper function that returns the glsl storage format string of [`StorageFormat`](crate::StorageFormat) -fn glsl_storage_format(format: crate::StorageFormat) -> Result<&'static str, Error> { - use crate::StorageFormat as Sf; - - Ok(match format { - Sf::R8Unorm => "r8", - Sf::R8Snorm => "r8_snorm", - Sf::R8Uint => "r8ui", - Sf::R8Sint => "r8i", - Sf::R16Uint => "r16ui", - Sf::R16Sint => "r16i", - Sf::R16Float => "r16f", - Sf::Rg8Unorm => "rg8", - Sf::Rg8Snorm => "rg8_snorm", - Sf::Rg8Uint => "rg8ui", - Sf::Rg8Sint => "rg8i", - Sf::R32Uint => "r32ui", - Sf::R32Sint => "r32i", - Sf::R32Float => "r32f", - Sf::Rg16Uint => "rg16ui", - Sf::Rg16Sint => "rg16i", - Sf::Rg16Float => "rg16f", - Sf::Rgba8Unorm => "rgba8", - Sf::Rgba8Snorm => "rgba8_snorm", - Sf::Rgba8Uint => "rgba8ui", - Sf::Rgba8Sint => "rgba8i", - Sf::Rgb10a2Uint => "rgb10_a2ui", - Sf::Rgb10a2Unorm => "rgb10_a2", - Sf::Rg11b10Ufloat => "r11f_g11f_b10f", - Sf::R64Uint => "r64ui", - Sf::Rg32Uint => "rg32ui", - Sf::Rg32Sint => "rg32i", - Sf::Rg32Float => "rg32f", - Sf::Rgba16Uint => "rgba16ui", - Sf::Rgba16Sint => "rgba16i", - Sf::Rgba16Float => "rgba16f", - Sf::Rgba32Uint => "rgba32ui", - Sf::Rgba32Sint => "rgba32i", - Sf::Rgba32Float => "rgba32f", - Sf::R16Unorm => "r16", - Sf::R16Snorm => "r16_snorm", - Sf::Rg16Unorm => "rg16", - Sf::Rg16Snorm => "rg16_snorm", - Sf::Rgba16Unorm => "rgba16", - Sf::Rgba16Snorm => "rgba16_snorm", - - Sf::Bgra8Unorm => { - return Err(Error::Custom( - "Support format BGRA8 is not implemented".into(), - )) - } - }) -} - fn is_value_init_supported(module: &crate::Module, ty: Handle<crate::Type>) -> bool { match module.types[ty].inner { TypeInner::Scalar { .. } | TypeInner::Vector { .. } | TypeInner::Matrix { .. } => true, diff --git a/third_party/rust/naga/src/back/glsl/writer.rs b/third_party/rust/naga/src/back/glsl/writer.rs @@ -0,0 +1,4568 @@ +use super::*; + +/// Writer responsible for all code generation. +pub struct Writer<'a, W> { + // Inputs + /// The module being written. + pub(in crate::back::glsl) module: &'a crate::Module, + /// The module analysis. + pub(in crate::back::glsl) info: &'a valid::ModuleInfo, + /// The output writer. + out: W, + /// User defined configuration to be used. + pub(in crate::back::glsl) options: &'a Options, + /// The bound checking policies to be used + pub(in crate::back::glsl) policies: proc::BoundsCheckPolicies, + + // Internal State + /// Features manager used to store all the needed features and write them. + pub(in crate::back::glsl) features: FeaturesManager, + namer: proc::Namer, + /// A map with all the names needed for writing the module + /// (generated by a [`Namer`](crate::proc::Namer)). + names: crate::FastHashMap<NameKey, String>, + /// A map with the names of global variables needed for reflections. + reflection_names_globals: crate::FastHashMap<Handle<crate::GlobalVariable>, String>, + /// The selected entry point. + pub(in crate::back::glsl) entry_point: &'a crate::EntryPoint, + /// The index of the selected entry point. + pub(in crate::back::glsl) entry_point_idx: proc::EntryPointIndex, + /// A generator for unique block numbers. + block_id: IdGenerator, + /// Set of expressions that have associated temporary variables. + named_expressions: crate::NamedExpressions, + /// Set of expressions that need to be baked to avoid unnecessary repetition in output + need_bake_expressions: back::NeedBakeExpressions, + /// Information about nesting of loops and switches. + /// + /// Used for forwarding continue statements in switches that have been + /// transformed to `do {} while(false);` loops. + continue_ctx: back::continue_forward::ContinueCtx, + /// How many views to render to, if doing multiview rendering. + pub(in crate::back::glsl) multiview: Option<core::num::NonZeroU32>, + /// Mapping of varying variables to their location. Needed for reflections. + varying: crate::FastHashMap<String, VaryingLocation>, + /// Number of user-defined clip planes. Only non-zero for vertex shaders. + clip_distance_count: u32, +} + +impl<'a, W: Write> Writer<'a, W> { + /// Creates a new [`Writer`] instance. + /// + /// # Errors + /// - If the version specified is invalid or supported. + /// - If the entry point couldn't be found in the module. + /// - If the version specified doesn't support some used features. + pub fn new( + out: W, + module: &'a crate::Module, + info: &'a valid::ModuleInfo, + options: &'a Options, + pipeline_options: &'a PipelineOptions, + policies: proc::BoundsCheckPolicies, + ) -> Result<Self, Error> { + // Check if the requested version is supported + if !options.version.is_supported() { + log::error!("Version {}", options.version); + return Err(Error::VersionNotSupported); + } + + // Try to find the entry point and corresponding index + let ep_idx = module + .entry_points + .iter() + .position(|ep| { + pipeline_options.shader_stage == ep.stage && pipeline_options.entry_point == ep.name + }) + .ok_or(Error::EntryPointNotFound)?; + + // Generate a map with names required to write the module + let mut names = crate::FastHashMap::default(); + let mut namer = proc::Namer::default(); + namer.reset( + module, + &keywords::RESERVED_KEYWORD_SET, + proc::CaseInsensitiveKeywordSet::empty(), + &[ + "gl_", // all GL built-in variables + "_group", // all normal bindings + "_immediates_binding_", // all immediate data bindings + ], + &mut names, + ); + + // Build the instance + let mut this = Self { + module, + info, + out, + options, + policies, + + namer, + features: FeaturesManager::new(), + names, + reflection_names_globals: crate::FastHashMap::default(), + entry_point: &module.entry_points[ep_idx], + entry_point_idx: ep_idx as u16, + multiview: pipeline_options.multiview, + block_id: IdGenerator::default(), + named_expressions: Default::default(), + need_bake_expressions: Default::default(), + continue_ctx: back::continue_forward::ContinueCtx::default(), + varying: Default::default(), + clip_distance_count: 0, + }; + + // Find all features required to print this module + this.collect_required_features()?; + + Ok(this) + } + + /// Writes the [`Module`](crate::Module) as glsl to the output + /// + /// # Notes + /// If an error occurs while writing, the output might have been written partially + /// + /// # Panics + /// Might panic if the module is invalid + pub fn write(&mut self) -> Result<ReflectionInfo, Error> { + // We use `writeln!(self.out)` throughout the write to add newlines + // to make the output more readable + + let es = self.options.version.is_es(); + + // Write the version (It must be the first thing or it isn't a valid glsl output) + writeln!(self.out, "#version {}", self.options.version)?; + // Write all the needed extensions + // + // This used to be the last thing being written as it allowed to search for features while + // writing the module saving some loops but some older versions (420 or less) required the + // extensions to appear before being used, even though extensions are part of the + // preprocessor not the processor ¯\_(ツ)_/¯ + self.features.write(self.options, &mut self.out)?; + + // glsl es requires a precision to be specified for floats and ints + // TODO: Should this be user configurable? + if es { + writeln!(self.out)?; + writeln!(self.out, "precision highp float;")?; + writeln!(self.out, "precision highp int;")?; + writeln!(self.out)?; + } + + if self.entry_point.stage == ShaderStage::Compute { + let workgroup_size = self.entry_point.workgroup_size; + writeln!( + self.out, + "layout(local_size_x = {}, local_size_y = {}, local_size_z = {}) in;", + workgroup_size[0], workgroup_size[1], workgroup_size[2] + )?; + writeln!(self.out)?; + } + + if self.entry_point.stage == ShaderStage::Vertex + && !self + .options + .writer_flags + .contains(WriterFlags::DRAW_PARAMETERS) + && self.features.contains(Features::INSTANCE_INDEX) + { + writeln!(self.out, "uniform uint {FIRST_INSTANCE_BINDING};")?; + writeln!(self.out)?; + } + + // Enable early depth tests if needed + if let Some(early_depth_test) = self.entry_point.early_depth_test { + // If early depth test is supported for this version of GLSL + if self.options.version.supports_early_depth_test() { + match early_depth_test { + crate::EarlyDepthTest::Force => { + writeln!(self.out, "layout(early_fragment_tests) in;")?; + } + crate::EarlyDepthTest::Allow { conservative, .. } => { + use crate::ConservativeDepth as Cd; + let depth = match conservative { + Cd::GreaterEqual => "greater", + Cd::LessEqual => "less", + Cd::Unchanged => "unchanged", + }; + writeln!(self.out, "layout (depth_{depth}) out float gl_FragDepth;")?; + } + } + } else { + log::warn!( + "Early depth testing is not supported for this version of GLSL: {}", + self.options.version + ); + } + } + + if self.entry_point.stage == ShaderStage::Vertex && self.options.version.is_webgl() { + if let Some(multiview) = self.multiview.as_ref() { + writeln!(self.out, "layout(num_views = {multiview}) in;")?; + writeln!(self.out)?; + } + } + + // Write struct types. + // + // This are always ordered because the IR is structured in a way that + // you can't make a struct without adding all of its members first. + for (handle, ty) in self.module.types.iter() { + if let TypeInner::Struct { ref members, .. } = ty.inner { + let struct_name = &self.names[&NameKey::Type(handle)]; + + // Structures ending with runtime-sized arrays can only be + // rendered as shader storage blocks in GLSL, not stand-alone + // struct types. + if !self.module.types[members.last().unwrap().ty] + .inner + .is_dynamically_sized(&self.module.types) + { + write!(self.out, "struct {struct_name} ")?; + self.write_struct_body(handle, members)?; + writeln!(self.out, ";")?; + } + } + } + + // Write functions for special types. + for (type_key, struct_ty) in self.module.special_types.predeclared_types.iter() { + match type_key { + &crate::PredeclaredType::ModfResult { size, scalar } + | &crate::PredeclaredType::FrexpResult { size, scalar } => { + let struct_name = &self.names[&NameKey::Type(*struct_ty)]; + let arg_type_name_owner; + let arg_type_name = if let Some(size) = size { + arg_type_name_owner = format!( + "{}vec{}", + if scalar.width == 8 { "d" } else { "" }, + size as u8 + ); + &arg_type_name_owner + } else if scalar.width == 8 { + "double" + } else { + "float" + }; + + let other_type_name_owner; + let (defined_func_name, called_func_name, other_type_name) = + if matches!(type_key, &crate::PredeclaredType::ModfResult { .. }) { + (MODF_FUNCTION, "modf", arg_type_name) + } else { + let other_type_name = if let Some(size) = size { + other_type_name_owner = format!("ivec{}", size as u8); + &other_type_name_owner + } else { + "int" + }; + (FREXP_FUNCTION, "frexp", other_type_name) + }; + + writeln!(self.out)?; + if !self.options.version.supports_frexp_function() + && matches!(type_key, &crate::PredeclaredType::FrexpResult { .. }) + { + writeln!( + self.out, + "{struct_name} {defined_func_name}({arg_type_name} arg) {{ + {other_type_name} other = arg == {arg_type_name}(0) ? {other_type_name}(0) : {other_type_name}({arg_type_name}(1) + log2(arg)); + {arg_type_name} fract = arg * exp2({arg_type_name}(-other)); + return {struct_name}(fract, other); +}}", + )?; + } else { + writeln!( + self.out, + "{struct_name} {defined_func_name}({arg_type_name} arg) {{ + {other_type_name} other; + {arg_type_name} fract = {called_func_name}(arg, other); + return {struct_name}(fract, other); +}}", + )?; + } + } + &crate::PredeclaredType::AtomicCompareExchangeWeakResult(_) => { + // Handled by the general struct writing loop earlier. + } + } + } + + // Write all named constants + let mut constants = self + .module + .constants + .iter() + .filter(|&(_, c)| c.name.is_some()) + .peekable(); + while let Some((handle, _)) = constants.next() { + self.write_global_constant(handle)?; + // Add extra newline for readability on last iteration + if constants.peek().is_none() { + writeln!(self.out)?; + } + } + + let ep_info = self.info.get_entry_point(self.entry_point_idx as usize); + + // Write the globals + // + // Unless explicitly disabled with WriterFlags::INCLUDE_UNUSED_ITEMS, + // we filter all globals that aren't used by the selected entry point as they might be + // interfere with each other (i.e. two globals with the same location but different with + // different classes) + let include_unused = self + .options + .writer_flags + .contains(WriterFlags::INCLUDE_UNUSED_ITEMS); + for (handle, global) in self.module.global_variables.iter() { + let is_unused = ep_info[handle].is_empty(); + if !include_unused && is_unused { + continue; + } + + match self.module.types[global.ty].inner { + // We treat images separately because they might require + // writing the storage format + TypeInner::Image { + mut dim, + arrayed, + class, + } => { + // Gather the storage format if needed + let storage_format_access = match self.module.types[global.ty].inner { + TypeInner::Image { + class: crate::ImageClass::Storage { format, access }, + .. + } => Some((format, access)), + _ => None, + }; + + if dim == crate::ImageDimension::D1 && es { + dim = crate::ImageDimension::D2 + } + + // Gether the location if needed + let layout_binding = if self.options.version.supports_explicit_locations() { + let br = global.binding.as_ref().unwrap(); + self.options.binding_map.get(br).cloned() + } else { + None + }; + + // Write all the layout qualifiers + if layout_binding.is_some() || storage_format_access.is_some() { + write!(self.out, "layout(")?; + if let Some(binding) = layout_binding { + write!(self.out, "binding = {binding}")?; + } + if let Some((format, _)) = storage_format_access { + let format_str = glsl_storage_format(format)?; + let separator = match layout_binding { + Some(_) => ",", + None => "", + }; + write!(self.out, "{separator}{format_str}")?; + } + write!(self.out, ") ")?; + } + + if let Some((_, access)) = storage_format_access { + self.write_storage_access(access)?; + } + + // All images in glsl are `uniform` + // The trailing space is important + write!(self.out, "uniform ")?; + + // write the type + // + // This is way we need the leading space because `write_image_type` doesn't add + // any spaces at the beginning or end + self.write_image_type(dim, arrayed, class)?; + + // Finally write the name and end the global with a `;` + // The leading space is important + let global_name = self.get_global_name(handle, global); + writeln!(self.out, " {global_name};")?; + writeln!(self.out)?; + + self.reflection_names_globals.insert(handle, global_name); + } + // glsl has no concept of samplers so we just ignore it + TypeInner::Sampler { .. } => continue, + // All other globals are written by `write_global` + _ => { + self.write_global(handle, global)?; + // Add a newline (only for readability) + writeln!(self.out)?; + } + } + } + + for arg in self.entry_point.function.arguments.iter() { + self.write_varying(arg.binding.as_ref(), arg.ty, false)?; + } + if let Some(ref result) = self.entry_point.function.result { + self.write_varying(result.binding.as_ref(), result.ty, true)?; + } + writeln!(self.out)?; + + // Write all regular functions + for (handle, function) in self.module.functions.iter() { + // Check that the function doesn't use globals that aren't supported + // by the current entry point + if !include_unused && !ep_info.dominates_global_use(&self.info[handle]) { + continue; + } + + let fun_info = &self.info[handle]; + + // Skip functions that that are not compatible with this entry point's stage. + // + // When validation is enabled, it rejects modules whose entry points try to call + // incompatible functions, so if we got this far, then any functions incompatible + // with our selected entry point must not be used. + // + // When validation is disabled, `fun_info.available_stages` is always just + // `ShaderStages::all()`, so this will write all functions in the module, and + // the downstream GLSL compiler will catch any problems. + if !fun_info.available_stages.contains(ep_info.available_stages) { + continue; + } + + // Write the function + self.write_function(back::FunctionType::Function(handle), function, fun_info)?; + + writeln!(self.out)?; + } + + self.write_function( + back::FunctionType::EntryPoint(self.entry_point_idx), + &self.entry_point.function, + ep_info, + )?; + + // Add newline at the end of file + writeln!(self.out)?; + + // Collect all reflection info and return it to the user + self.collect_reflection_info() + } + + fn write_array_size( + &mut self, + base: Handle<crate::Type>, + size: crate::ArraySize, + ) -> BackendResult { + write!(self.out, "[")?; + + // Write the array size + // Writes nothing if `IndexableLength::Dynamic` + match size.resolve(self.module.to_ctx())? { + proc::IndexableLength::Known(size) => { + write!(self.out, "{size}")?; + } + proc::IndexableLength::Dynamic => (), + } + + write!(self.out, "]")?; + + if let TypeInner::Array { + base: next_base, + size: next_size, + .. + } = self.module.types[base].inner + { + self.write_array_size(next_base, next_size)?; + } + + Ok(()) + } + + /// Helper method used to write value types + /// + /// # Notes + /// Adds no trailing or leading whitespace + fn write_value_type(&mut self, inner: &TypeInner) -> BackendResult { + match *inner { + // Scalars are simple we just get the full name from `glsl_scalar` + TypeInner::Scalar(scalar) + | TypeInner::Atomic(scalar) + | TypeInner::ValuePointer { + size: None, + scalar, + space: _, + } => write!(self.out, "{}", glsl_scalar(scalar)?.full)?, + // Vectors are just `gvecN` where `g` is the scalar prefix and `N` is the vector size + TypeInner::Vector { size, scalar } + | TypeInner::ValuePointer { + size: Some(size), + scalar, + space: _, + } => write!(self.out, "{}vec{}", glsl_scalar(scalar)?.prefix, size as u8)?, + // Matrices are written with `gmatMxN` where `g` is the scalar prefix (only floats and + // doubles are allowed), `M` is the columns count and `N` is the rows count + // + // glsl supports a matrix shorthand `gmatN` where `N` = `M` but it doesn't justify the + // extra branch to write matrices this way + TypeInner::Matrix { + columns, + rows, + scalar, + } => write!( + self.out, + "{}mat{}x{}", + glsl_scalar(scalar)?.prefix, + columns as u8, + rows as u8 + )?, + // GLSL arrays are written as `type name[size]` + // Here we only write the size of the array i.e. `[size]` + // Base `type` and `name` should be written outside + TypeInner::Array { base, size, .. } => self.write_array_size(base, size)?, + // Write all variants instead of `_` so that if new variants are added a + // no exhaustiveness error is thrown + TypeInner::Pointer { .. } + | TypeInner::Struct { .. } + | TypeInner::Image { .. } + | TypeInner::Sampler { .. } + | TypeInner::AccelerationStructure { .. } + | TypeInner::RayQuery { .. } + | TypeInner::BindingArray { .. } => { + return Err(Error::Custom(format!("Unable to write type {inner:?}"))) + } + } + + Ok(()) + } + + /// Helper method used to write non image/sampler types + /// + /// # Notes + /// Adds no trailing or leading whitespace + fn write_type(&mut self, ty: Handle<crate::Type>) -> BackendResult { + match self.module.types[ty].inner { + // glsl has no pointer types so just write types as normal and loads are skipped + TypeInner::Pointer { base, .. } => self.write_type(base), + // glsl structs are written as just the struct name + TypeInner::Struct { .. } => { + // Get the struct name + let name = &self.names[&NameKey::Type(ty)]; + write!(self.out, "{name}")?; + Ok(()) + } + // glsl array has the size separated from the base type + TypeInner::Array { base, .. } => self.write_type(base), + ref other => self.write_value_type(other), + } + } + + /// Helper method to write a image type + /// + /// # Notes + /// Adds no leading or trailing whitespace + fn write_image_type( + &mut self, + dim: crate::ImageDimension, + arrayed: bool, + class: crate::ImageClass, + ) -> BackendResult { + // glsl images consist of four parts the scalar prefix, the image "type", the dimensions + // and modifiers + // + // There exists two image types + // - sampler - for sampled images + // - image - for storage images + // + // There are three possible modifiers that can be used together and must be written in + // this order to be valid + // - MS - used if it's a multisampled image + // - Array - used if it's an image array + // - Shadow - used if it's a depth image + use crate::ImageClass as Ic; + use crate::Scalar as S; + let float = S { + kind: crate::ScalarKind::Float, + width: 4, + }; + let (base, scalar, ms, comparison) = match class { + Ic::Sampled { kind, multi: true } => ("sampler", S { kind, width: 4 }, "MS", ""), + Ic::Sampled { kind, multi: false } => ("sampler", S { kind, width: 4 }, "", ""), + Ic::Depth { multi: true } => ("sampler", float, "MS", ""), + Ic::Depth { multi: false } => ("sampler", float, "", "Shadow"), + Ic::Storage { format, .. } => ("image", format.into(), "", ""), + Ic::External => unimplemented!(), + }; + + let precision = if self.options.version.is_es() { + "highp " + } else { + "" + }; + + write!( + self.out, + "{}{}{}{}{}{}{}", + precision, + glsl_scalar(scalar)?.prefix, + base, + glsl_dimension(dim), + ms, + if arrayed { "Array" } else { "" }, + comparison + )?; + + Ok(()) + } + + /// Helper method used by [Self::write_global] to write just the layout part of + /// a non image/sampler global variable, if applicable. + /// + /// # Notes + /// + /// Adds trailing whitespace if any layout qualifier is written + fn write_global_layout(&mut self, global: &crate::GlobalVariable) -> BackendResult { + // Determine which (if any) explicit memory layout to use, and whether we support it + let layout = match global.space { + crate::AddressSpace::Uniform => { + if !self.options.version.supports_std140_layout() { + return Err(Error::Custom( + "Uniform address space requires std140 layout support".to_string(), + )); + } + + Some("std140") + } + crate::AddressSpace::Storage { .. } => { + if !self.options.version.supports_std430_layout() { + return Err(Error::Custom( + "Storage address space requires std430 layout support".to_string(), + )); + } + + Some("std430") + } + _ => None, + }; + + // If our version supports explicit layouts, we can also output the explicit binding + // if we have it + if self.options.version.supports_explicit_locations() { + if let Some(ref br) = global.binding { + match self.options.binding_map.get(br) { + Some(binding) => { + write!(self.out, "layout(")?; + + if let Some(layout) = layout { + write!(self.out, "{layout}, ")?; + } + + write!(self.out, "binding = {binding}) ")?; + + return Ok(()); + } + None => { + log::debug!("unassigned binding for {:?}", global.name); + } + } + } + } + + // Either no explicit bindings are supported or we didn't have any. + // Write just the memory layout. + if let Some(layout) = layout { + write!(self.out, "layout({layout}) ")?; + } + + Ok(()) + } + + /// Helper method used to write non images/sampler globals + /// + /// # Notes + /// Adds a newline + /// + /// # Panics + /// If the global has type sampler + fn write_global( + &mut self, + handle: Handle<crate::GlobalVariable>, + global: &crate::GlobalVariable, + ) -> BackendResult { + self.write_global_layout(global)?; + + if let crate::AddressSpace::Storage { access } = global.space { + self.write_storage_access(access)?; + } + + if let Some(storage_qualifier) = glsl_storage_qualifier(global.space) { + write!(self.out, "{storage_qualifier} ")?; + } + + match global.space { + crate::AddressSpace::Private => { + self.write_simple_global(handle, global)?; + } + crate::AddressSpace::WorkGroup => { + self.write_simple_global(handle, global)?; + } + crate::AddressSpace::Immediate => { + self.write_simple_global(handle, global)?; + } + crate::AddressSpace::Uniform => { + self.write_interface_block(handle, global)?; + } + crate::AddressSpace::Storage { .. } => { + self.write_interface_block(handle, global)?; + } + crate::AddressSpace::TaskPayload => { + self.write_interface_block(handle, global)?; + } + // A global variable in the `Function` address space is a + // contradiction in terms. + crate::AddressSpace::Function => unreachable!(), + // Textures and samplers are handled directly in `Writer::write`. + crate::AddressSpace::Handle => unreachable!(), + } + + Ok(()) + } + + fn write_simple_global( + &mut self, + handle: Handle<crate::GlobalVariable>, + global: &crate::GlobalVariable, + ) -> BackendResult { + self.write_type(global.ty)?; + write!(self.out, " ")?; + self.write_global_name(handle, global)?; + + if let TypeInner::Array { base, size, .. } = self.module.types[global.ty].inner { + self.write_array_size(base, size)?; + } + + if global.space.initializable() && is_value_init_supported(self.module, global.ty) { + write!(self.out, " = ")?; + if let Some(init) = global.init { + self.write_const_expr(init, &self.module.global_expressions)?; + } else { + self.write_zero_init_value(global.ty)?; + } + } + + writeln!(self.out, ";")?; + + if let crate::AddressSpace::Immediate = global.space { + let global_name = self.get_global_name(handle, global); + self.reflection_names_globals.insert(handle, global_name); + } + + Ok(()) + } + + /// Write an interface block for a single Naga global. + /// + /// Write `block_name { members }`. Since `block_name` must be unique + /// between blocks and structs, we add `_block_ID` where `ID` is a + /// `IdGenerator` generated number. Write `members` in the same way we write + /// a struct's members. + fn write_interface_block( + &mut self, + handle: Handle<crate::GlobalVariable>, + global: &crate::GlobalVariable, + ) -> BackendResult { + // Write the block name, it's just the struct name appended with `_block_ID` + let ty_name = &self.names[&NameKey::Type(global.ty)]; + let block_name = format!( + "{}_block_{}{:?}", + // avoid double underscores as they are reserved in GLSL + ty_name.trim_end_matches('_'), + self.block_id.generate(), + self.entry_point.stage, + ); + write!(self.out, "{block_name} ")?; + self.reflection_names_globals.insert(handle, block_name); + + match self.module.types[global.ty].inner { + TypeInner::Struct { ref members, .. } + if self.module.types[members.last().unwrap().ty] + .inner + .is_dynamically_sized(&self.module.types) => + { + // Structs with dynamically sized arrays must have their + // members lifted up as members of the interface block. GLSL + // can't write such struct types anyway. + self.write_struct_body(global.ty, members)?; + write!(self.out, " ")?; + self.write_global_name(handle, global)?; + } + _ => { + // A global of any other type is written as the sole member + // of the interface block. Since the interface block is + // anonymous, this becomes visible in the global scope. + write!(self.out, "{{ ")?; + self.write_type(global.ty)?; + write!(self.out, " ")?; + self.write_global_name(handle, global)?; + if let TypeInner::Array { base, size, .. } = self.module.types[global.ty].inner { + self.write_array_size(base, size)?; + } + write!(self.out, "; }}")?; + } + } + + writeln!(self.out, ";")?; + + Ok(()) + } + + /// Helper method used to find which expressions of a given function require baking + /// + /// # Notes + /// Clears `need_bake_expressions` set before adding to it + fn update_expressions_to_bake(&mut self, func: &crate::Function, info: &valid::FunctionInfo) { + use crate::Expression; + self.need_bake_expressions.clear(); + for (fun_handle, expr) in func.expressions.iter() { + let expr_info = &info[fun_handle]; + let min_ref_count = func.expressions[fun_handle].bake_ref_count(); + if min_ref_count <= expr_info.ref_count { + self.need_bake_expressions.insert(fun_handle); + } + + let inner = expr_info.ty.inner_with(&self.module.types); + + if let Expression::Math { + fun, + arg, + arg1, + arg2, + .. + } = *expr + { + match fun { + crate::MathFunction::Dot => { + // if the expression is a Dot product with integer arguments, + // then the args needs baking as well + if let TypeInner::Scalar(crate::Scalar { + kind: crate::ScalarKind::Sint | crate::ScalarKind::Uint, + .. + }) = *inner + { + self.need_bake_expressions.insert(arg); + self.need_bake_expressions.insert(arg1.unwrap()); + } + } + crate::MathFunction::Dot4U8Packed | crate::MathFunction::Dot4I8Packed => { + self.need_bake_expressions.insert(arg); + self.need_bake_expressions.insert(arg1.unwrap()); + } + crate::MathFunction::Pack4xI8 + | crate::MathFunction::Pack4xU8 + | crate::MathFunction::Pack4xI8Clamp + | crate::MathFunction::Pack4xU8Clamp + | crate::MathFunction::Unpack4xI8 + | crate::MathFunction::Unpack4xU8 + | crate::MathFunction::QuantizeToF16 => { + self.need_bake_expressions.insert(arg); + } + /* crate::MathFunction::Pack4x8unorm | */ + crate::MathFunction::Unpack4x8snorm + if !self.options.version.supports_pack_unpack_4x8() => + { + // We have a fallback if the platform doesn't natively support these + self.need_bake_expressions.insert(arg); + } + /* crate::MathFunction::Pack4x8unorm | */ + crate::MathFunction::Unpack4x8unorm + if !self.options.version.supports_pack_unpack_4x8() => + { + self.need_bake_expressions.insert(arg); + } + /* crate::MathFunction::Pack2x16snorm | */ + crate::MathFunction::Unpack2x16snorm + if !self.options.version.supports_pack_unpack_snorm_2x16() => + { + self.need_bake_expressions.insert(arg); + } + /* crate::MathFunction::Pack2x16unorm | */ + crate::MathFunction::Unpack2x16unorm + if !self.options.version.supports_pack_unpack_unorm_2x16() => + { + self.need_bake_expressions.insert(arg); + } + crate::MathFunction::ExtractBits => { + // Only argument 1 is re-used. + self.need_bake_expressions.insert(arg1.unwrap()); + } + crate::MathFunction::InsertBits => { + // Only argument 2 is re-used. + self.need_bake_expressions.insert(arg2.unwrap()); + } + crate::MathFunction::CountLeadingZeros => { + if let Some(crate::ScalarKind::Sint) = inner.scalar_kind() { + self.need_bake_expressions.insert(arg); + } + } + _ => {} + } + } + } + + for statement in func.body.iter() { + match *statement { + crate::Statement::Atomic { + fun: crate::AtomicFunction::Exchange { compare: Some(cmp) }, + .. + } => { + self.need_bake_expressions.insert(cmp); + } + _ => {} + } + } + } + + /// Helper method used to get a name for a global + /// + /// Globals have different naming schemes depending on their binding: + /// - Globals without bindings use the name from the [`Namer`](crate::proc::Namer) + /// - Globals with resource binding are named `_group_X_binding_Y` where `X` + /// is the group and `Y` is the binding + fn get_global_name( + &self, + handle: Handle<crate::GlobalVariable>, + global: &crate::GlobalVariable, + ) -> String { + match (&global.binding, global.space) { + (&Some(ref br), _) => { + format!( + "_group_{}_binding_{}_{}", + br.group, + br.binding, + self.entry_point.stage.to_str() + ) + } + (&None, crate::AddressSpace::Immediate) => { + format!("_immediates_binding_{}", self.entry_point.stage.to_str()) + } + (&None, _) => self.names[&NameKey::GlobalVariable(handle)].clone(), + } + } + + /// Helper method used to write a name for a global without additional heap allocation + fn write_global_name( + &mut self, + handle: Handle<crate::GlobalVariable>, + global: &crate::GlobalVariable, + ) -> BackendResult { + match (&global.binding, global.space) { + (&Some(ref br), _) => write!( + self.out, + "_group_{}_binding_{}_{}", + br.group, + br.binding, + self.entry_point.stage.to_str() + )?, + (&None, crate::AddressSpace::Immediate) => write!( + self.out, + "_immediates_binding_{}", + self.entry_point.stage.to_str() + )?, + (&None, _) => write!( + self.out, + "{}", + &self.names[&NameKey::GlobalVariable(handle)] + )?, + } + + Ok(()) + } + + /// Write a GLSL global that will carry a Naga entry point's argument or return value. + /// + /// A Naga entry point's arguments and return value are rendered in GLSL as + /// variables at global scope with the `in` and `out` storage qualifiers. + /// The code we generate for `main` loads from all the `in` globals into + /// appropriately named locals. Before it returns, `main` assigns the + /// components of its return value into all the `out` globals. + /// + /// This function writes a declaration for one such GLSL global, + /// representing a value passed into or returned from [`self.entry_point`] + /// that has a [`Location`] binding. The global's name is generated based on + /// the location index and the shader stages being connected; see + /// [`VaryingName`]. This means we don't need to know the names of + /// arguments, just their types and bindings. + /// + /// Emit nothing for entry point arguments or return values with [`BuiltIn`] + /// bindings; `main` will read from or assign to the appropriate GLSL + /// special variable; these are pre-declared. As an exception, we do declare + /// `gl_Position` or `gl_FragCoord` with the `invariant` qualifier if + /// needed. + /// + /// Use `output` together with [`self.entry_point.stage`] to determine which + /// shader stages are being connected, and choose the `in` or `out` storage + /// qualifier. + /// + /// [`self.entry_point`]: Writer::entry_point + /// [`self.entry_point.stage`]: crate::EntryPoint::stage + /// [`Location`]: crate::Binding::Location + /// [`BuiltIn`]: crate::Binding::BuiltIn + fn write_varying( + &mut self, + binding: Option<&crate::Binding>, + ty: Handle<crate::Type>, + output: bool, + ) -> Result<(), Error> { + // For a struct, emit a separate global for each member with a binding. + if let TypeInner::Struct { ref members, .. } = self.module.types[ty].inner { + for member in members { + self.write_varying(member.binding.as_ref(), member.ty, output)?; + } + return Ok(()); + } + + let binding = match binding { + None => return Ok(()), + Some(binding) => binding, + }; + + let (location, interpolation, sampling, blend_src) = match *binding { + crate::Binding::Location { + location, + interpolation, + sampling, + blend_src, + per_primitive: _, + } => (location, interpolation, sampling, blend_src), + crate::Binding::BuiltIn(built_in) => { + match built_in { + crate::BuiltIn::Position { invariant: true } => { + match (self.options.version, self.entry_point.stage) { + ( + Version::Embedded { + version: 300, + is_webgl: true, + }, + ShaderStage::Fragment, + ) => { + // `invariant gl_FragCoord` is not allowed in WebGL2 and possibly + // OpenGL ES in general (waiting on confirmation). + // + // See https://github.com/KhronosGroup/WebGL/issues/3518 + } + _ => { + writeln!( + self.out, + "invariant {};", + glsl_built_in( + built_in, + VaryingOptions::from_writer_options(self.options, output) + ) + )?; + } + } + } + crate::BuiltIn::ClipDistance => { + // Re-declare `gl_ClipDistance` with number of clip planes. + let TypeInner::Array { size, .. } = self.module.types[ty].inner else { + unreachable!(); + }; + let proc::IndexableLength::Known(size) = + size.resolve(self.module.to_ctx())? + else { + unreachable!(); + }; + self.clip_distance_count = size; + writeln!(self.out, "out float gl_ClipDistance[{size}];")?; + } + _ => {} + } + return Ok(()); + } + }; + + // Write the interpolation modifier if needed + // + // We ignore all interpolation and auxiliary modifiers that aren't used in fragment + // shaders' input globals or vertex shaders' output globals. + let emit_interpolation_and_auxiliary = match self.entry_point.stage { + ShaderStage::Vertex => output, + ShaderStage::Fragment => !output, + ShaderStage::Compute => false, + ShaderStage::Task | ShaderStage::Mesh => unreachable!(), + }; + + // Write the I/O locations, if allowed + let io_location = if self.options.version.supports_explicit_locations() + || !emit_interpolation_and_auxiliary + { + if self.options.version.supports_io_locations() { + if let Some(blend_src) = blend_src { + write!( + self.out, + "layout(location = {location}, index = {blend_src}) " + )?; + } else { + write!(self.out, "layout(location = {location}) ")?; + } + None + } else { + Some(VaryingLocation { + location, + index: blend_src.unwrap_or(0), + }) + } + } else { + None + }; + + // Write the interpolation qualifier. + if let Some(interp) = interpolation { + if emit_interpolation_and_auxiliary { + write!(self.out, "{} ", glsl_interpolation(interp))?; + } + } + + // Write the sampling auxiliary qualifier. + // + // Before GLSL 4.2, the `centroid` and `sample` qualifiers were required to appear + // immediately before the `in` / `out` qualifier, so we'll just follow that rule + // here, regardless of the version. + if let Some(sampling) = sampling { + if emit_interpolation_and_auxiliary { + if let Some(qualifier) = glsl_sampling(sampling)? { + write!(self.out, "{qualifier} ")?; + } + } + } + + // Write the input/output qualifier. + write!(self.out, "{} ", if output { "out" } else { "in" })?; + + // Write the type + // `write_type` adds no leading or trailing spaces + self.write_type(ty)?; + + // Finally write the global name and end the global with a `;` and a newline + // Leading space is important + let vname = VaryingName { + binding: &crate::Binding::Location { + location, + interpolation: None, + sampling: None, + blend_src, + per_primitive: false, + }, + stage: self.entry_point.stage, + options: VaryingOptions::from_writer_options(self.options, output), + }; + writeln!(self.out, " {vname};")?; + + if let Some(location) = io_location { + self.varying.insert(vname.to_string(), location); + } + + Ok(()) + } + + /// Helper method used to write functions (both entry points and regular functions) + /// + /// # Notes + /// Adds a newline + fn write_function( + &mut self, + ty: back::FunctionType, + func: &crate::Function, + info: &valid::FunctionInfo, + ) -> BackendResult { + // Create a function context for the function being written + let ctx = back::FunctionCtx { + ty, + info, + expressions: &func.expressions, + named_expressions: &func.named_expressions, + }; + + self.named_expressions.clear(); + self.update_expressions_to_bake(func, info); + + // Write the function header + // + // glsl headers are the same as in c: + // `ret_type name(args)` + // `ret_type` is the return type + // `name` is the function name + // `args` is a comma separated list of `type name` + // | - `type` is the argument type + // | - `name` is the argument name + + // Start by writing the return type if any otherwise write void + // This is the only place where `void` is a valid type + // (though it's more a keyword than a type) + if let back::FunctionType::EntryPoint(_) = ctx.ty { + write!(self.out, "void")?; + } else if let Some(ref result) = func.result { + self.write_type(result.ty)?; + if let TypeInner::Array { base, size, .. } = self.module.types[result.ty].inner { + self.write_array_size(base, size)? + } + } else { + write!(self.out, "void")?; + } + + // Write the function name and open parentheses for the argument list + let function_name = match ctx.ty { + back::FunctionType::Function(handle) => &self.names[&NameKey::Function(handle)], + back::FunctionType::EntryPoint(_) => "main", + }; + write!(self.out, " {function_name}(")?; + + // Write the comma separated argument list + // + // We need access to `Self` here so we use the reference passed to the closure as an + // argument instead of capturing as that would cause a borrow checker error + let arguments = match ctx.ty { + back::FunctionType::EntryPoint(_) => &[][..], + back::FunctionType::Function(_) => &func.arguments, + }; + let arguments: Vec<_> = arguments + .iter() + .enumerate() + .filter(|&(_, arg)| match self.module.types[arg.ty].inner { + TypeInner::Sampler { .. } => false, + _ => true, + }) + .collect(); + self.write_slice(&arguments, |this, _, &(i, arg)| { + // Write the argument type + match this.module.types[arg.ty].inner { + // We treat images separately because they might require + // writing the storage format + TypeInner::Image { + dim, + arrayed, + class, + } => { + // Write the storage format if needed + if let TypeInner::Image { + class: crate::ImageClass::Storage { format, .. }, + .. + } = this.module.types[arg.ty].inner + { + write!(this.out, "layout({}) ", glsl_storage_format(format)?)?; + } + + // write the type + // + // This is way we need the leading space because `write_image_type` doesn't add + // any spaces at the beginning or end + this.write_image_type(dim, arrayed, class)?; + } + TypeInner::Pointer { base, .. } => { + // write parameter qualifiers + write!(this.out, "inout ")?; + this.write_type(base)?; + } + // All other types are written by `write_type` + _ => { + this.write_type(arg.ty)?; + } + } + + // Write the argument name + // The leading space is important + write!(this.out, " {}", &this.names[&ctx.argument_key(i as u32)])?; + + // Write array size + match this.module.types[arg.ty].inner { + TypeInner::Array { base, size, .. } => { + this.write_array_size(base, size)?; + } + TypeInner::Pointer { base, .. } => { + if let TypeInner::Array { base, size, .. } = this.module.types[base].inner { + this.write_array_size(base, size)?; + } + } + _ => {} + } + + Ok(()) + })?; + + // Close the parentheses and open braces to start the function body + writeln!(self.out, ") {{")?; + + if self.options.zero_initialize_workgroup_memory + && ctx.ty.is_compute_like_entry_point(self.module) + { + self.write_workgroup_variables_initialization(&ctx)?; + } + + // Compose the function arguments from globals, in case of an entry point. + if let back::FunctionType::EntryPoint(ep_index) = ctx.ty { + let stage = self.module.entry_points[ep_index as usize].stage; + for (index, arg) in func.arguments.iter().enumerate() { + write!(self.out, "{}", back::INDENT)?; + self.write_type(arg.ty)?; + let name = &self.names[&NameKey::EntryPointArgument(ep_index, index as u32)]; + write!(self.out, " {name}")?; + write!(self.out, " = ")?; + match self.module.types[arg.ty].inner { + TypeInner::Struct { ref members, .. } => { + self.write_type(arg.ty)?; + write!(self.out, "(")?; + for (index, member) in members.iter().enumerate() { + let varying_name = VaryingName { + binding: member.binding.as_ref().unwrap(), + stage, + options: VaryingOptions::from_writer_options(self.options, false), + }; + if index != 0 { + write!(self.out, ", ")?; + } + write!(self.out, "{varying_name}")?; + } + writeln!(self.out, ");")?; + } + _ => { + let varying_name = VaryingName { + binding: arg.binding.as_ref().unwrap(), + stage, + options: VaryingOptions::from_writer_options(self.options, false), + }; + writeln!(self.out, "{varying_name};")?; + } + } + } + } + + // Write all function locals + // Locals are `type name (= init)?;` where the init part (including the =) are optional + // + // Always adds a newline + for (handle, local) in func.local_variables.iter() { + // Write indentation (only for readability) and the type + // `write_type` adds no trailing space + write!(self.out, "{}", back::INDENT)?; + self.write_type(local.ty)?; + + // Write the local name + // The leading space is important + write!(self.out, " {}", self.names[&ctx.name_key(handle)])?; + // Write size for array type + if let TypeInner::Array { base, size, .. } = self.module.types[local.ty].inner { + self.write_array_size(base, size)?; + } + // Write the local initializer if needed + if let Some(init) = local.init { + // Put the equal signal only if there's a initializer + // The leading and trailing spaces aren't needed but help with readability + write!(self.out, " = ")?; + + // Write the constant + // `write_constant` adds no trailing or leading space/newline + self.write_expr(init, &ctx)?; + } else if is_value_init_supported(self.module, local.ty) { + write!(self.out, " = ")?; + self.write_zero_init_value(local.ty)?; + } + + // Finish the local with `;` and add a newline (only for readability) + writeln!(self.out, ";")? + } + + // Write the function body (statement list) + for sta in func.body.iter() { + // Write a statement, the indentation should always be 1 when writing the function body + // `write_stmt` adds a newline + self.write_stmt(sta, &ctx, back::Level(1))?; + } + + // Close braces and add a newline + writeln!(self.out, "}}")?; + + Ok(()) + } + + fn write_workgroup_variables_initialization( + &mut self, + ctx: &back::FunctionCtx, + ) -> BackendResult { + let mut vars = self + .module + .global_variables + .iter() + .filter(|&(handle, var)| { + !ctx.info[handle].is_empty() && var.space == crate::AddressSpace::WorkGroup + }) + .peekable(); + + if vars.peek().is_some() { + let level = back::Level(1); + + writeln!(self.out, "{level}if (gl_LocalInvocationID == uvec3(0u)) {{")?; + + for (handle, var) in vars { + let name = &self.names[&NameKey::GlobalVariable(handle)]; + write!(self.out, "{}{} = ", level.next(), name)?; + self.write_zero_init_value(var.ty)?; + writeln!(self.out, ";")?; + } + + writeln!(self.out, "{level}}}")?; + self.write_control_barrier(crate::Barrier::WORK_GROUP, level)?; + } + + Ok(()) + } + + /// Write a list of comma separated `T` values using a writer function `F`. + /// + /// The writer function `F` receives a mutable reference to `self` that if needed won't cause + /// borrow checker issues (using for example a closure with `self` will cause issues), the + /// second argument is the 0 based index of the element on the list, and the last element is + /// a reference to the element `T` being written + /// + /// # Notes + /// - Adds no newlines or leading/trailing whitespace + /// - The last element won't have a trailing `,` + fn write_slice<T, F: FnMut(&mut Self, u32, &T) -> BackendResult>( + &mut self, + data: &[T], + mut f: F, + ) -> BackendResult { + // Loop through `data` invoking `f` for each element + for (index, item) in data.iter().enumerate() { + if index != 0 { + write!(self.out, ", ")?; + } + f(self, index as u32, item)?; + } + + Ok(()) + } + + /// Helper method used to write global constants + fn write_global_constant(&mut self, handle: Handle<crate::Constant>) -> BackendResult { + write!(self.out, "const ")?; + let constant = &self.module.constants[handle]; + self.write_type(constant.ty)?; + let name = &self.names[&NameKey::Constant(handle)]; + write!(self.out, " {name}")?; + if let TypeInner::Array { base, size, .. } = self.module.types[constant.ty].inner { + self.write_array_size(base, size)?; + } + write!(self.out, " = ")?; + self.write_const_expr(constant.init, &self.module.global_expressions)?; + writeln!(self.out, ";")?; + Ok(()) + } + + /// Helper method used to output a dot product as an arithmetic expression + /// + fn write_dot_product( + &mut self, + arg: Handle<crate::Expression>, + arg1: Handle<crate::Expression>, + size: usize, + ctx: &back::FunctionCtx, + ) -> BackendResult { + // Write parentheses around the dot product expression to prevent operators + // with different precedences from applying earlier. + write!(self.out, "(")?; + + // Cycle through all the components of the vector + for index in 0..size { + let component = back::COMPONENTS[index]; + // Write the addition to the previous product + // This will print an extra '+' at the beginning but that is fine in glsl + write!(self.out, " + ")?; + // Write the first vector expression, this expression is marked to be + // cached so unless it can't be cached (for example, it's a Constant) + // it shouldn't produce large expressions. + self.write_expr(arg, ctx)?; + // Access the current component on the first vector + write!(self.out, ".{component} * ")?; + // Write the second vector expression, this expression is marked to be + // cached so unless it can't be cached (for example, it's a Constant) + // it shouldn't produce large expressions. + self.write_expr(arg1, ctx)?; + // Access the current component on the second vector + write!(self.out, ".{component}")?; + } + + write!(self.out, ")")?; + Ok(()) + } + + /// Helper method used to write structs + /// + /// # Notes + /// Ends in a newline + fn write_struct_body( + &mut self, + handle: Handle<crate::Type>, + members: &[crate::StructMember], + ) -> BackendResult { + // glsl structs are written as in C + // `struct name() { members };` + // | `struct` is a keyword + // | `name` is the struct name + // | `members` is a semicolon separated list of `type name` + // | `type` is the member type + // | `name` is the member name + writeln!(self.out, "{{")?; + + for (idx, member) in members.iter().enumerate() { + // The indentation is only for readability + write!(self.out, "{}", back::INDENT)?; + + match self.module.types[member.ty].inner { + TypeInner::Array { + base, + size, + stride: _, + } => { + self.write_type(base)?; + write!( + self.out, + " {}", + &self.names[&NameKey::StructMember(handle, idx as u32)] + )?; + // Write [size] + self.write_array_size(base, size)?; + // Newline is important + writeln!(self.out, ";")?; + } + _ => { + // Write the member type + // Adds no trailing space + self.write_type(member.ty)?; + + // Write the member name and put a semicolon + // The leading space is important + // All members must have a semicolon even the last one + writeln!( + self.out, + " {};", + &self.names[&NameKey::StructMember(handle, idx as u32)] + )?; + } + } + } + + write!(self.out, "}}")?; + Ok(()) + } + + /// Helper method used to write statements + /// + /// # Notes + /// Always adds a newline + fn write_stmt( + &mut self, + sta: &crate::Statement, + ctx: &back::FunctionCtx, + level: back::Level, + ) -> BackendResult { + use crate::Statement; + + match *sta { + // This is where we can generate intermediate constants for some expression types. + Statement::Emit(ref range) => { + for handle in range.clone() { + let ptr_class = ctx.resolve_type(handle, &self.module.types).pointer_space(); + let expr_name = if ptr_class.is_some() { + // GLSL can't save a pointer-valued expression in a variable, + // but we shouldn't ever need to: they should never be named expressions, + // and none of the expression types flagged by bake_ref_count can be pointer-valued. + None + } else if let Some(name) = ctx.named_expressions.get(&handle) { + // Front end provides names for all variables at the start of writing. + // But we write them to step by step. We need to recache them + // Otherwise, we could accidentally write variable name instead of full expression. + // Also, we use sanitized names! It defense backend from generating variable with name from reserved keywords. + Some(self.namer.call(name)) + } else if self.need_bake_expressions.contains(&handle) { + Some(Baked(handle).to_string()) + } else { + None + }; + + // If we are going to write an `ImageLoad` next and the target image + // is sampled and we are using the `Restrict` policy for bounds + // checking images we need to write a local holding the clamped lod. + if let crate::Expression::ImageLoad { + image, + level: Some(level_expr), + .. + } = ctx.expressions[handle] + { + if let TypeInner::Image { + class: crate::ImageClass::Sampled { .. }, + .. + } = *ctx.resolve_type(image, &self.module.types) + { + if let proc::BoundsCheckPolicy::Restrict = self.policies.image_load { + write!(self.out, "{level}")?; + self.write_clamped_lod(ctx, handle, image, level_expr)? + } + } + } + + if let Some(name) = expr_name { + write!(self.out, "{level}")?; + self.write_named_expr(handle, name, handle, ctx)?; + } + } + } + // Blocks are simple we just need to write the block statements between braces + // We could also just print the statements but this is more readable and maps more + // closely to the IR + Statement::Block(ref block) => { + write!(self.out, "{level}")?; + writeln!(self.out, "{{")?; + for sta in block.iter() { + // Increase the indentation to help with readability + self.write_stmt(sta, ctx, level.next())? + } + writeln!(self.out, "{level}}}")? + } + // Ifs are written as in C: + // ``` + // if(condition) { + // accept + // } else { + // reject + // } + // ``` + Statement::If { + condition, + ref accept, + ref reject, + } => { + write!(self.out, "{level}")?; + write!(self.out, "if (")?; + self.write_expr(condition, ctx)?; + writeln!(self.out, ") {{")?; + + for sta in accept { + // Increase indentation to help with readability + self.write_stmt(sta, ctx, level.next())?; + } + + // If there are no statements in the reject block we skip writing it + // This is only for readability + if !reject.is_empty() { + writeln!(self.out, "{level}}} else {{")?; + + for sta in reject { + // Increase indentation to help with readability + self.write_stmt(sta, ctx, level.next())?; + } + } + + writeln!(self.out, "{level}}}")? + } + // Switch are written as in C: + // ``` + // switch (selector) { + // // Fallthrough + // case label: + // block + // // Non fallthrough + // case label: + // block + // break; + // default: + // block + // } + // ``` + // Where the `default` case happens isn't important but we put it last + // so that we don't need to print a `break` for it + Statement::Switch { + selector, + ref cases, + } => { + let l2 = level.next(); + // Some GLSL consumers may not handle switches with a single + // body correctly: See wgpu#4514. Write such switch statements + // as a `do {} while(false);` loop instead. + // + // Since doing so may inadvertently capture `continue` + // statements in the switch body, we must apply continue + // forwarding. See the `naga::back::continue_forward` module + // docs for details. + let one_body = cases + .iter() + .rev() + .skip(1) + .all(|case| case.fall_through && case.body.is_empty()); + if one_body { + // Unlike HLSL, in GLSL `continue_ctx` only needs to know + // about [`Switch`] statements that are being rendered as + // `do-while` loops. + if let Some(variable) = self.continue_ctx.enter_switch(&mut self.namer) { + writeln!(self.out, "{level}bool {variable} = false;",)?; + }; + writeln!(self.out, "{level}do {{")?; + // Note: Expressions have no side-effects so we don't need to emit selector expression. + + // Body + if let Some(case) = cases.last() { + for sta in case.body.iter() { + self.write_stmt(sta, ctx, l2)?; + } + } + // End do-while + writeln!(self.out, "{level}}} while(false);")?; + + // Handle any forwarded continue statements. + use back::continue_forward::ExitControlFlow; + let op = match self.continue_ctx.exit_switch() { + ExitControlFlow::None => None, + ExitControlFlow::Continue { variable } => Some(("continue", variable)), + ExitControlFlow::Break { variable } => Some(("break", variable)), + }; + if let Some((control_flow, variable)) = op { + writeln!(self.out, "{level}if ({variable}) {{")?; + writeln!(self.out, "{l2}{control_flow};")?; + writeln!(self.out, "{level}}}")?; + } + } else { + // Start the switch + write!(self.out, "{level}")?; + write!(self.out, "switch(")?; + self.write_expr(selector, ctx)?; + writeln!(self.out, ") {{")?; + + // Write all cases + for case in cases { + match case.value { + crate::SwitchValue::I32(value) => { + write!(self.out, "{l2}case {value}:")? + } + crate::SwitchValue::U32(value) => { + write!(self.out, "{l2}case {value}u:")? + } + crate::SwitchValue::Default => write!(self.out, "{l2}default:")?, + } + + let write_block_braces = !(case.fall_through && case.body.is_empty()); + if write_block_braces { + writeln!(self.out, " {{")?; + } else { + writeln!(self.out)?; + } + + for sta in case.body.iter() { + self.write_stmt(sta, ctx, l2.next())?; + } + + if !case.fall_through && case.body.last().is_none_or(|s| !s.is_terminator()) + { + writeln!(self.out, "{}break;", l2.next())?; + } + + if write_block_braces { + writeln!(self.out, "{l2}}}")?; + } + } + + writeln!(self.out, "{level}}}")? + } + } + // Loops in naga IR are based on wgsl loops, glsl can emulate the behaviour by using a + // while true loop and appending the continuing block to the body resulting on: + // ``` + // bool loop_init = true; + // while(true) { + // if (!loop_init) { <continuing> } + // loop_init = false; + // <body> + // } + // ``` + Statement::Loop { + ref body, + ref continuing, + break_if, + } => { + self.continue_ctx.enter_loop(); + if !continuing.is_empty() || break_if.is_some() { + let gate_name = self.namer.call("loop_init"); + writeln!(self.out, "{level}bool {gate_name} = true;")?; + writeln!(self.out, "{level}while(true) {{")?; + let l2 = level.next(); + let l3 = l2.next(); + writeln!(self.out, "{l2}if (!{gate_name}) {{")?; + for sta in continuing { + self.write_stmt(sta, ctx, l3)?; + } + if let Some(condition) = break_if { + write!(self.out, "{l3}if (")?; + self.write_expr(condition, ctx)?; + writeln!(self.out, ") {{")?; + writeln!(self.out, "{}break;", l3.next())?; + writeln!(self.out, "{l3}}}")?; + } + writeln!(self.out, "{l2}}}")?; + writeln!(self.out, "{}{} = false;", level.next(), gate_name)?; + } else { + writeln!(self.out, "{level}while(true) {{")?; + } + for sta in body { + self.write_stmt(sta, ctx, level.next())?; + } + writeln!(self.out, "{level}}}")?; + self.continue_ctx.exit_loop(); + } + // Break, continue and return as written as in C + // `break;` + Statement::Break => { + write!(self.out, "{level}")?; + writeln!(self.out, "break;")? + } + // `continue;` + Statement::Continue => { + // Sometimes we must render a `Continue` statement as a `break`. + // See the docs for the `back::continue_forward` module. + if let Some(variable) = self.continue_ctx.continue_encountered() { + writeln!(self.out, "{level}{variable} = true;",)?; + writeln!(self.out, "{level}break;")? + } else { + writeln!(self.out, "{level}continue;")? + } + } + // `return expr;`, `expr` is optional + Statement::Return { value } => { + write!(self.out, "{level}")?; + match ctx.ty { + back::FunctionType::Function(_) => { + write!(self.out, "return")?; + // Write the expression to be returned if needed + if let Some(expr) = value { + write!(self.out, " ")?; + self.write_expr(expr, ctx)?; + } + writeln!(self.out, ";")?; + } + back::FunctionType::EntryPoint(ep_index) => { + let mut has_point_size = false; + let ep = &self.module.entry_points[ep_index as usize]; + if let Some(ref result) = ep.function.result { + let value = value.unwrap(); + match self.module.types[result.ty].inner { + TypeInner::Struct { ref members, .. } => { + let temp_struct_name = match ctx.expressions[value] { + crate::Expression::Compose { .. } => { + let return_struct = "_tmp_return"; + write!( + self.out, + "{} {} = ", + &self.names[&NameKey::Type(result.ty)], + return_struct + )?; + self.write_expr(value, ctx)?; + writeln!(self.out, ";")?; + write!(self.out, "{level}")?; + Some(return_struct) + } + _ => None, + }; + + for (index, member) in members.iter().enumerate() { + if let Some(crate::Binding::BuiltIn( + crate::BuiltIn::PointSize, + )) = member.binding + { + has_point_size = true; + } + + let varying_name = VaryingName { + binding: member.binding.as_ref().unwrap(), + stage: ep.stage, + options: VaryingOptions::from_writer_options( + self.options, + true, + ), + }; + write!(self.out, "{varying_name} = ")?; + + if let Some(struct_name) = temp_struct_name { + write!(self.out, "{struct_name}")?; + } else { + self.write_expr(value, ctx)?; + } + + // Write field name + writeln!( + self.out, + ".{};", + &self.names + [&NameKey::StructMember(result.ty, index as u32)] + )?; + write!(self.out, "{level}")?; + } + } + _ => { + let name = VaryingName { + binding: result.binding.as_ref().unwrap(), + stage: ep.stage, + options: VaryingOptions::from_writer_options( + self.options, + true, + ), + }; + write!(self.out, "{name} = ")?; + self.write_expr(value, ctx)?; + writeln!(self.out, ";")?; + write!(self.out, "{level}")?; + } + } + } + + let is_vertex_stage = self.module.entry_points[ep_index as usize].stage + == ShaderStage::Vertex; + if is_vertex_stage + && self + .options + .writer_flags + .contains(WriterFlags::ADJUST_COORDINATE_SPACE) + { + writeln!( + self.out, + "gl_Position.yz = vec2(-gl_Position.y, gl_Position.z * 2.0 - gl_Position.w);", + )?; + write!(self.out, "{level}")?; + } + + if is_vertex_stage + && self + .options + .writer_flags + .contains(WriterFlags::FORCE_POINT_SIZE) + && !has_point_size + { + writeln!(self.out, "gl_PointSize = 1.0;")?; + write!(self.out, "{level}")?; + } + writeln!(self.out, "return;")?; + } + } + } + // This is one of the places were glsl adds to the syntax of C in this case the discard + // keyword which ceases all further processing in a fragment shader, it's called OpKill + // in spir-v that's why it's called `Statement::Kill` + Statement::Kill => writeln!(self.out, "{level}discard;")?, + Statement::ControlBarrier(flags) => { + self.write_control_barrier(flags, level)?; + } + Statement::MemoryBarrier(flags) => { + self.write_memory_barrier(flags, level)?; + } + // Stores in glsl are just variable assignments written as `pointer = value;` + Statement::Store { pointer, value } => { + write!(self.out, "{level}")?; + self.write_expr(pointer, ctx)?; + write!(self.out, " = ")?; + self.write_expr(value, ctx)?; + writeln!(self.out, ";")? + } + Statement::WorkGroupUniformLoad { pointer, result } => { + // GLSL doesn't have pointers, which means that this backend needs to ensure that + // the actual "loading" is happening between the two barriers. + // This is done in `Emit` by never emitting a variable name for pointer variables + self.write_control_barrier(crate::Barrier::WORK_GROUP, level)?; + + let result_name = Baked(result).to_string(); + write!(self.out, "{level}")?; + // Expressions cannot have side effects, so just writing the expression here is fine. + self.write_named_expr(pointer, result_name, result, ctx)?; + + self.write_control_barrier(crate::Barrier::WORK_GROUP, level)?; + } + // Stores a value into an image. + Statement::ImageStore { + image, + coordinate, + array_index, + value, + } => { + write!(self.out, "{level}")?; + self.write_image_store(ctx, image, coordinate, array_index, value)? + } + // A `Call` is written `name(arguments)` where `arguments` is a comma separated expressions list + Statement::Call { + function, + ref arguments, + result, + } => { + write!(self.out, "{level}")?; + if let Some(expr) = result { + let name = Baked(expr).to_string(); + let result = self.module.functions[function].result.as_ref().unwrap(); + self.write_type(result.ty)?; + write!(self.out, " {name}")?; + if let TypeInner::Array { base, size, .. } = self.module.types[result.ty].inner + { + self.write_array_size(base, size)? + } + write!(self.out, " = ")?; + self.named_expressions.insert(expr, name); + } + write!(self.out, "{}(", &self.names[&NameKey::Function(function)])?; + let arguments: Vec<_> = arguments + .iter() + .enumerate() + .filter_map(|(i, arg)| { + let arg_ty = self.module.functions[function].arguments[i].ty; + match self.module.types[arg_ty].inner { + TypeInner::Sampler { .. } => None, + _ => Some(*arg), + } + }) + .collect(); + self.write_slice(&arguments, |this, _, arg| this.write_expr(*arg, ctx))?; + writeln!(self.out, ");")? + } + Statement::Atomic { + pointer, + ref fun, + value, + result, + } => { + write!(self.out, "{level}")?; + + match *fun { + crate::AtomicFunction::Exchange { + compare: Some(compare_expr), + } => { + let result_handle = result.expect("CompareExchange must have a result"); + let res_name = Baked(result_handle).to_string(); + self.write_type(ctx.info[result_handle].ty.handle().unwrap())?; + write!(self.out, " {res_name};")?; + write!(self.out, " {res_name}.old_value = atomicCompSwap(")?; + self.write_expr(pointer, ctx)?; + write!(self.out, ", ")?; + self.write_expr(compare_expr, ctx)?; + write!(self.out, ", ")?; + self.write_expr(value, ctx)?; + writeln!(self.out, ");")?; + + write!( + self.out, + "{level}{res_name}.exchanged = ({res_name}.old_value == " + )?; + self.write_expr(compare_expr, ctx)?; + writeln!(self.out, ");")?; + self.named_expressions.insert(result_handle, res_name); + } + _ => { + if let Some(result) = result { + let res_name = Baked(result).to_string(); + self.write_type(ctx.info[result].ty.handle().unwrap())?; + write!(self.out, " {res_name} = ")?; + self.named_expressions.insert(result, res_name); + } + let fun_str = fun.to_glsl(); + write!(self.out, "atomic{fun_str}(")?; + self.write_expr(pointer, ctx)?; + write!(self.out, ", ")?; + if let crate::AtomicFunction::Subtract = *fun { + // Emulate `atomicSub` with `atomicAdd` by negating the value. + write!(self.out, "-")?; + } + self.write_expr(value, ctx)?; + writeln!(self.out, ");")?; + } + } + } + // Stores a value into an image. + Statement::ImageAtomic { + image, + coordinate, + array_index, + fun, + value, + } => { + write!(self.out, "{level}")?; + self.write_image_atomic(ctx, image, coordinate, array_index, fun, value)? + } + Statement::RayQuery { .. } => unreachable!(), + Statement::SubgroupBallot { result, predicate } => { + write!(self.out, "{level}")?; + let res_name = Baked(result).to_string(); + let res_ty = ctx.info[result].ty.inner_with(&self.module.types); + self.write_value_type(res_ty)?; + write!(self.out, " {res_name} = ")?; + self.named_expressions.insert(result, res_name); + + write!(self.out, "subgroupBallot(")?; + match predicate { + Some(predicate) => self.write_expr(predicate, ctx)?, + None => write!(self.out, "true")?, + } + writeln!(self.out, ");")?; + } + Statement::SubgroupCollectiveOperation { + op, + collective_op, + argument, + result, + } => { + write!(self.out, "{level}")?; + let res_name = Baked(result).to_string(); + let res_ty = ctx.info[result].ty.inner_with(&self.module.types); + self.write_value_type(res_ty)?; + write!(self.out, " {res_name} = ")?; + self.named_expressions.insert(result, res_name); + + match (collective_op, op) { + (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::All) => { + write!(self.out, "subgroupAll(")? + } + (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Any) => { + write!(self.out, "subgroupAny(")? + } + (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Add) => { + write!(self.out, "subgroupAdd(")? + } + (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Mul) => { + write!(self.out, "subgroupMul(")? + } + (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Max) => { + write!(self.out, "subgroupMax(")? + } + (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Min) => { + write!(self.out, "subgroupMin(")? + } + (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::And) => { + write!(self.out, "subgroupAnd(")? + } + (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Or) => { + write!(self.out, "subgroupOr(")? + } + (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Xor) => { + write!(self.out, "subgroupXor(")? + } + (crate::CollectiveOperation::ExclusiveScan, crate::SubgroupOperation::Add) => { + write!(self.out, "subgroupExclusiveAdd(")? + } + (crate::CollectiveOperation::ExclusiveScan, crate::SubgroupOperation::Mul) => { + write!(self.out, "subgroupExclusiveMul(")? + } + (crate::CollectiveOperation::InclusiveScan, crate::SubgroupOperation::Add) => { + write!(self.out, "subgroupInclusiveAdd(")? + } + (crate::CollectiveOperation::InclusiveScan, crate::SubgroupOperation::Mul) => { + write!(self.out, "subgroupInclusiveMul(")? + } + _ => unimplemented!(), + } + self.write_expr(argument, ctx)?; + writeln!(self.out, ");")?; + } + Statement::SubgroupGather { + mode, + argument, + result, + } => { + write!(self.out, "{level}")?; + let res_name = Baked(result).to_string(); + let res_ty = ctx.info[result].ty.inner_with(&self.module.types); + self.write_value_type(res_ty)?; + write!(self.out, " {res_name} = ")?; + self.named_expressions.insert(result, res_name); + + match mode { + crate::GatherMode::BroadcastFirst => { + write!(self.out, "subgroupBroadcastFirst(")?; + } + crate::GatherMode::Broadcast(_) => { + write!(self.out, "subgroupBroadcast(")?; + } + crate::GatherMode::Shuffle(_) => { + write!(self.out, "subgroupShuffle(")?; + } + crate::GatherMode::ShuffleDown(_) => { + write!(self.out, "subgroupShuffleDown(")?; + } + crate::GatherMode::ShuffleUp(_) => { + write!(self.out, "subgroupShuffleUp(")?; + } + crate::GatherMode::ShuffleXor(_) => { + write!(self.out, "subgroupShuffleXor(")?; + } + crate::GatherMode::QuadBroadcast(_) => { + write!(self.out, "subgroupQuadBroadcast(")?; + } + crate::GatherMode::QuadSwap(direction) => match direction { + crate::Direction::X => { + write!(self.out, "subgroupQuadSwapHorizontal(")?; + } + crate::Direction::Y => { + write!(self.out, "subgroupQuadSwapVertical(")?; + } + crate::Direction::Diagonal => { + write!(self.out, "subgroupQuadSwapDiagonal(")?; + } + }, + } + self.write_expr(argument, ctx)?; + match mode { + crate::GatherMode::BroadcastFirst => {} + crate::GatherMode::Broadcast(index) + | crate::GatherMode::Shuffle(index) + | crate::GatherMode::ShuffleDown(index) + | crate::GatherMode::ShuffleUp(index) + | crate::GatherMode::ShuffleXor(index) + | crate::GatherMode::QuadBroadcast(index) => { + write!(self.out, ", ")?; + self.write_expr(index, ctx)?; + } + crate::GatherMode::QuadSwap(_) => {} + } + writeln!(self.out, ");")?; + } + } + + Ok(()) + } + + /// Write a const expression. + /// + /// Write `expr`, a handle to an [`Expression`] in the current [`Module`]'s + /// constant expression arena, as GLSL expression. + /// + /// # Notes + /// Adds no newlines or leading/trailing whitespace + /// + /// [`Expression`]: crate::Expression + /// [`Module`]: crate::Module + fn write_const_expr( + &mut self, + expr: Handle<crate::Expression>, + arena: &crate::Arena<crate::Expression>, + ) -> BackendResult { + self.write_possibly_const_expr( + expr, + arena, + |expr| &self.info[expr], + |writer, expr| writer.write_const_expr(expr, arena), + ) + } + + /// Write [`Expression`] variants that can occur in both runtime and const expressions. + /// + /// Write `expr`, a handle to an [`Expression`] in the arena `expressions`, + /// as as GLSL expression. This must be one of the [`Expression`] variants + /// that is allowed to occur in constant expressions. + /// + /// Use `write_expression` to write subexpressions. + /// + /// This is the common code for `write_expr`, which handles arbitrary + /// runtime expressions, and `write_const_expr`, which only handles + /// const-expressions. Each of those callers passes itself (essentially) as + /// the `write_expression` callback, so that subexpressions are restricted + /// to the appropriate variants. + /// + /// # Notes + /// Adds no newlines or leading/trailing whitespace + /// + /// [`Expression`]: crate::Expression + fn write_possibly_const_expr<'w, I, E>( + &'w mut self, + expr: Handle<crate::Expression>, + expressions: &crate::Arena<crate::Expression>, + info: I, + write_expression: E, + ) -> BackendResult + where + I: Fn(Handle<crate::Expression>) -> &'w proc::TypeResolution, + E: Fn(&mut Self, Handle<crate::Expression>) -> BackendResult, + { + use crate::Expression; + + match expressions[expr] { + Expression::Literal(literal) => { + match literal { + // Floats are written using `Debug` instead of `Display` because it always appends the + // decimal part even it's zero which is needed for a valid glsl float constant + crate::Literal::F64(value) => write!(self.out, "{value:?}LF")?, + crate::Literal::F32(value) => write!(self.out, "{value:?}")?, + crate::Literal::F16(_) => { + return Err(Error::Custom("GLSL has no 16-bit float type".into())); + } + // Unsigned integers need a `u` at the end + // + // While `core` doesn't necessarily need it, it's allowed and since `es` needs it we + // always write it as the extra branch wouldn't have any benefit in readability + crate::Literal::U32(value) => write!(self.out, "{value}u")?, + crate::Literal::I32(value) => write!(self.out, "{value}")?, + crate::Literal::Bool(value) => write!(self.out, "{value}")?, + crate::Literal::I64(_) => { + return Err(Error::Custom("GLSL has no 64-bit integer type".into())); + } + crate::Literal::U64(_) => { + return Err(Error::Custom("GLSL has no 64-bit integer type".into())); + } + crate::Literal::AbstractInt(_) | crate::Literal::AbstractFloat(_) => { + return Err(Error::Custom( + "Abstract types should not appear in IR presented to backends".into(), + )); + } + } + } + Expression::Constant(handle) => { + let constant = &self.module.constants[handle]; + if constant.name.is_some() { + write!(self.out, "{}", self.names[&NameKey::Constant(handle)])?; + } else { + self.write_const_expr(constant.init, &self.module.global_expressions)?; + } + } + Expression::ZeroValue(ty) => { + self.write_zero_init_value(ty)?; + } + Expression::Compose { ty, ref components } => { + self.write_type(ty)?; + + if let TypeInner::Array { base, size, .. } = self.module.types[ty].inner { + self.write_array_size(base, size)?; + } + + write!(self.out, "(")?; + for (index, component) in components.iter().enumerate() { + if index != 0 { + write!(self.out, ", ")?; + } + write_expression(self, *component)?; + } + write!(self.out, ")")? + } + // `Splat` needs to actually write down a vector, it's not always inferred in GLSL. + Expression::Splat { size: _, value } => { + let resolved = info(expr).inner_with(&self.module.types); + self.write_value_type(resolved)?; + write!(self.out, "(")?; + write_expression(self, value)?; + write!(self.out, ")")? + } + _ => { + return Err(Error::Override); + } + } + + Ok(()) + } + + /// Helper method to write expressions + /// + /// # Notes + /// Doesn't add any newlines or leading/trailing spaces + fn write_expr( + &mut self, + expr: Handle<crate::Expression>, + ctx: &back::FunctionCtx, + ) -> BackendResult { + use crate::Expression; + + if let Some(name) = self.named_expressions.get(&expr) { + write!(self.out, "{name}")?; + return Ok(()); + } + + match ctx.expressions[expr] { + Expression::Literal(_) + | Expression::Constant(_) + | Expression::ZeroValue(_) + | Expression::Compose { .. } + | Expression::Splat { .. } => { + self.write_possibly_const_expr( + expr, + ctx.expressions, + |expr| &ctx.info[expr].ty, + |writer, expr| writer.write_expr(expr, ctx), + )?; + } + Expression::Override(_) => return Err(Error::Override), + // `Access` is applied to arrays, vectors and matrices and is written as indexing + Expression::Access { base, index } => { + self.write_expr(base, ctx)?; + write!(self.out, "[")?; + self.write_expr(index, ctx)?; + write!(self.out, "]")? + } + // `AccessIndex` is the same as `Access` except that the index is a constant and it can + // be applied to structs, in this case we need to find the name of the field at that + // index and write `base.field_name` + Expression::AccessIndex { base, index } => { + self.write_expr(base, ctx)?; + + let base_ty_res = &ctx.info[base].ty; + let mut resolved = base_ty_res.inner_with(&self.module.types); + let base_ty_handle = match *resolved { + TypeInner::Pointer { base, space: _ } => { + resolved = &self.module.types[base].inner; + Some(base) + } + _ => base_ty_res.handle(), + }; + + match *resolved { + TypeInner::Vector { .. } => { + // Write vector access as a swizzle + write!(self.out, ".{}", back::COMPONENTS[index as usize])? + } + TypeInner::Matrix { .. } + | TypeInner::Array { .. } + | TypeInner::ValuePointer { .. } => write!(self.out, "[{index}]")?, + TypeInner::Struct { .. } => { + // This will never panic in case the type is a `Struct`, this is not true + // for other types so we can only check while inside this match arm + let ty = base_ty_handle.unwrap(); + + write!( + self.out, + ".{}", + &self.names[&NameKey::StructMember(ty, index)] + )? + } + ref other => return Err(Error::Custom(format!("Cannot index {other:?}"))), + } + } + // `Swizzle` adds a few letters behind the dot. + Expression::Swizzle { + size, + vector, + pattern, + } => { + self.write_expr(vector, ctx)?; + write!(self.out, ".")?; + for &sc in pattern[..size as usize].iter() { + self.out.write_char(back::COMPONENTS[sc as usize])?; + } + } + // Function arguments are written as the argument name + Expression::FunctionArgument(pos) => { + write!(self.out, "{}", &self.names[&ctx.argument_key(pos)])? + } + // Global variables need some special work for their name but + // `get_global_name` does the work for us + Expression::GlobalVariable(handle) => { + let global = &self.module.global_variables[handle]; + self.write_global_name(handle, global)? + } + // A local is written as it's name + Expression::LocalVariable(handle) => { + write!(self.out, "{}", self.names[&ctx.name_key(handle)])? + } + // glsl has no pointers so there's no load operation, just write the pointer expression + Expression::Load { pointer } => self.write_expr(pointer, ctx)?, + // `ImageSample` is a bit complicated compared to the rest of the IR. + // + // First there are three variations depending whether the sample level is explicitly set, + // if it's automatic or it it's bias: + // `texture(image, coordinate)` - Automatic sample level + // `texture(image, coordinate, bias)` - Bias sample level + // `textureLod(image, coordinate, level)` - Zero or Exact sample level + // + // Furthermore if `depth_ref` is some we need to append it to the coordinate vector + Expression::ImageSample { + image, + sampler: _, //TODO? + gather, + coordinate, + array_index, + offset, + level, + depth_ref, + clamp_to_edge: _, + } => { + let (dim, class, arrayed) = match *ctx.resolve_type(image, &self.module.types) { + TypeInner::Image { + dim, + class, + arrayed, + .. + } => (dim, class, arrayed), + _ => unreachable!(), + }; + let mut err = None; + if dim == crate::ImageDimension::Cube { + if offset.is_some() { + err = Some("gsamplerCube[Array][Shadow] doesn't support texture sampling with offsets"); + } + if arrayed + && matches!(class, crate::ImageClass::Depth { .. }) + && matches!(level, crate::SampleLevel::Gradient { .. }) + { + err = Some("samplerCubeArrayShadow don't support textureGrad"); + } + } + if gather.is_some() && level != crate::SampleLevel::Zero { + err = Some("textureGather doesn't support LOD parameters"); + } + if let Some(err) = err { + return Err(Error::Custom(String::from(err))); + } + + // `textureLod[Offset]` on `sampler2DArrayShadow` and `samplerCubeShadow` does not exist in GLSL, + // unless `GL_EXT_texture_shadow_lod` is present. + // But if the target LOD is zero, we can emulate that by using `textureGrad[Offset]` with a constant gradient of 0. + let workaround_lod_with_grad = ((dim == crate::ImageDimension::Cube && !arrayed) + || (dim == crate::ImageDimension::D2 && arrayed)) + && level == crate::SampleLevel::Zero + && matches!(class, crate::ImageClass::Depth { .. }) + && !self.features.contains(Features::TEXTURE_SHADOW_LOD); + + // Write the function to be used depending on the sample level + let fun_name = match level { + crate::SampleLevel::Zero if gather.is_some() => "textureGather", + crate::SampleLevel::Zero if workaround_lod_with_grad => "textureGrad", + crate::SampleLevel::Auto | crate::SampleLevel::Bias(_) => "texture", + crate::SampleLevel::Zero | crate::SampleLevel::Exact(_) => "textureLod", + crate::SampleLevel::Gradient { .. } => "textureGrad", + }; + let offset_name = match offset { + Some(_) => "Offset", + None => "", + }; + + write!(self.out, "{fun_name}{offset_name}(")?; + + // Write the image that will be used + self.write_expr(image, ctx)?; + // The space here isn't required but it helps with readability + write!(self.out, ", ")?; + + // TODO: handle clamp_to_edge + // https://github.com/gfx-rs/wgpu/issues/7791 + + // We need to get the coordinates vector size to later build a vector that's `size + 1` + // if `depth_ref` is some, if it isn't a vector we panic as that's not a valid expression + let mut coord_dim = match *ctx.resolve_type(coordinate, &self.module.types) { + TypeInner::Vector { size, .. } => size as u8, + TypeInner::Scalar { .. } => 1, + _ => unreachable!(), + }; + + if array_index.is_some() { + coord_dim += 1; + } + let merge_depth_ref = depth_ref.is_some() && gather.is_none() && coord_dim < 4; + if merge_depth_ref { + coord_dim += 1; + } + + let tex_1d_hack = dim == crate::ImageDimension::D1 && self.options.version.is_es(); + let is_vec = tex_1d_hack || coord_dim != 1; + // Compose a new texture coordinates vector + if is_vec { + write!(self.out, "vec{}(", coord_dim + tex_1d_hack as u8)?; + } + self.write_expr(coordinate, ctx)?; + if tex_1d_hack { + write!(self.out, ", 0.0")?; + } + if let Some(expr) = array_index { + write!(self.out, ", ")?; + self.write_expr(expr, ctx)?; + } + if merge_depth_ref { + write!(self.out, ", ")?; + self.write_expr(depth_ref.unwrap(), ctx)?; + } + if is_vec { + write!(self.out, ")")?; + } + + if let (Some(expr), false) = (depth_ref, merge_depth_ref) { + write!(self.out, ", ")?; + self.write_expr(expr, ctx)?; + } + + match level { + // Auto needs no more arguments + crate::SampleLevel::Auto => (), + // Zero needs level set to 0 + crate::SampleLevel::Zero => { + if workaround_lod_with_grad { + let vec_dim = match dim { + crate::ImageDimension::Cube => 3, + _ => 2, + }; + write!(self.out, ", vec{vec_dim}(0.0), vec{vec_dim}(0.0)")?; + } else if gather.is_none() { + write!(self.out, ", 0.0")?; + } + } + // Exact and bias require another argument + crate::SampleLevel::Exact(expr) => { + write!(self.out, ", ")?; + self.write_expr(expr, ctx)?; + } + crate::SampleLevel::Bias(_) => { + // This needs to be done after the offset writing + } + crate::SampleLevel::Gradient { x, y } => { + // If we are using sampler2D to replace sampler1D, we also + // need to make sure to use vec2 gradients + if tex_1d_hack { + write!(self.out, ", vec2(")?; + self.write_expr(x, ctx)?; + write!(self.out, ", 0.0)")?; + write!(self.out, ", vec2(")?; + self.write_expr(y, ctx)?; + write!(self.out, ", 0.0)")?; + } else { + write!(self.out, ", ")?; + self.write_expr(x, ctx)?; + write!(self.out, ", ")?; + self.write_expr(y, ctx)?; + } + } + } + + if let Some(constant) = offset { + write!(self.out, ", ")?; + if tex_1d_hack { + write!(self.out, "ivec2(")?; + } + self.write_const_expr(constant, ctx.expressions)?; + if tex_1d_hack { + write!(self.out, ", 0)")?; + } + } + + // Bias is always the last argument + if let crate::SampleLevel::Bias(expr) = level { + write!(self.out, ", ")?; + self.write_expr(expr, ctx)?; + } + + if let (Some(component), None) = (gather, depth_ref) { + write!(self.out, ", {}", component as usize)?; + } + + // End the function + write!(self.out, ")")? + } + Expression::ImageLoad { + image, + coordinate, + array_index, + sample, + level, + } => self.write_image_load(expr, ctx, image, coordinate, array_index, sample, level)?, + // Query translates into one of the: + // - textureSize/imageSize + // - textureQueryLevels + // - textureSamples/imageSamples + Expression::ImageQuery { image, query } => { + use crate::ImageClass; + + // This will only panic if the module is invalid + let (dim, class) = match *ctx.resolve_type(image, &self.module.types) { + TypeInner::Image { + dim, + arrayed: _, + class, + } => (dim, class), + _ => unreachable!(), + }; + let components = match dim { + crate::ImageDimension::D1 => 1, + crate::ImageDimension::D2 => 2, + crate::ImageDimension::D3 => 3, + crate::ImageDimension::Cube => 2, + }; + + if let crate::ImageQuery::Size { .. } = query { + match components { + 1 => write!(self.out, "uint(")?, + _ => write!(self.out, "uvec{components}(")?, + } + } else { + write!(self.out, "uint(")?; + } + + match query { + crate::ImageQuery::Size { level } => { + match class { + ImageClass::Sampled { multi, .. } | ImageClass::Depth { multi } => { + write!(self.out, "textureSize(")?; + self.write_expr(image, ctx)?; + if let Some(expr) = level { + let cast_to_int = matches!( + *ctx.resolve_type(expr, &self.module.types), + TypeInner::Scalar(crate::Scalar { + kind: crate::ScalarKind::Uint, + .. + }) + ); + + write!(self.out, ", ")?; + + if cast_to_int { + write!(self.out, "int(")?; + } + + self.write_expr(expr, ctx)?; + + if cast_to_int { + write!(self.out, ")")?; + } + } else if !multi { + // All textureSize calls requires an lod argument + // except for multisampled samplers + write!(self.out, ", 0")?; + } + } + ImageClass::Storage { .. } => { + write!(self.out, "imageSize(")?; + self.write_expr(image, ctx)?; + } + ImageClass::External => unimplemented!(), + } + write!(self.out, ")")?; + if components != 1 || self.options.version.is_es() { + write!(self.out, ".{}", &"xyz"[..components])?; + } + } + crate::ImageQuery::NumLevels => { + write!(self.out, "textureQueryLevels(",)?; + self.write_expr(image, ctx)?; + write!(self.out, ")",)?; + } + crate::ImageQuery::NumLayers => { + let fun_name = match class { + ImageClass::Sampled { .. } | ImageClass::Depth { .. } => "textureSize", + ImageClass::Storage { .. } => "imageSize", + ImageClass::External => unimplemented!(), + }; + write!(self.out, "{fun_name}(")?; + self.write_expr(image, ctx)?; + // All textureSize calls requires an lod argument + // except for multisampled samplers + if !class.is_multisampled() { + write!(self.out, ", 0")?; + } + write!(self.out, ")")?; + if components != 1 || self.options.version.is_es() { + write!(self.out, ".{}", back::COMPONENTS[components])?; + } + } + crate::ImageQuery::NumSamples => { + let fun_name = match class { + ImageClass::Sampled { .. } | ImageClass::Depth { .. } => { + "textureSamples" + } + ImageClass::Storage { .. } => "imageSamples", + ImageClass::External => unimplemented!(), + }; + write!(self.out, "{fun_name}(")?; + self.write_expr(image, ctx)?; + write!(self.out, ")",)?; + } + } + + write!(self.out, ")")?; + } + Expression::Unary { op, expr } => { + let operator_or_fn = match op { + crate::UnaryOperator::Negate => "-", + crate::UnaryOperator::LogicalNot => { + match *ctx.resolve_type(expr, &self.module.types) { + TypeInner::Vector { .. } => "not", + _ => "!", + } + } + crate::UnaryOperator::BitwiseNot => "~", + }; + write!(self.out, "{operator_or_fn}(")?; + + self.write_expr(expr, ctx)?; + + write!(self.out, ")")? + } + // `Binary` we just write `left op right`, except when dealing with + // comparison operations on vectors as they are implemented with + // builtin functions. + // Once again we wrap everything in parentheses to avoid precedence issues + Expression::Binary { + mut op, + left, + right, + } => { + // Holds `Some(function_name)` if the binary operation is + // implemented as a function call + use crate::{BinaryOperator as Bo, ScalarKind as Sk, TypeInner as Ti}; + + let left_inner = ctx.resolve_type(left, &self.module.types); + let right_inner = ctx.resolve_type(right, &self.module.types); + + let function = match (left_inner, right_inner) { + (&Ti::Vector { scalar, .. }, &Ti::Vector { .. }) => match op { + Bo::Less + | Bo::LessEqual + | Bo::Greater + | Bo::GreaterEqual + | Bo::Equal + | Bo::NotEqual => BinaryOperation::VectorCompare, + Bo::Modulo if scalar.kind == Sk::Float => BinaryOperation::Modulo, + Bo::And if scalar.kind == Sk::Bool => { + op = crate::BinaryOperator::LogicalAnd; + BinaryOperation::VectorComponentWise + } + Bo::InclusiveOr if scalar.kind == Sk::Bool => { + op = crate::BinaryOperator::LogicalOr; + BinaryOperation::VectorComponentWise + } + _ => BinaryOperation::Other, + }, + _ => match (left_inner.scalar_kind(), right_inner.scalar_kind()) { + (Some(Sk::Float), _) | (_, Some(Sk::Float)) => match op { + Bo::Modulo => BinaryOperation::Modulo, + _ => BinaryOperation::Other, + }, + (Some(Sk::Bool), Some(Sk::Bool)) => match op { + Bo::InclusiveOr => { + op = crate::BinaryOperator::LogicalOr; + BinaryOperation::Other + } + Bo::And => { + op = crate::BinaryOperator::LogicalAnd; + BinaryOperation::Other + } + _ => BinaryOperation::Other, + }, + _ => BinaryOperation::Other, + }, + }; + + match function { + BinaryOperation::VectorCompare => { + let op_str = match op { + Bo::Less => "lessThan(", + Bo::LessEqual => "lessThanEqual(", + Bo::Greater => "greaterThan(", + Bo::GreaterEqual => "greaterThanEqual(", + Bo::Equal => "equal(", + Bo::NotEqual => "notEqual(", + _ => unreachable!(), + }; + write!(self.out, "{op_str}")?; + self.write_expr(left, ctx)?; + write!(self.out, ", ")?; + self.write_expr(right, ctx)?; + write!(self.out, ")")?; + } + BinaryOperation::VectorComponentWise => { + self.write_value_type(left_inner)?; + write!(self.out, "(")?; + + let size = match *left_inner { + Ti::Vector { size, .. } => size, + _ => unreachable!(), + }; + + for i in 0..size as usize { + if i != 0 { + write!(self.out, ", ")?; + } + + self.write_expr(left, ctx)?; + write!(self.out, ".{}", back::COMPONENTS[i])?; + + write!(self.out, " {} ", back::binary_operation_str(op))?; + + self.write_expr(right, ctx)?; + write!(self.out, ".{}", back::COMPONENTS[i])?; + } + + write!(self.out, ")")?; + } + // TODO: handle undefined behavior of BinaryOperator::Modulo + // + // sint: + // if right == 0 return 0 + // if left == min(type_of(left)) && right == -1 return 0 + // if sign(left) == -1 || sign(right) == -1 return result as defined by WGSL + // + // uint: + // if right == 0 return 0 + // + // float: + // if right == 0 return ? see https://github.com/gpuweb/gpuweb/issues/2798 + BinaryOperation::Modulo => { + write!(self.out, "(")?; + + // write `e1 - e2 * trunc(e1 / e2)` + self.write_expr(left, ctx)?; + write!(self.out, " - ")?; + self.write_expr(right, ctx)?; + write!(self.out, " * ")?; + write!(self.out, "trunc(")?; + self.write_expr(left, ctx)?; + write!(self.out, " / ")?; + self.write_expr(right, ctx)?; + write!(self.out, ")")?; + + write!(self.out, ")")?; + } + BinaryOperation::Other => { + write!(self.out, "(")?; + + self.write_expr(left, ctx)?; + write!(self.out, " {} ", back::binary_operation_str(op))?; + self.write_expr(right, ctx)?; + + write!(self.out, ")")?; + } + } + } + // `Select` is written as `condition ? accept : reject` + // We wrap everything in parentheses to avoid precedence issues + Expression::Select { + condition, + accept, + reject, + } => { + let cond_ty = ctx.resolve_type(condition, &self.module.types); + let vec_select = if let TypeInner::Vector { .. } = *cond_ty { + true + } else { + false + }; + + // TODO: Boolean mix on desktop required GL_EXT_shader_integer_mix + if vec_select { + // Glsl defines that for mix when the condition is a boolean the first element + // is picked if condition is false and the second if condition is true + write!(self.out, "mix(")?; + self.write_expr(reject, ctx)?; + write!(self.out, ", ")?; + self.write_expr(accept, ctx)?; + write!(self.out, ", ")?; + self.write_expr(condition, ctx)?; + } else { + write!(self.out, "(")?; + self.write_expr(condition, ctx)?; + write!(self.out, " ? ")?; + self.write_expr(accept, ctx)?; + write!(self.out, " : ")?; + self.write_expr(reject, ctx)?; + } + + write!(self.out, ")")? + } + // `Derivative` is a function call to a glsl provided function + Expression::Derivative { axis, ctrl, expr } => { + use crate::{DerivativeAxis as Axis, DerivativeControl as Ctrl}; + let fun_name = if self.options.version.supports_derivative_control() { + match (axis, ctrl) { + (Axis::X, Ctrl::Coarse) => "dFdxCoarse", + (Axis::X, Ctrl::Fine) => "dFdxFine", + (Axis::X, Ctrl::None) => "dFdx", + (Axis::Y, Ctrl::Coarse) => "dFdyCoarse", + (Axis::Y, Ctrl::Fine) => "dFdyFine", + (Axis::Y, Ctrl::None) => "dFdy", + (Axis::Width, Ctrl::Coarse) => "fwidthCoarse", + (Axis::Width, Ctrl::Fine) => "fwidthFine", + (Axis::Width, Ctrl::None) => "fwidth", + } + } else { + match axis { + Axis::X => "dFdx", + Axis::Y => "dFdy", + Axis::Width => "fwidth", + } + }; + write!(self.out, "{fun_name}(")?; + self.write_expr(expr, ctx)?; + write!(self.out, ")")? + } + // `Relational` is a normal function call to some glsl provided functions + Expression::Relational { fun, argument } => { + use crate::RelationalFunction as Rf; + + let fun_name = match fun { + Rf::IsInf => "isinf", + Rf::IsNan => "isnan", + Rf::All => "all", + Rf::Any => "any", + }; + write!(self.out, "{fun_name}(")?; + + self.write_expr(argument, ctx)?; + + write!(self.out, ")")? + } + Expression::Math { + fun, + arg, + arg1, + arg2, + arg3, + } => { + use crate::MathFunction as Mf; + + let fun_name = match fun { + // comparison + Mf::Abs => "abs", + Mf::Min => "min", + Mf::Max => "max", + Mf::Clamp => { + let scalar_kind = ctx + .resolve_type(arg, &self.module.types) + .scalar_kind() + .unwrap(); + match scalar_kind { + crate::ScalarKind::Float => "clamp", + // Clamp is undefined if min > max. In practice this means it can use a median-of-three + // instruction to determine the value. This is fine according to the WGSL spec for float + // clamp, but integer clamp _must_ use min-max. As such we write out min/max. + _ => { + write!(self.out, "min(max(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ", ")?; + self.write_expr(arg1.unwrap(), ctx)?; + write!(self.out, "), ")?; + self.write_expr(arg2.unwrap(), ctx)?; + write!(self.out, ")")?; + + return Ok(()); + } + } + } + Mf::Saturate => { + write!(self.out, "clamp(")?; + + self.write_expr(arg, ctx)?; + + match *ctx.resolve_type(arg, &self.module.types) { + TypeInner::Vector { size, .. } => write!( + self.out, + ", vec{}(0.0), vec{0}(1.0)", + common::vector_size_str(size) + )?, + _ => write!(self.out, ", 0.0, 1.0")?, + } + + write!(self.out, ")")?; + + return Ok(()); + } + // trigonometry + Mf::Cos => "cos", + Mf::Cosh => "cosh", + Mf::Sin => "sin", + Mf::Sinh => "sinh", + Mf::Tan => "tan", + Mf::Tanh => "tanh", + Mf::Acos => "acos", + Mf::Asin => "asin", + Mf::Atan => "atan", + Mf::Asinh => "asinh", + Mf::Acosh => "acosh", + Mf::Atanh => "atanh", + Mf::Radians => "radians", + Mf::Degrees => "degrees", + // glsl doesn't have atan2 function + // use two-argument variation of the atan function + Mf::Atan2 => "atan", + // decomposition + Mf::Ceil => "ceil", + Mf::Floor => "floor", + Mf::Round => "roundEven", + Mf::Fract => "fract", + Mf::Trunc => "trunc", + Mf::Modf => MODF_FUNCTION, + Mf::Frexp => FREXP_FUNCTION, + Mf::Ldexp => "ldexp", + // exponent + Mf::Exp => "exp", + Mf::Exp2 => "exp2", + Mf::Log => "log", + Mf::Log2 => "log2", + Mf::Pow => "pow", + // geometry + Mf::Dot => match *ctx.resolve_type(arg, &self.module.types) { + TypeInner::Vector { + scalar: + crate::Scalar { + kind: crate::ScalarKind::Float, + .. + }, + .. + } => "dot", + TypeInner::Vector { size, .. } => { + return self.write_dot_product(arg, arg1.unwrap(), size as usize, ctx) + } + _ => unreachable!( + "Correct TypeInner for dot product should be already validated" + ), + }, + fun @ (Mf::Dot4I8Packed | Mf::Dot4U8Packed) => { + let conversion = match fun { + Mf::Dot4I8Packed => "int", + Mf::Dot4U8Packed => "", + _ => unreachable!(), + }; + + let arg1 = arg1.unwrap(); + + // Write parentheses around the dot product expression to prevent operators + // with different precedences from applying earlier. + write!(self.out, "(")?; + for i in 0..4 { + // Since `bitfieldExtract` only sign extends if the value is signed, we + // need to convert the inputs to `int` in case of `Dot4I8Packed`. For + // `Dot4U8Packed`, the code below only introduces parenthesis around + // each factor, which aren't strictly needed because both operands are + // baked, but which don't hurt either. + write!(self.out, "bitfieldExtract({conversion}(")?; + self.write_expr(arg, ctx)?; + write!(self.out, "), {}, 8)", i * 8)?; + + write!(self.out, " * bitfieldExtract({conversion}(")?; + self.write_expr(arg1, ctx)?; + write!(self.out, "), {}, 8)", i * 8)?; + + if i != 3 { + write!(self.out, " + ")?; + } + } + write!(self.out, ")")?; + + return Ok(()); + } + Mf::Outer => "outerProduct", + Mf::Cross => "cross", + Mf::Distance => "distance", + Mf::Length => "length", + Mf::Normalize => "normalize", + Mf::FaceForward => "faceforward", + Mf::Reflect => "reflect", + Mf::Refract => "refract", + // computational + Mf::Sign => "sign", + Mf::Fma => { + if self.options.version.supports_fma_function() { + // Use the fma function when available + "fma" + } else { + // No fma support. Transform the function call into an arithmetic expression + write!(self.out, "(")?; + + self.write_expr(arg, ctx)?; + write!(self.out, " * ")?; + + let arg1 = + arg1.ok_or_else(|| Error::Custom("Missing fma arg1".to_owned()))?; + self.write_expr(arg1, ctx)?; + write!(self.out, " + ")?; + + let arg2 = + arg2.ok_or_else(|| Error::Custom("Missing fma arg2".to_owned()))?; + self.write_expr(arg2, ctx)?; + write!(self.out, ")")?; + + return Ok(()); + } + } + Mf::Mix => "mix", + Mf::Step => "step", + Mf::SmoothStep => "smoothstep", + Mf::Sqrt => "sqrt", + Mf::InverseSqrt => "inversesqrt", + Mf::Inverse => "inverse", + Mf::Transpose => "transpose", + Mf::Determinant => "determinant", + Mf::QuantizeToF16 => match *ctx.resolve_type(arg, &self.module.types) { + TypeInner::Scalar { .. } => { + write!(self.out, "unpackHalf2x16(packHalf2x16(vec2(")?; + self.write_expr(arg, ctx)?; + write!(self.out, "))).x")?; + return Ok(()); + } + TypeInner::Vector { + size: crate::VectorSize::Bi, + .. + } => { + write!(self.out, "unpackHalf2x16(packHalf2x16(")?; + self.write_expr(arg, ctx)?; + write!(self.out, "))")?; + return Ok(()); + } + TypeInner::Vector { + size: crate::VectorSize::Tri, + .. + } => { + write!(self.out, "vec3(unpackHalf2x16(packHalf2x16(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ".xy)), unpackHalf2x16(packHalf2x16(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ".zz)).x)")?; + return Ok(()); + } + TypeInner::Vector { + size: crate::VectorSize::Quad, + .. + } => { + write!(self.out, "vec4(unpackHalf2x16(packHalf2x16(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ".xy)), unpackHalf2x16(packHalf2x16(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ".zw)))")?; + return Ok(()); + } + _ => unreachable!( + "Correct TypeInner for QuantizeToF16 should be already validated" + ), + }, + // bits + Mf::CountTrailingZeros => { + match *ctx.resolve_type(arg, &self.module.types) { + TypeInner::Vector { size, scalar, .. } => { + let s = common::vector_size_str(size); + if let crate::ScalarKind::Uint = scalar.kind { + write!(self.out, "min(uvec{s}(findLSB(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ")), uvec{s}(32u))")?; + } else { + write!(self.out, "ivec{s}(min(uvec{s}(findLSB(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ")), uvec{s}(32u)))")?; + } + } + TypeInner::Scalar(scalar) => { + if let crate::ScalarKind::Uint = scalar.kind { + write!(self.out, "min(uint(findLSB(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ")), 32u)")?; + } else { + write!(self.out, "int(min(uint(findLSB(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ")), 32u))")?; + } + } + _ => unreachable!(), + }; + return Ok(()); + } + Mf::CountLeadingZeros => { + if self.options.version.supports_integer_functions() { + match *ctx.resolve_type(arg, &self.module.types) { + TypeInner::Vector { size, scalar } => { + let s = common::vector_size_str(size); + + if let crate::ScalarKind::Uint = scalar.kind { + write!(self.out, "uvec{s}(ivec{s}(31) - findMSB(")?; + self.write_expr(arg, ctx)?; + write!(self.out, "))")?; + } else { + write!(self.out, "mix(ivec{s}(31) - findMSB(")?; + self.write_expr(arg, ctx)?; + write!(self.out, "), ivec{s}(0), lessThan(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ", ivec{s}(0)))")?; + } + } + TypeInner::Scalar(scalar) => { + if let crate::ScalarKind::Uint = scalar.kind { + write!(self.out, "uint(31 - findMSB(")?; + } else { + write!(self.out, "(")?; + self.write_expr(arg, ctx)?; + write!(self.out, " < 0 ? 0 : 31 - findMSB(")?; + } + + self.write_expr(arg, ctx)?; + write!(self.out, "))")?; + } + _ => unreachable!(), + }; + } else { + match *ctx.resolve_type(arg, &self.module.types) { + TypeInner::Vector { size, scalar } => { + let s = common::vector_size_str(size); + + if let crate::ScalarKind::Uint = scalar.kind { + write!(self.out, "uvec{s}(")?; + write!(self.out, "vec{s}(31.0) - floor(log2(vec{s}(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ") + 0.5)))")?; + } else { + write!(self.out, "ivec{s}(")?; + write!(self.out, "mix(vec{s}(31.0) - floor(log2(vec{s}(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ") + 0.5)), ")?; + write!(self.out, "vec{s}(0.0), lessThan(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ", ivec{s}(0u))))")?; + } + } + TypeInner::Scalar(scalar) => { + if let crate::ScalarKind::Uint = scalar.kind { + write!(self.out, "uint(31.0 - floor(log2(float(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ") + 0.5)))")?; + } else { + write!(self.out, "(")?; + self.write_expr(arg, ctx)?; + write!(self.out, " < 0 ? 0 : int(")?; + write!(self.out, "31.0 - floor(log2(float(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ") + 0.5))))")?; + } + } + _ => unreachable!(), + }; + } + + return Ok(()); + } + Mf::CountOneBits => "bitCount", + Mf::ReverseBits => "bitfieldReverse", + Mf::ExtractBits => { + // The behavior of ExtractBits is undefined when offset + count > bit_width. We need + // to first sanitize the offset and count first. If we don't do this, AMD and Intel chips + // will return out-of-spec values if the extracted range is not within the bit width. + // + // This encodes the exact formula specified by the wgsl spec, without temporary values: + // https://gpuweb.github.io/gpuweb/wgsl/#extractBits-unsigned-builtin + // + // w = sizeof(x) * 8 + // o = min(offset, w) + // c = min(count, w - o) + // + // bitfieldExtract(x, o, c) + // + // extract_bits(e, min(offset, w), min(count, w - min(offset, w)))) + let scalar_bits = ctx + .resolve_type(arg, &self.module.types) + .scalar_width() + .unwrap() + * 8; + + write!(self.out, "bitfieldExtract(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ", int(min(")?; + self.write_expr(arg1.unwrap(), ctx)?; + write!(self.out, ", {scalar_bits}u)), int(min(",)?; + self.write_expr(arg2.unwrap(), ctx)?; + write!(self.out, ", {scalar_bits}u - min(")?; + self.write_expr(arg1.unwrap(), ctx)?; + write!(self.out, ", {scalar_bits}u))))")?; + + return Ok(()); + } + Mf::InsertBits => { + // InsertBits has the same considerations as ExtractBits above + let scalar_bits = ctx + .resolve_type(arg, &self.module.types) + .scalar_width() + .unwrap() + * 8; + + write!(self.out, "bitfieldInsert(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ", ")?; + self.write_expr(arg1.unwrap(), ctx)?; + write!(self.out, ", int(min(")?; + self.write_expr(arg2.unwrap(), ctx)?; + write!(self.out, ", {scalar_bits}u)), int(min(",)?; + self.write_expr(arg3.unwrap(), ctx)?; + write!(self.out, ", {scalar_bits}u - min(")?; + self.write_expr(arg2.unwrap(), ctx)?; + write!(self.out, ", {scalar_bits}u))))")?; + + return Ok(()); + } + Mf::FirstTrailingBit => "findLSB", + Mf::FirstLeadingBit => "findMSB", + // data packing + Mf::Pack4x8snorm => { + if self.options.version.supports_pack_unpack_4x8() { + "packSnorm4x8" + } else { + // polyfill should go here. Needs a corresponding entry in `need_bake_expression` + return Err(Error::UnsupportedExternal("packSnorm4x8".into())); + } + } + Mf::Pack4x8unorm => { + if self.options.version.supports_pack_unpack_4x8() { + "packUnorm4x8" + } else { + return Err(Error::UnsupportedExternal("packUnorm4x8".to_owned())); + } + } + Mf::Pack2x16snorm => { + if self.options.version.supports_pack_unpack_snorm_2x16() { + "packSnorm2x16" + } else { + return Err(Error::UnsupportedExternal("packSnorm2x16".to_owned())); + } + } + Mf::Pack2x16unorm => { + if self.options.version.supports_pack_unpack_unorm_2x16() { + "packUnorm2x16" + } else { + return Err(Error::UnsupportedExternal("packUnorm2x16".to_owned())); + } + } + Mf::Pack2x16float => { + if self.options.version.supports_pack_unpack_half_2x16() { + "packHalf2x16" + } else { + return Err(Error::UnsupportedExternal("packHalf2x16".to_owned())); + } + } + + fun @ (Mf::Pack4xI8 | Mf::Pack4xU8 | Mf::Pack4xI8Clamp | Mf::Pack4xU8Clamp) => { + let was_signed = matches!(fun, Mf::Pack4xI8 | Mf::Pack4xI8Clamp); + let clamp_bounds = match fun { + Mf::Pack4xI8Clamp => Some(("-128", "127")), + Mf::Pack4xU8Clamp => Some(("0", "255")), + _ => None, + }; + let const_suffix = if was_signed { "" } else { "u" }; + if was_signed { + write!(self.out, "uint(")?; + } + let write_arg = |this: &mut Self| -> BackendResult { + if let Some((min, max)) = clamp_bounds { + write!(this.out, "clamp(")?; + this.write_expr(arg, ctx)?; + write!(this.out, ", {min}{const_suffix}, {max}{const_suffix})")?; + } else { + this.write_expr(arg, ctx)?; + } + Ok(()) + }; + write!(self.out, "(")?; + write_arg(self)?; + write!(self.out, "[0] & 0xFF{const_suffix}) | ((")?; + write_arg(self)?; + write!(self.out, "[1] & 0xFF{const_suffix}) << 8) | ((")?; + write_arg(self)?; + write!(self.out, "[2] & 0xFF{const_suffix}) << 16) | ((")?; + write_arg(self)?; + write!(self.out, "[3] & 0xFF{const_suffix}) << 24)")?; + if was_signed { + write!(self.out, ")")?; + } + + return Ok(()); + } + // data unpacking + Mf::Unpack2x16float => { + if self.options.version.supports_pack_unpack_half_2x16() { + "unpackHalf2x16" + } else { + return Err(Error::UnsupportedExternal("unpackHalf2x16".into())); + } + } + Mf::Unpack2x16snorm => { + if self.options.version.supports_pack_unpack_snorm_2x16() { + "unpackSnorm2x16" + } else { + let scale = 32767; + + write!(self.out, "(vec2(ivec2(")?; + self.write_expr(arg, ctx)?; + write!(self.out, " << 16, ")?; + self.write_expr(arg, ctx)?; + write!(self.out, ") >> 16) / {scale}.0)")?; + return Ok(()); + } + } + Mf::Unpack2x16unorm => { + if self.options.version.supports_pack_unpack_unorm_2x16() { + "unpackUnorm2x16" + } else { + let scale = 65535; + + write!(self.out, "(vec2(")?; + self.write_expr(arg, ctx)?; + write!(self.out, " & 0xFFFFu, ")?; + self.write_expr(arg, ctx)?; + write!(self.out, " >> 16) / {scale}.0)")?; + return Ok(()); + } + } + Mf::Unpack4x8snorm => { + if self.options.version.supports_pack_unpack_4x8() { + "unpackSnorm4x8" + } else { + let scale = 127; + + write!(self.out, "(vec4(ivec4(")?; + self.write_expr(arg, ctx)?; + write!(self.out, " << 24, ")?; + self.write_expr(arg, ctx)?; + write!(self.out, " << 16, ")?; + self.write_expr(arg, ctx)?; + write!(self.out, " << 8, ")?; + self.write_expr(arg, ctx)?; + write!(self.out, ") >> 24) / {scale}.0)")?; + return Ok(()); + } + } + Mf::Unpack4x8unorm => { + if self.options.version.supports_pack_unpack_4x8() { + "unpackUnorm4x8" + } else { + let scale = 255; + + write!(self.out, "(vec4(")?; + self.write_expr(arg, ctx)?; + write!(self.out, " & 0xFFu, ")?; + self.write_expr(arg, ctx)?; + write!(self.out, " >> 8 & 0xFFu, ")?; + self.write_expr(arg, ctx)?; + write!(self.out, " >> 16 & 0xFFu, ")?; + self.write_expr(arg, ctx)?; + write!(self.out, " >> 24) / {scale}.0)")?; + return Ok(()); + } + } + fun @ (Mf::Unpack4xI8 | Mf::Unpack4xU8) => { + let sign_prefix = match fun { + Mf::Unpack4xI8 => 'i', + Mf::Unpack4xU8 => 'u', + _ => unreachable!(), + }; + write!(self.out, "{sign_prefix}vec4(")?; + for i in 0..4 { + write!(self.out, "bitfieldExtract(")?; + // Since bitfieldExtract only sign extends if the value is signed, this + // cast is needed + match fun { + Mf::Unpack4xI8 => { + write!(self.out, "int(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ")")?; + } + Mf::Unpack4xU8 => self.write_expr(arg, ctx)?, + _ => unreachable!(), + }; + write!(self.out, ", {}, 8)", i * 8)?; + if i != 3 { + write!(self.out, ", ")?; + } + } + write!(self.out, ")")?; + + return Ok(()); + } + }; + + let extract_bits = fun == Mf::ExtractBits; + let insert_bits = fun == Mf::InsertBits; + + // Some GLSL functions always return signed integers (like findMSB), + // so they need to be cast to uint if the argument is also an uint. + let ret_might_need_int_to_uint = matches!( + fun, + Mf::FirstTrailingBit | Mf::FirstLeadingBit | Mf::CountOneBits | Mf::Abs + ); + + // Some GLSL functions only accept signed integers (like abs), + // so they need their argument cast from uint to int. + let arg_might_need_uint_to_int = matches!(fun, Mf::Abs); + + // Check if the argument is an unsigned integer and return the vector size + // in case it's a vector + let maybe_uint_size = match *ctx.resolve_type(arg, &self.module.types) { + TypeInner::Scalar(crate::Scalar { + kind: crate::ScalarKind::Uint, + .. + }) => Some(None), + TypeInner::Vector { + scalar: + crate::Scalar { + kind: crate::ScalarKind::Uint, + .. + }, + size, + } => Some(Some(size)), + _ => None, + }; + + // Cast to uint if the function needs it + if ret_might_need_int_to_uint { + if let Some(maybe_size) = maybe_uint_size { + match maybe_size { + Some(size) => write!(self.out, "uvec{}(", size as u8)?, + None => write!(self.out, "uint(")?, + } + } + } + + write!(self.out, "{fun_name}(")?; + + // Cast to int if the function needs it + if arg_might_need_uint_to_int { + if let Some(maybe_size) = maybe_uint_size { + match maybe_size { + Some(size) => write!(self.out, "ivec{}(", size as u8)?, + None => write!(self.out, "int(")?, + } + } + } + + self.write_expr(arg, ctx)?; + + // Close the cast from uint to int + if arg_might_need_uint_to_int && maybe_uint_size.is_some() { + write!(self.out, ")")? + } + + if let Some(arg) = arg1 { + write!(self.out, ", ")?; + if extract_bits { + write!(self.out, "int(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ")")?; + } else { + self.write_expr(arg, ctx)?; + } + } + if let Some(arg) = arg2 { + write!(self.out, ", ")?; + if extract_bits || insert_bits { + write!(self.out, "int(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ")")?; + } else { + self.write_expr(arg, ctx)?; + } + } + if let Some(arg) = arg3 { + write!(self.out, ", ")?; + if insert_bits { + write!(self.out, "int(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ")")?; + } else { + self.write_expr(arg, ctx)?; + } + } + write!(self.out, ")")?; + + // Close the cast from int to uint + if ret_might_need_int_to_uint && maybe_uint_size.is_some() { + write!(self.out, ")")? + } + } + // `As` is always a call. + // If `convert` is true the function name is the type + // Else the function name is one of the glsl provided bitcast functions + Expression::As { + expr, + kind: target_kind, + convert, + } => { + let inner = ctx.resolve_type(expr, &self.module.types); + match convert { + Some(width) => { + // this is similar to `write_type`, but with the target kind + let scalar = glsl_scalar(crate::Scalar { + kind: target_kind, + width, + })?; + match *inner { + TypeInner::Matrix { columns, rows, .. } => write!( + self.out, + "{}mat{}x{}", + scalar.prefix, columns as u8, rows as u8 + )?, + TypeInner::Vector { size, .. } => { + write!(self.out, "{}vec{}", scalar.prefix, size as u8)? + } + _ => write!(self.out, "{}", scalar.full)?, + } + + write!(self.out, "(")?; + self.write_expr(expr, ctx)?; + write!(self.out, ")")? + } + None => { + use crate::ScalarKind as Sk; + + let target_vector_type = match *inner { + TypeInner::Vector { size, scalar } => Some(TypeInner::Vector { + size, + scalar: crate::Scalar { + kind: target_kind, + width: scalar.width, + }, + }), + _ => None, + }; + + let source_kind = inner.scalar_kind().unwrap(); + + match (source_kind, target_kind, target_vector_type) { + // No conversion needed + (Sk::Sint, Sk::Sint, _) + | (Sk::Uint, Sk::Uint, _) + | (Sk::Float, Sk::Float, _) + | (Sk::Bool, Sk::Bool, _) => { + self.write_expr(expr, ctx)?; + return Ok(()); + } + + // Cast to/from floats + (Sk::Float, Sk::Sint, _) => write!(self.out, "floatBitsToInt")?, + (Sk::Float, Sk::Uint, _) => write!(self.out, "floatBitsToUint")?, + (Sk::Sint, Sk::Float, _) => write!(self.out, "intBitsToFloat")?, + (Sk::Uint, Sk::Float, _) => write!(self.out, "uintBitsToFloat")?, + + // Cast between vector types + (_, _, Some(vector)) => { + self.write_value_type(&vector)?; + } + + // There is no way to bitcast between Uint/Sint in glsl. Use constructor conversion + (Sk::Uint | Sk::Bool, Sk::Sint, None) => write!(self.out, "int")?, + (Sk::Sint | Sk::Bool, Sk::Uint, None) => write!(self.out, "uint")?, + (Sk::Bool, Sk::Float, None) => write!(self.out, "float")?, + (Sk::Sint | Sk::Uint | Sk::Float, Sk::Bool, None) => { + write!(self.out, "bool")? + } + + (Sk::AbstractInt | Sk::AbstractFloat, _, _) + | (_, Sk::AbstractInt | Sk::AbstractFloat, _) => unreachable!(), + }; + + write!(self.out, "(")?; + self.write_expr(expr, ctx)?; + write!(self.out, ")")?; + } + } + } + // These expressions never show up in `Emit`. + Expression::CallResult(_) + | Expression::AtomicResult { .. } + | Expression::RayQueryProceedResult + | Expression::WorkGroupUniformLoadResult { .. } + | Expression::SubgroupOperationResult { .. } + | Expression::SubgroupBallotResult => unreachable!(), + // `ArrayLength` is written as `expr.length()` and we convert it to a uint + Expression::ArrayLength(expr) => { + write!(self.out, "uint(")?; + self.write_expr(expr, ctx)?; + write!(self.out, ".length())")? + } + // not supported yet + Expression::RayQueryGetIntersection { .. } + | Expression::RayQueryVertexPositions { .. } => unreachable!(), + } + + Ok(()) + } + + /// Helper function to write the local holding the clamped lod + fn write_clamped_lod( + &mut self, + ctx: &back::FunctionCtx, + expr: Handle<crate::Expression>, + image: Handle<crate::Expression>, + level_expr: Handle<crate::Expression>, + ) -> Result<(), Error> { + // Define our local and start a call to `clamp` + write!( + self.out, + "int {}{} = clamp(", + Baked(expr), + CLAMPED_LOD_SUFFIX + )?; + // Write the lod that will be clamped + self.write_expr(level_expr, ctx)?; + // Set the min value to 0 and start a call to `textureQueryLevels` to get + // the maximum value + write!(self.out, ", 0, textureQueryLevels(")?; + // Write the target image as an argument to `textureQueryLevels` + self.write_expr(image, ctx)?; + // Close the call to `textureQueryLevels` subtract 1 from it since + // the lod argument is 0 based, close the `clamp` call and end the + // local declaration statement. + writeln!(self.out, ") - 1);")?; + + Ok(()) + } + + // Helper method used to retrieve how many elements a coordinate vector + // for the images operations need. + fn get_coordinate_vector_size(&self, dim: crate::ImageDimension, arrayed: bool) -> u8 { + // openGL es doesn't have 1D images so we need workaround it + let tex_1d_hack = dim == crate::ImageDimension::D1 && self.options.version.is_es(); + // Get how many components the coordinate vector needs for the dimensions only + let tex_coord_size = match dim { + crate::ImageDimension::D1 => 1, + crate::ImageDimension::D2 => 2, + crate::ImageDimension::D3 => 3, + crate::ImageDimension::Cube => 2, + }; + // Calculate the true size of the coordinate vector by adding 1 for arrayed images + // and another 1 if we need to workaround 1D images by making them 2D + tex_coord_size + tex_1d_hack as u8 + arrayed as u8 + } + + /// Helper method to write the coordinate vector for image operations + fn write_texture_coord( + &mut self, + ctx: &back::FunctionCtx, + vector_size: u8, + coordinate: Handle<crate::Expression>, + array_index: Option<Handle<crate::Expression>>, + // Emulate 1D images as 2D for profiles that don't support it (glsl es) + tex_1d_hack: bool, + ) -> Result<(), Error> { + match array_index { + // If the image needs an array indice we need to add it to the end of our + // coordinate vector, to do so we will use the `ivec(ivec, scalar)` + // constructor notation (NOTE: the inner `ivec` can also be a scalar, this + // is important for 1D arrayed images). + Some(layer_expr) => { + write!(self.out, "ivec{vector_size}(")?; + self.write_expr(coordinate, ctx)?; + write!(self.out, ", ")?; + // If we are replacing sampler1D with sampler2D we also need + // to add another zero to the coordinates vector for the y component + if tex_1d_hack { + write!(self.out, "0, ")?; + } + self.write_expr(layer_expr, ctx)?; + write!(self.out, ")")?; + } + // Otherwise write just the expression (and the 1D hack if needed) + None => { + let uvec_size = match *ctx.resolve_type(coordinate, &self.module.types) { + TypeInner::Scalar(crate::Scalar { + kind: crate::ScalarKind::Uint, + .. + }) => Some(None), + TypeInner::Vector { + size, + scalar: + crate::Scalar { + kind: crate::ScalarKind::Uint, + .. + }, + } => Some(Some(size as u32)), + _ => None, + }; + if tex_1d_hack { + write!(self.out, "ivec2(")?; + } else if uvec_size.is_some() { + match uvec_size { + Some(None) => write!(self.out, "int(")?, + Some(Some(size)) => write!(self.out, "ivec{size}(")?, + _ => {} + } + } + self.write_expr(coordinate, ctx)?; + if tex_1d_hack { + write!(self.out, ", 0)")?; + } else if uvec_size.is_some() { + write!(self.out, ")")?; + } + } + } + + Ok(()) + } + + /// Helper method to write the `ImageStore` statement + fn write_image_store( + &mut self, + ctx: &back::FunctionCtx, + image: Handle<crate::Expression>, + coordinate: Handle<crate::Expression>, + array_index: Option<Handle<crate::Expression>>, + value: Handle<crate::Expression>, + ) -> Result<(), Error> { + use crate::ImageDimension as IDim; + + // NOTE: openGL requires that `imageStore`s have no effects when the texel is invalid + // so we don't need to generate bounds checks (OpenGL 4.2 Core §3.9.20) + + // This will only panic if the module is invalid + let dim = match *ctx.resolve_type(image, &self.module.types) { + TypeInner::Image { dim, .. } => dim, + _ => unreachable!(), + }; + + // Begin our call to `imageStore` + write!(self.out, "imageStore(")?; + self.write_expr(image, ctx)?; + // Separate the image argument from the coordinates + write!(self.out, ", ")?; + + // openGL es doesn't have 1D images so we need workaround it + let tex_1d_hack = dim == IDim::D1 && self.options.version.is_es(); + // Write the coordinate vector + self.write_texture_coord( + ctx, + // Get the size of the coordinate vector + self.get_coordinate_vector_size(dim, array_index.is_some()), + coordinate, + array_index, + tex_1d_hack, + )?; + + // Separate the coordinate from the value to write and write the expression + // of the value to write. + write!(self.out, ", ")?; + self.write_expr(value, ctx)?; + // End the call to `imageStore` and the statement. + writeln!(self.out, ");")?; + + Ok(()) + } + + /// Helper method to write the `ImageAtomic` statement + fn write_image_atomic( + &mut self, + ctx: &back::FunctionCtx, + image: Handle<crate::Expression>, + coordinate: Handle<crate::Expression>, + array_index: Option<Handle<crate::Expression>>, + fun: crate::AtomicFunction, + value: Handle<crate::Expression>, + ) -> Result<(), Error> { + use crate::ImageDimension as IDim; + + // NOTE: openGL requires that `imageAtomic`s have no effects when the texel is invalid + // so we don't need to generate bounds checks (OpenGL 4.2 Core §3.9.20) + + // This will only panic if the module is invalid + let dim = match *ctx.resolve_type(image, &self.module.types) { + TypeInner::Image { dim, .. } => dim, + _ => unreachable!(), + }; + + // Begin our call to `imageAtomic` + let fun_str = fun.to_glsl(); + write!(self.out, "imageAtomic{fun_str}(")?; + self.write_expr(image, ctx)?; + // Separate the image argument from the coordinates + write!(self.out, ", ")?; + + // openGL es doesn't have 1D images so we need workaround it + let tex_1d_hack = dim == IDim::D1 && self.options.version.is_es(); + // Write the coordinate vector + self.write_texture_coord( + ctx, + // Get the size of the coordinate vector + self.get_coordinate_vector_size(dim, false), + coordinate, + array_index, + tex_1d_hack, + )?; + + // Separate the coordinate from the value to write and write the expression + // of the value to write. + write!(self.out, ", ")?; + self.write_expr(value, ctx)?; + // End the call to `imageAtomic` and the statement. + writeln!(self.out, ");")?; + + Ok(()) + } + + /// Helper method for writing an `ImageLoad` expression. + #[allow(clippy::too_many_arguments)] + fn write_image_load( + &mut self, + handle: Handle<crate::Expression>, + ctx: &back::FunctionCtx, + image: Handle<crate::Expression>, + coordinate: Handle<crate::Expression>, + array_index: Option<Handle<crate::Expression>>, + sample: Option<Handle<crate::Expression>>, + level: Option<Handle<crate::Expression>>, + ) -> Result<(), Error> { + use crate::ImageDimension as IDim; + + // `ImageLoad` is a bit complicated. + // There are two functions one for sampled + // images another for storage images, the former uses `texelFetch` and the + // latter uses `imageLoad`. + // + // Furthermore we have `level` which is always `Some` for sampled images + // and `None` for storage images, so we end up with two functions: + // - `texelFetch(image, coordinate, level)` for sampled images + // - `imageLoad(image, coordinate)` for storage images + // + // Finally we also have to consider bounds checking, for storage images + // this is easy since openGL requires that invalid texels always return + // 0, for sampled images we need to either verify that all arguments are + // in bounds (`ReadZeroSkipWrite`) or make them a valid texel (`Restrict`). + + // This will only panic if the module is invalid + let (dim, class) = match *ctx.resolve_type(image, &self.module.types) { + TypeInner::Image { + dim, + arrayed: _, + class, + } => (dim, class), + _ => unreachable!(), + }; + + // Get the name of the function to be used for the load operation + // and the policy to be used with it. + let (fun_name, policy) = match class { + // Sampled images inherit the policy from the user passed policies + crate::ImageClass::Sampled { .. } => ("texelFetch", self.policies.image_load), + crate::ImageClass::Storage { .. } => { + // OpenGL ES 3.1 mentions in Chapter "8.22 Texture Image Loads and Stores" that: + // "Invalid image loads will return a vector where the value of R, G, and B components + // is 0 and the value of the A component is undefined." + // + // OpenGL 4.2 Core mentions in Chapter "3.9.20 Texture Image Loads and Stores" that: + // "Invalid image loads will return zero." + // + // So, we only inject bounds checks for ES + let policy = if self.options.version.is_es() { + self.policies.image_load + } else { + proc::BoundsCheckPolicy::Unchecked + }; + ("imageLoad", policy) + } + // TODO: Is there even a function for this? + crate::ImageClass::Depth { multi: _ } => { + return Err(Error::Custom( + "WGSL `textureLoad` from depth textures is not supported in GLSL".to_string(), + )) + } + crate::ImageClass::External => unimplemented!(), + }; + + // openGL es doesn't have 1D images so we need workaround it + let tex_1d_hack = dim == IDim::D1 && self.options.version.is_es(); + // Get the size of the coordinate vector + let vector_size = self.get_coordinate_vector_size(dim, array_index.is_some()); + + if let proc::BoundsCheckPolicy::ReadZeroSkipWrite = policy { + // To write the bounds checks for `ReadZeroSkipWrite` we will use a + // ternary operator since we are in the middle of an expression and + // need to return a value. + // + // NOTE: glsl does short circuit when evaluating logical + // expressions so we can be sure that after we test a + // condition it will be true for the next ones + + // Write parentheses around the ternary operator to prevent problems with + // expressions emitted before or after it having more precedence + write!(self.out, "(",)?; + + // The lod check needs to precede the size check since we need + // to use the lod to get the size of the image at that level. + if let Some(level_expr) = level { + self.write_expr(level_expr, ctx)?; + write!(self.out, " < textureQueryLevels(",)?; + self.write_expr(image, ctx)?; + // Chain the next check + write!(self.out, ") && ")?; + } + + // Check that the sample arguments doesn't exceed the number of samples + if let Some(sample_expr) = sample { + self.write_expr(sample_expr, ctx)?; + write!(self.out, " < textureSamples(",)?; + self.write_expr(image, ctx)?; + // Chain the next check + write!(self.out, ") && ")?; + } + + // We now need to write the size checks for the coordinates and array index + // first we write the comparison function in case the image is 1D non arrayed + // (and no 1D to 2D hack was needed) we are comparing scalars so the less than + // operator will suffice, but otherwise we'll be comparing two vectors so we'll + // need to use the `lessThan` function but it returns a vector of booleans (one + // for each comparison) so we need to fold it all in one scalar boolean, since + // we want all comparisons to pass we use the `all` function which will only + // return `true` if all the elements of the boolean vector are also `true`. + // + // So we'll end with one of the following forms + // - `coord < textureSize(image, lod)` for 1D images + // - `all(lessThan(coord, textureSize(image, lod)))` for normal images + // - `all(lessThan(ivec(coord, array_index), textureSize(image, lod)))` + // for arrayed images + // - `all(lessThan(coord, textureSize(image)))` for multi sampled images + + if vector_size != 1 { + write!(self.out, "all(lessThan(")?; + } + + // Write the coordinate vector + self.write_texture_coord(ctx, vector_size, coordinate, array_index, tex_1d_hack)?; + + if vector_size != 1 { + // If we used the `lessThan` function we need to separate the + // coordinates from the image size. + write!(self.out, ", ")?; + } else { + // If we didn't use it (ie. 1D images) we perform the comparison + // using the less than operator. + write!(self.out, " < ")?; + } + + // Call `textureSize` to get our image size + write!(self.out, "textureSize(")?; + self.write_expr(image, ctx)?; + // `textureSize` uses the lod as a second argument for mipmapped images + if let Some(level_expr) = level { + // Separate the image from the lod + write!(self.out, ", ")?; + self.write_expr(level_expr, ctx)?; + } + // Close the `textureSize` call + write!(self.out, ")")?; + + if vector_size != 1 { + // Close the `all` and `lessThan` calls + write!(self.out, "))")?; + } + + // Finally end the condition part of the ternary operator + write!(self.out, " ? ")?; + } + + // Begin the call to the function used to load the texel + write!(self.out, "{fun_name}(")?; + self.write_expr(image, ctx)?; + write!(self.out, ", ")?; + + // If we are using `Restrict` bounds checking we need to pass valid texel + // coordinates, to do so we use the `clamp` function to get a value between + // 0 and the image size - 1 (indexing begins at 0) + if let proc::BoundsCheckPolicy::Restrict = policy { + write!(self.out, "clamp(")?; + } + + // Write the coordinate vector + self.write_texture_coord(ctx, vector_size, coordinate, array_index, tex_1d_hack)?; + + // If we are using `Restrict` bounds checking we need to write the rest of the + // clamp we initiated before writing the coordinates. + if let proc::BoundsCheckPolicy::Restrict = policy { + // Write the min value 0 + if vector_size == 1 { + write!(self.out, ", 0")?; + } else { + write!(self.out, ", ivec{vector_size}(0)")?; + } + // Start the `textureSize` call to use as the max value. + write!(self.out, ", textureSize(")?; + self.write_expr(image, ctx)?; + // If the image is mipmapped we need to add the lod argument to the + // `textureSize` call, but this needs to be the clamped lod, this should + // have been generated earlier and put in a local. + if class.is_mipmapped() { + write!(self.out, ", {}{}", Baked(handle), CLAMPED_LOD_SUFFIX)?; + } + // Close the `textureSize` call + write!(self.out, ")")?; + + // Subtract 1 from the `textureSize` call since the coordinates are zero based. + if vector_size == 1 { + write!(self.out, " - 1")?; + } else { + write!(self.out, " - ivec{vector_size}(1)")?; + } + + // Close the `clamp` call + write!(self.out, ")")?; + + // Add the clamped lod (if present) as the second argument to the + // image load function. + if level.is_some() { + write!(self.out, ", {}{}", Baked(handle), CLAMPED_LOD_SUFFIX)?; + } + + // If a sample argument is needed we need to clamp it between 0 and + // the number of samples the image has. + if let Some(sample_expr) = sample { + write!(self.out, ", clamp(")?; + self.write_expr(sample_expr, ctx)?; + // Set the min value to 0 and start the call to `textureSamples` + write!(self.out, ", 0, textureSamples(")?; + self.write_expr(image, ctx)?; + // Close the `textureSamples` call, subtract 1 from it since the sample + // argument is zero based, and close the `clamp` call + writeln!(self.out, ") - 1)")?; + } + } else if let Some(sample_or_level) = sample.or(level) { + // GLSL only support SInt on this field while WGSL support also UInt + let cast_to_int = matches!( + *ctx.resolve_type(sample_or_level, &self.module.types), + TypeInner::Scalar(crate::Scalar { + kind: crate::ScalarKind::Uint, + .. + }) + ); + + // If no bounds checking is need just add the sample or level argument + // after the coordinates + write!(self.out, ", ")?; + + if cast_to_int { + write!(self.out, "int(")?; + } + + self.write_expr(sample_or_level, ctx)?; + + if cast_to_int { + write!(self.out, ")")?; + } + } + + // Close the image load function. + write!(self.out, ")")?; + + // If we were using the `ReadZeroSkipWrite` policy we need to end the first branch + // (which is taken if the condition is `true`) with a colon (`:`) and write the + // second branch which is just a 0 value. + if let proc::BoundsCheckPolicy::ReadZeroSkipWrite = policy { + // Get the kind of the output value. + let kind = match class { + // Only sampled images can reach here since storage images + // don't need bounds checks and depth images aren't implemented + crate::ImageClass::Sampled { kind, .. } => kind, + _ => unreachable!(), + }; + + // End the first branch + write!(self.out, " : ")?; + // Write the 0 value + write!( + self.out, + "{}vec4(", + glsl_scalar(crate::Scalar { kind, width: 4 })?.prefix, + )?; + self.write_zero_init_scalar(kind)?; + // Close the zero value constructor + write!(self.out, ")")?; + // Close the parentheses surrounding our ternary + write!(self.out, ")")?; + } + + Ok(()) + } + + fn write_named_expr( + &mut self, + handle: Handle<crate::Expression>, + name: String, + // The expression which is being named. + // Generally, this is the same as handle, except in WorkGroupUniformLoad + named: Handle<crate::Expression>, + ctx: &back::FunctionCtx, + ) -> BackendResult { + match ctx.info[named].ty { + proc::TypeResolution::Handle(ty_handle) => match self.module.types[ty_handle].inner { + TypeInner::Struct { .. } => { + let ty_name = &self.names[&NameKey::Type(ty_handle)]; + write!(self.out, "{ty_name}")?; + } + _ => { + self.write_type(ty_handle)?; + } + }, + proc::TypeResolution::Value(ref inner) => { + self.write_value_type(inner)?; + } + } + + let resolved = ctx.resolve_type(named, &self.module.types); + + write!(self.out, " {name}")?; + if let TypeInner::Array { base, size, .. } = *resolved { + self.write_array_size(base, size)?; + } + write!(self.out, " = ")?; + self.write_expr(handle, ctx)?; + writeln!(self.out, ";")?; + self.named_expressions.insert(named, name); + + Ok(()) + } + + /// Helper function that write string with default zero initialization for supported types + fn write_zero_init_value(&mut self, ty: Handle<crate::Type>) -> BackendResult { + let inner = &self.module.types[ty].inner; + match *inner { + TypeInner::Scalar(scalar) | TypeInner::Atomic(scalar) => { + self.write_zero_init_scalar(scalar.kind)?; + } + TypeInner::Vector { scalar, .. } => { + self.write_value_type(inner)?; + write!(self.out, "(")?; + self.write_zero_init_scalar(scalar.kind)?; + write!(self.out, ")")?; + } + TypeInner::Matrix { .. } => { + self.write_value_type(inner)?; + write!(self.out, "(")?; + self.write_zero_init_scalar(crate::ScalarKind::Float)?; + write!(self.out, ")")?; + } + TypeInner::Array { base, size, .. } => { + let count = match size.resolve(self.module.to_ctx())? { + proc::IndexableLength::Known(count) => count, + proc::IndexableLength::Dynamic => return Ok(()), + }; + self.write_type(base)?; + self.write_array_size(base, size)?; + write!(self.out, "(")?; + for _ in 1..count { + self.write_zero_init_value(base)?; + write!(self.out, ", ")?; + } + // write last parameter without comma and space + self.write_zero_init_value(base)?; + write!(self.out, ")")?; + } + TypeInner::Struct { ref members, .. } => { + let name = &self.names[&NameKey::Type(ty)]; + write!(self.out, "{name}(")?; + for (index, member) in members.iter().enumerate() { + if index != 0 { + write!(self.out, ", ")?; + } + self.write_zero_init_value(member.ty)?; + } + write!(self.out, ")")?; + } + _ => unreachable!(), + } + + Ok(()) + } + + /// Helper function that write string with zero initialization for scalar + fn write_zero_init_scalar(&mut self, kind: crate::ScalarKind) -> BackendResult { + match kind { + crate::ScalarKind::Bool => write!(self.out, "false")?, + crate::ScalarKind::Uint => write!(self.out, "0u")?, + crate::ScalarKind::Float => write!(self.out, "0.0")?, + crate::ScalarKind::Sint => write!(self.out, "0")?, + crate::ScalarKind::AbstractInt | crate::ScalarKind::AbstractFloat => { + return Err(Error::Custom( + "Abstract types should not appear in IR presented to backends".to_string(), + )) + } + } + + Ok(()) + } + + /// Issue a control barrier. + fn write_control_barrier( + &mut self, + flags: crate::Barrier, + level: back::Level, + ) -> BackendResult { + self.write_memory_barrier(flags, level)?; + writeln!(self.out, "{level}barrier();")?; + Ok(()) + } + + /// Issue a memory barrier. + fn write_memory_barrier(&mut self, flags: crate::Barrier, level: back::Level) -> BackendResult { + if flags.contains(crate::Barrier::STORAGE) { + writeln!(self.out, "{level}memoryBarrierBuffer();")?; + } + if flags.contains(crate::Barrier::WORK_GROUP) { + writeln!(self.out, "{level}memoryBarrierShared();")?; + } + if flags.contains(crate::Barrier::SUB_GROUP) { + writeln!(self.out, "{level}subgroupMemoryBarrier();")?; + } + if flags.contains(crate::Barrier::TEXTURE) { + writeln!(self.out, "{level}memoryBarrierImage();")?; + } + Ok(()) + } + + /// Helper function that return the glsl storage access string of [`StorageAccess`](crate::StorageAccess) + /// + /// glsl allows adding both `readonly` and `writeonly` but this means that + /// they can only be used to query information about the resource which isn't what + /// we want here so when storage access is both `LOAD` and `STORE` add no modifiers + fn write_storage_access(&mut self, storage_access: crate::StorageAccess) -> BackendResult { + if storage_access.contains(crate::StorageAccess::ATOMIC) { + return Ok(()); + } + if !storage_access.contains(crate::StorageAccess::STORE) { + write!(self.out, "readonly ")?; + } + if !storage_access.contains(crate::StorageAccess::LOAD) { + write!(self.out, "writeonly ")?; + } + Ok(()) + } + + /// Helper method used to produce the reflection info that's returned to the user + fn collect_reflection_info(&mut self) -> Result<ReflectionInfo, Error> { + let info = self.info.get_entry_point(self.entry_point_idx as usize); + let mut texture_mapping = crate::FastHashMap::default(); + let mut uniforms = crate::FastHashMap::default(); + + for sampling in info.sampling_set.iter() { + let tex_name = self.reflection_names_globals[&sampling.image].clone(); + + match texture_mapping.entry(tex_name) { + hash_map::Entry::Vacant(v) => { + v.insert(TextureMapping { + texture: sampling.image, + sampler: Some(sampling.sampler), + }); + } + hash_map::Entry::Occupied(e) => { + if e.get().sampler != Some(sampling.sampler) { + log::error!("Conflicting samplers for {}", e.key()); + return Err(Error::ImageMultipleSamplers); + } + } + } + } + + let mut immediates_info = None; + for (handle, var) in self.module.global_variables.iter() { + if info[handle].is_empty() { + continue; + } + match self.module.types[var.ty].inner { + TypeInner::Image { .. } => { + let tex_name = self.reflection_names_globals[&handle].clone(); + match texture_mapping.entry(tex_name) { + hash_map::Entry::Vacant(v) => { + v.insert(TextureMapping { + texture: handle, + sampler: None, + }); + } + hash_map::Entry::Occupied(_) => { + // already used with a sampler, do nothing + } + } + } + _ => match var.space { + crate::AddressSpace::Uniform | crate::AddressSpace::Storage { .. } => { + let name = self.reflection_names_globals[&handle].clone(); + uniforms.insert(handle, name); + } + crate::AddressSpace::Immediate => { + let name = self.reflection_names_globals[&handle].clone(); + immediates_info = Some((name, var.ty)); + } + _ => (), + }, + } + } + + let mut immediates_segments = Vec::new(); + let mut immediates_items = vec![]; + + if let Some((name, ty)) = immediates_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 = proc::Layouter::default(); + layouter.update(self.module.to_ctx()).unwrap(); + + // We start with the name of the binding itself. + immediates_segments.push(name); + + // We then recursively collect all the uniform fields of the immediate data. + self.collect_immediates_items( + ty, + &mut immediates_segments, + &layouter, + &mut 0, + &mut immediates_items, + ); + } + + Ok(ReflectionInfo { + texture_mapping, + uniforms, + varying: mem::take(&mut self.varying), + immediates_items, + clip_distance_count: self.clip_distance_count, + }) + } + + fn collect_immediates_items( + &mut self, + ty: Handle<crate::Type>, + segments: &mut Vec<String>, + layouter: &proc::Layouter, + offset: &mut u32, + items: &mut Vec<ImmediateItem>, + ) { + // 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(ImmediateItem { + 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 immediates"); + }; + + for i in 0..count.get() { + // Add the array accessor and recurse. + segments.push(format!("[{i}]")); + self.collect_immediates_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_immediates_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!(), + } + } +} diff --git a/third_party/rust/naga/src/back/hlsl/mod.rs b/third_party/rust/naga/src/back/hlsl/mod.rs @@ -177,15 +177,15 @@ use crate::{back, ir, proc}; /// /// The [`back::hlsl::Options`] structure provides `BindTarget`s for various /// situations in which Naga may need to generate an HLSL global variable, like -/// [`binding_map`] for Naga global variables, or [`push_constants_target`] for -/// a module's sole [`PushConstant`] variable. See those fields' documentation +/// [`binding_map`] for Naga global variables, or [`immediates_target`] for +/// a module's sole [`Immediate`] variable. See those fields' documentation /// for details. /// /// [`Storage`]: crate::ir::AddressSpace::Storage /// [`back::hlsl::Options`]: Options /// [`binding_map`]: Options::binding_map -/// [`push_constants_target`]: Options::push_constants_target -/// [`PushConstant`]: crate::ir::AddressSpace::PushConstant +/// [`immediates_target`]: Options::immediates_target +/// [`Immediate`]: crate::ir::AddressSpace::Immediate #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] @@ -498,16 +498,16 @@ pub struct Options { /// to make them work like in Vulkan/Metal, with help of the host. pub special_constants_binding: Option<BindTarget>, - /// HLSL binding information for the [`PushConstant`] global, if present. + /// HLSL binding information for the [`Immediate`] global, if present. /// - /// If a module contains a global in the [`PushConstant`] address space, the + /// If a module contains a global in the [`Immediate`] address space, the /// `dx12` backend stores its value directly in the root signature as a /// series of [`D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS`], whose binding /// information is given here. /// - /// [`PushConstant`]: crate::ir::AddressSpace::PushConstant + /// [`Immediate`]: crate::ir::AddressSpace::Immediate /// [`D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS`]: https://learn.microsoft.com/en-us/windows/win32/api/d3d12/ne-d3d12-d3d12_root_parameter_type - pub push_constants_target: Option<BindTarget>, + pub immediates_target: Option<BindTarget>, /// HLSL binding information for the sampler heap and comparison sampler heap. pub sampler_heap_target: SamplerHeapBindTargets, @@ -554,7 +554,7 @@ impl Default for Options { special_constants_binding: None, sampler_heap_target: SamplerHeapBindTargets::default(), sampler_buffer_binding_map: alloc::collections::BTreeMap::default(), - push_constants_target: None, + immediates_target: None, dynamic_storage_buffer_offsets_targets: alloc::collections::BTreeMap::new(), external_texture_binding_map: ExternalTextureBindingMap::default(), zero_initialize_workgroup_memory: true, diff --git a/third_party/rust/naga/src/back/hlsl/writer.rs b/third_party/rust/naga/src/back/hlsl/writer.rs @@ -441,7 +441,7 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { _ => false, }) { - log::info!( + log::debug!( "Skipping function {:?} (name {:?}) because global {:?} is inaccessible", handle, function.name, @@ -945,7 +945,7 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { if let Some(ref binding) = global.binding { if let Err(err) = self.options.resolve_resource_binding(binding) { - log::info!( + log::debug!( "Skipping global {:?} (name {:?}) for being inaccessible: {}", handle, global.name, @@ -1003,16 +1003,16 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { self.write_type(module, global.ty)?; register } - crate::AddressSpace::PushConstant => { - // The type of the push constants will be wrapped in `ConstantBuffer` + crate::AddressSpace::Immediate => { + // The type of the immediates will be wrapped in `ConstantBuffer` write!(self.out, "ConstantBuffer<")?; "b" } }; - // If the global is a push constant write the type now because it will be a + // If the global is a immediate data write the type now because it will be a // generic argument to `ConstantBuffer` - if global.space == crate::AddressSpace::PushConstant { + if global.space == crate::AddressSpace::Immediate { self.write_global_type(module, global.ty)?; // need to write the array size if the type was emitted with `write_type` @@ -1027,9 +1027,9 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { let name = &self.names[&NameKey::GlobalVariable(handle)]; write!(self.out, " {name}")?; - // Push constants need to be assigned a binding explicitly by the consumer + // Immediates need to be assigned a binding explicitly by the consumer // since naga has no way to know the binding from the shader alone - if global.space == crate::AddressSpace::PushConstant { + if global.space == crate::AddressSpace::Immediate { match module.types[global.ty].inner { TypeInner::Struct { .. } => {} _ => { @@ -1041,9 +1041,9 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { let target = self .options - .push_constants_target + .immediates_target .as_ref() - .expect("No bind target was defined for the push constants block"); + .expect("No bind target was defined for the immediates block"); write!(self.out, ": register(b{}", target.register)?; if target.space != 0 { write!(self.out, ", space{}", target.space)?; @@ -1187,7 +1187,7 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { { Ok(bindings) => bindings, Err(err) => { - log::info!( + log::debug!( "Skipping global {:?} (name {:?}) for being inaccessible: {}", handle, global.name, @@ -3085,7 +3085,7 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { crate::AddressSpace::Function | crate::AddressSpace::Private | crate::AddressSpace::WorkGroup - | crate::AddressSpace::PushConstant + | crate::AddressSpace::Immediate | crate::AddressSpace::TaskPayload, ) | None => true, diff --git a/third_party/rust/naga/src/back/mod.rs b/third_party/rust/naga/src/back/mod.rs @@ -215,6 +215,10 @@ impl FunctionCtx<'_> { external_texture_key, ) } + // This is a const function, which _sometimes_ gets called, + // so this lint is _sometimes_ triggered, depending on feature set. + #[expect(clippy::allow_attributes)] + #[allow(clippy::panic)] FunctionType::EntryPoint(_) => { panic!("External textures cannot be used as arguments to entry points") } diff --git a/third_party/rust/naga/src/back/msl/keywords.rs b/third_party/rust/naga/src/back/msl/keywords.rs @@ -1,12 +1,13 @@ -use crate::proc::KeywordSet; +use crate::proc::{concrete_int_scalars, vector_size_str, vector_sizes, KeywordSet}; use crate::racy_lock::RacyLock; +use alloc::{format, string::String, vec::Vec}; // MSLS - Metal Shading Language Specification: // https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf // // C++ - Standard for Programming Language C++ (N4431) // https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4431.pdf -pub const RESERVED: &[&str] = &[ +const RESERVED: &[&str] = &[ // Undocumented "assert", // found in https://github.com/gfx-rs/wgpu/issues/5347 // Standard for Programming Language C++ (N4431): 2.5 Alternative tokens @@ -346,6 +347,7 @@ pub const RESERVED: &[&str] = &[ super::writer::MODF_FUNCTION, super::writer::ABS_FUNCTION, super::writer::DIV_FUNCTION, + // DOT_FUNCTION_PREFIX variants are added dynamically below super::writer::MOD_FUNCTION, super::writer::NEG_FUNCTION, super::writer::F2I32_FUNCTION, @@ -359,8 +361,31 @@ pub const RESERVED: &[&str] = &[ super::writer::EXTERNAL_TEXTURE_WRAPPER_STRUCT, ]; +// The set of concrete integer dot product function variants. +// This must match the set of names that could be produced by +// `Writer::get_dot_wrapper_function_helper_name`. +static DOT_FUNCTION_NAMES: RacyLock<Vec<String>> = RacyLock::new(|| { + let mut names = Vec::new(); + for scalar in concrete_int_scalars().map(crate::Scalar::to_msl_name) { + for size_suffix in vector_sizes().map(vector_size_str) { + let fun_name = format!( + "{}_{}{}", + super::writer::DOT_FUNCTION_PREFIX, + scalar, + size_suffix + ); + names.push(fun_name); + } + } + names +}); + /// The above set of reserved keywords, turned into a cached HashSet. This saves /// significant time during [`Namer::reset`](crate::proc::Namer::reset). /// /// See <https://github.com/gfx-rs/wgpu/pull/7338> for benchmarks. -pub static RESERVED_SET: RacyLock<KeywordSet> = RacyLock::new(|| KeywordSet::from_iter(RESERVED)); +pub static RESERVED_SET: RacyLock<KeywordSet> = RacyLock::new(|| { + let mut set = KeywordSet::from_iter(RESERVED); + set.extend(DOT_FUNCTION_NAMES.iter().map(String::as_str)); + set +}); diff --git a/third_party/rust/naga/src/back/msl/mod.rs b/third_party/rust/naga/src/back/msl/mod.rs @@ -155,7 +155,7 @@ pub struct EntryPointResources { )] pub resources: BindingMap, - pub push_constant_buffer: Option<Slot>, + pub immediates_buffer: Option<Slot>, /// The slot of a buffer that contains an array of `u32`, /// one for the size of each bound buffer that contains a runtime array, @@ -247,8 +247,8 @@ pub enum EntryPointError { MissingBinding(String), #[error("mapping of {0:?} is missing")] MissingBindTarget(crate::ResourceBinding), - #[error("mapping for push constants is missing")] - MissingPushConstants, + #[error("mapping for immediates is missing")] + MissingImmediateData, #[error("mapping for sizes buffer is missing")] MissingSizesBuffer, } @@ -618,13 +618,13 @@ impl Options { } } - fn resolve_push_constants( + fn resolve_immediates( &self, ep: &crate::EntryPoint, ) -> Result<ResolvedBinding, EntryPointError> { let slot = self .get_entry_point_resources(ep) - .and_then(|res| res.push_constant_buffer); + .and_then(|res| res.immediates_buffer); match slot { Some(slot) => Ok(ResolvedBinding::Resource(BindTarget { buffer: Some(slot), @@ -635,7 +635,7 @@ impl Options { index: 0, interpolation: None, }), - None => Err(EntryPointError::MissingPushConstants), + None => Err(EntryPointError::MissingImmediateData), } } diff --git a/third_party/rust/naga/src/back/msl/sampler.rs b/third_party/rust/naga/src/back/msl/sampler.rs @@ -5,20 +5,15 @@ use serde::Deserialize; #[cfg(feature = "serialize")] use serde::Serialize; -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] pub enum Coord { + #[default] Normalized, Pixel, } -impl Default for Coord { - fn default() -> Self { - Self::Normalized - } -} - impl Coord { pub const fn as_str(&self) -> &'static str { match *self { @@ -28,23 +23,18 @@ impl Coord { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] pub enum Address { Repeat, MirroredRepeat, + #[default] ClampToEdge, ClampToZero, ClampToBorder, } -impl Default for Address { - fn default() -> Self { - Self::ClampToEdge - } -} - impl Address { pub const fn as_str(&self) -> &'static str { match *self { @@ -57,21 +47,16 @@ impl Address { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] pub enum BorderColor { + #[default] TransparentBlack, OpaqueBlack, OpaqueWhite, } -impl Default for BorderColor { - fn default() -> Self { - Self::TransparentBlack - } -} - impl BorderColor { pub const fn as_str(&self) -> &'static str { match *self { @@ -82,10 +67,11 @@ impl BorderColor { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] pub enum Filter { + #[default] Nearest, Linear, } @@ -99,16 +85,11 @@ impl Filter { } } -impl Default for Filter { - fn default() -> Self { - Self::Nearest - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] pub enum CompareFunc { + #[default] Never, Less, LessEqual, @@ -119,12 +100,6 @@ pub enum CompareFunc { Always, } -impl Default for CompareFunc { - fn default() -> Self { - Self::Never - } -} - impl CompareFunc { pub const fn as_str(&self) -> &'static str { match *self { diff --git a/third_party/rust/naga/src/back/msl/writer.rs b/third_party/rust/naga/src/back/msl/writer.rs @@ -19,7 +19,7 @@ use crate::{ back::{self, get_entry_points, Baked}, common, proc::{ - self, + self, concrete_int_scalars, index::{self, BoundsCheck}, ExternalTextureNameKey, NameKey, TypeResolution, }, @@ -55,6 +55,7 @@ pub(crate) const MODF_FUNCTION: &str = "naga_modf"; pub(crate) const FREXP_FUNCTION: &str = "naga_frexp"; pub(crate) const ABS_FUNCTION: &str = "naga_abs"; pub(crate) const DIV_FUNCTION: &str = "naga_div"; +pub(crate) const DOT_FUNCTION_PREFIX: &str = "naga_dot"; pub(crate) const MOD_FUNCTION: &str = "naga_mod"; pub(crate) const NEG_FUNCTION: &str = "naga_neg"; pub(crate) const F2I32_FUNCTION: &str = "naga_f2i32"; @@ -488,7 +489,7 @@ pub struct Writer<W> { } impl crate::Scalar { - fn to_msl_name(self) -> &'static str { + pub(super) fn to_msl_name(self) -> &'static str { use crate::ScalarKind as Sk; match self { Self { @@ -593,7 +594,7 @@ impl crate::AddressSpace { | Self::Storage { .. } | Self::Private | Self::WorkGroup - | Self::PushConstant + | Self::Immediate | Self::Handle | Self::TaskPayload => true, Self::Function => false, @@ -612,7 +613,7 @@ impl crate::AddressSpace { // These should always be read-write. Self::Private | Self::WorkGroup => false, // These translate to `constant` address space, no need for qualifiers. - Self::Uniform | Self::PushConstant => false, + Self::Uniform | Self::Immediate => false, // Not applicable. Self::Handle | Self::Function => false, } @@ -621,7 +622,7 @@ impl crate::AddressSpace { const fn to_msl_name(self) -> Option<&'static str> { match self { Self::Handle => None, - Self::Uniform | Self::PushConstant => Some("constant"), + Self::Uniform | Self::Immediate => Some("constant"), Self::Storage { .. } => Some("device"), Self::Private | Self::Function => Some("thread"), Self::WorkGroup => Some("threadgroup"), @@ -2334,26 +2335,28 @@ impl<W: Write> Writer<W> { crate::TypeInner::Vector { scalar: crate::Scalar { + // Resolve float values to MSL's builtin dot function. kind: crate::ScalarKind::Float, .. }, .. } => "dot", - crate::TypeInner::Vector { size, .. } => { - return self.put_dot_product( - arg, - arg1.unwrap(), - size as usize, - |writer, arg, index| { - // Write the vector expression; this expression is marked to be - // cached so unless it can't be cached (for example, it's a Constant) - // it shouldn't produce large expressions. - writer.put_expression(arg, context, true)?; - // Access the current component on the vector. - write!(writer.out, ".{}", back::COMPONENTS[index])?; - Ok(()) + crate::TypeInner::Vector { + size, + scalar: + scalar @ crate::Scalar { + kind: crate::ScalarKind::Sint | crate::ScalarKind::Uint, + .. }, - ); + } => { + // Integer vector dot: call our mangled helper `dot_{type}{N}(a, b)`. + let fun_name = self.get_dot_wrapper_function_helper_name(scalar, size); + write!(self.out, "{fun_name}(")?; + self.put_expression(arg, context, true)?; + write!(self.out, ", ")?; + self.put_expression(arg1.unwrap(), context, true)?; + write!(self.out, ")")?; + return Ok(()); } _ => unreachable!( "Correct TypeInner for dot product should be already validated" @@ -3370,26 +3373,15 @@ impl<W: Write> Writer<W> { } = *expr { match fun { - crate::MathFunction::Dot => { - // WGSL's `dot` function works on any `vecN` type, but Metal's only - // works on floating-point vectors, so we emit inline code for - // integer vector `dot` calls. But that code uses each argument `N` - // times, once for each component (see `put_dot_product`), so to - // avoid duplicated evaluation, we must bake integer operands. - - // check what kind of product this is depending - // on the resolve type of the Dot function itself - let inner = context.resolve_type(expr_handle); - if let crate::TypeInner::Scalar(scalar) = *inner { - match scalar.kind { - crate::ScalarKind::Sint | crate::ScalarKind::Uint => { - self.need_bake_expressions.insert(arg); - self.need_bake_expressions.insert(arg1.unwrap()); - } - _ => {} - } - } - } + // WGSL's `dot` function works on any `vecN` type, but Metal's only + // works on floating-point vectors, so we emit inline code for + // integer vector `dot` calls. But that code uses each argument `N` + // times, once for each component (see `put_dot_product`), so to + // avoid duplicated evaluation, we must bake integer operands. + // This applies both when using the polyfill (because of the duplicate + // evaluation issue) and when we don't use the polyfill (because we + // need them to be emitted before casting to packed chars -- see the + // comment at the call to `put_casting_to_packed_chars`). crate::MathFunction::Dot4U8Packed | crate::MathFunction::Dot4I8Packed => { self.need_bake_expressions.insert(arg); self.need_bake_expressions.insert(arg1.unwrap()); @@ -5806,6 +5798,24 @@ template <typename A> Ok(()) } + /// Build the mangled helper name for integer vector dot products. + /// + /// `scalar` must be a concrete integer scalar type. + /// + /// Result format: `{DOT_FUNCTION_PREFIX}_{type}{N}` (e.g., `naga_dot_int3`). + fn get_dot_wrapper_function_helper_name( + &self, + scalar: crate::Scalar, + size: crate::VectorSize, + ) -> String { + // Check for consistency with [`super::keywords::RESERVED_SET`] + debug_assert!(concrete_int_scalars().any(|s| s == scalar)); + + let type_name = scalar.to_msl_name(); + let size_suffix = common::vector_size_str(size); + format!("{DOT_FUNCTION_PREFIX}_{type_name}{size_suffix}") + } + #[allow(clippy::too_many_arguments)] fn write_wrapped_math_function( &mut self, @@ -5861,6 +5871,45 @@ template <typename A> writeln!(self.out, "}}")?; writeln!(self.out)?; } + + crate::MathFunction::Dot => match *arg_ty { + crate::TypeInner::Vector { size, scalar } + if matches!( + scalar.kind, + crate::ScalarKind::Sint | crate::ScalarKind::Uint + ) => + { + // De-duplicate per (fun, arg type) like other wrapped math functions + let wrapped = WrappedFunction::Math { + fun, + arg_ty: (Some(size), scalar), + }; + if !self.wrapped_functions.insert(wrapped) { + return Ok(()); + } + + let mut vec_ty = String::new(); + put_numeric_type(&mut vec_ty, scalar, &[size])?; + let mut ret_ty = String::new(); + put_numeric_type(&mut ret_ty, scalar, &[])?; + + let fun_name = self.get_dot_wrapper_function_helper_name(scalar, size); + + // Emit function signature and body using put_dot_product for the expression + writeln!(self.out, "{ret_ty} {fun_name}({vec_ty} a, {vec_ty} b) {{")?; + let level = back::Level(1); + write!(self.out, "{level}return ")?; + self.put_dot_product("a", "b", size as usize, |writer, name, index| { + write!(writer.out, "{name}.{}", back::COMPONENTS[index])?; + Ok(()) + })?; + writeln!(self.out, ";")?; + writeln!(self.out, "}}")?; + writeln!(self.out)?; + } + _ => {} + }, + _ => {} } Ok(()) @@ -6683,8 +6732,8 @@ template <typename A> break; } } - crate::AddressSpace::PushConstant => { - if let Err(e) = options.resolve_push_constants(ep) { + crate::AddressSpace::Immediate => { + if let Err(e) = options.resolve_immediates(ep) { ep_error = Some(e); break; } @@ -7091,9 +7140,11 @@ template <typename A> } } crate::ImageClass::Storage { .. } => { - return Err(Error::UnsupportedArrayOf( - "read-write textures".to_string(), - )); + if options.lang_version < (3, 0) { + return Err(Error::UnsupportedArrayOf( + "read-write textures".to_string(), + )); + } } crate::ImageClass::External => { return Err(Error::UnsupportedArrayOf( @@ -7113,7 +7164,7 @@ template <typename A> // the resolves have already been checked for `!fake_missing_bindings` case let resolved = match var.space { - crate::AddressSpace::PushConstant => options.resolve_push_constants(ep).ok(), + crate::AddressSpace::Immediate => options.resolve_immediates(ep).ok(), crate::AddressSpace::WorkGroup => None, _ => options .resolve_resource_binding(ep, var.binding.as_ref().unwrap()) diff --git a/third_party/rust/naga/src/back/spv/block.rs b/third_party/rust/naga/src/back/spv/block.rs @@ -221,8 +221,13 @@ impl Writer { ir_result: &crate::FunctionResult, result_members: &[ResultMember], body: &mut Vec<Instruction>, - ) -> Result<(), Error> { + task_payload: Option<Word>, + ) -> Result<Instruction, Error> { for (index, res_member) in result_members.iter().enumerate() { + // This isn't a real builtin, and is handled elsewhere + if res_member.built_in == Some(crate::BuiltIn::MeshTaskSize) { + continue; + } let member_value_id = match ir_result.binding { Some(_) => value_id, None => { @@ -253,7 +258,13 @@ impl Writer { _ => {} } } - Ok(()) + self.try_write_entry_point_task_return( + value_id, + ir_result, + result_members, + body, + task_payload, + ) } } @@ -3251,22 +3262,31 @@ impl BlockContext<'_> { let instruction = match self.function.entry_point_context { // If this is an entry point, and we need to return anything, // let's instead store the output variables and return `void`. - Some(ref context) => { - self.writer.write_entry_point_return( - value_id, - self.ir_function.result.as_ref().unwrap(), - &context.results, - &mut block.body, - )?; - Instruction::return_void() - } + Some(ref context) => self.writer.write_entry_point_return( + value_id, + self.ir_function.result.as_ref().unwrap(), + &context.results, + &mut block.body, + context.task_payload_variable_id, + )?, None => Instruction::return_value(value_id), }; self.function.consume(block, instruction); return Ok(BlockExitDisposition::Discarded); } Statement::Return { value: None } => { - self.function.consume(block, Instruction::return_void()); + if let Some(super::EntryPointContext { + mesh_state: Some(ref mesh_state), + .. + }) = self.function.entry_point_context + { + self.function.consume( + block, + Instruction::branch(mesh_state.entry_point_epilogue_id), + ); + } else { + self.function.consume(block, Instruction::return_void()); + } return Ok(BlockExitDisposition::Discarded); } Statement::Kill => { @@ -3274,7 +3294,7 @@ impl BlockContext<'_> { return Ok(BlockExitDisposition::Discarded); } Statement::ControlBarrier(flags) => { - self.writer.write_control_barrier(flags, &mut block); + self.writer.write_control_barrier(flags, &mut block.body); } Statement::MemoryBarrier(flags) => { self.writer.write_memory_barrier(flags, &mut block); @@ -3613,7 +3633,7 @@ impl BlockContext<'_> { } Statement::WorkGroupUniformLoad { pointer, result } => { self.writer - .write_control_barrier(crate::Barrier::WORK_GROUP, &mut block); + .write_control_barrier(crate::Barrier::WORK_GROUP, &mut block.body); let result_type_id = self.get_expression_type_id(&self.fun_info[result].ty); // Embed the body of match self.write_access_chain( @@ -3653,7 +3673,7 @@ impl BlockContext<'_> { } } self.writer - .write_control_barrier(crate::Barrier::WORK_GROUP, &mut block); + .write_control_barrier(crate::Barrier::WORK_GROUP, &mut block.body); } Statement::RayQuery { query, ref fun } => { self.write_ray_query_function(query, fun, &mut block); @@ -3726,6 +3746,16 @@ impl BlockContext<'_> { LoopContext::default(), debug_info, )?; + if let Some(super::EntryPointContext { + mesh_state: Some(ref mesh_state), + .. + }) = self.function.entry_point_context + { + let mut block = Block::new(mesh_state.entry_point_epilogue_id); + self.writer + .write_mesh_shader_return(mesh_state, &mut block)?; + self.function.consume(block, Instruction::return_void()); + } Ok(()) } diff --git a/third_party/rust/naga/src/back/spv/helpers.rs b/third_party/rust/naga/src/back/spv/helpers.rs @@ -1,5 +1,6 @@ use alloc::{vec, vec::Vec}; +use arrayvec::ArrayVec; use spirv::Word; use crate::{Handle, UniqueArena}; @@ -34,7 +35,7 @@ pub(super) fn string_to_byte_chunks(input: &str, limit: usize) -> Vec<&[u8]> { let mut start: usize = 0; let mut words = vec![]; while offset < input.len() { - offset = input.floor_char_boundary(offset + limit); + offset = input.floor_char_boundary_polyfill(offset + limit); // Clippy wants us to call as_bytes() first to avoid the UTF-8 check, // but we want to assert the output is valid UTF-8. #[allow(clippy::sliced_string_as_bytes)] @@ -53,8 +54,8 @@ pub(super) const fn map_storage_class(space: crate::AddressSpace) -> spirv::Stor crate::AddressSpace::Storage { .. } => spirv::StorageClass::StorageBuffer, crate::AddressSpace::Uniform => spirv::StorageClass::Uniform, crate::AddressSpace::WorkGroup => spirv::StorageClass::Workgroup, - crate::AddressSpace::PushConstant => spirv::StorageClass::PushConstant, - crate::AddressSpace::TaskPayload => unreachable!(), + crate::AddressSpace::Immediate => spirv::StorageClass::PushConstant, + crate::AddressSpace::TaskPayload => spirv::StorageClass::TaskPayloadWorkgroupEXT, } } @@ -98,7 +99,7 @@ pub fn global_needs_wrapper(ir_module: &crate::Module, var: &crate::GlobalVariab match var.space { crate::AddressSpace::Uniform | crate::AddressSpace::Storage { .. } - | crate::AddressSpace::PushConstant => {} + | crate::AddressSpace::Immediate => {} _ => return false, }; match ir_module.types[var.ty].inner { @@ -123,33 +124,45 @@ pub fn global_needs_wrapper(ir_module: &crate::Module, var: &crate::GlobalVariab } ///HACK: this is taken from std unstable, remove it when std's floor_char_boundary is stable +/// and available in our msrv. trait U8Internal { - fn is_utf8_char_boundary(&self) -> bool; + fn is_utf8_char_boundary_polyfill(&self) -> bool; } impl U8Internal for u8 { - fn is_utf8_char_boundary(&self) -> bool { + fn is_utf8_char_boundary_polyfill(&self) -> bool { // This is bit magic equivalent to: b < 128 || b >= 192 (*self as i8) >= -0x40 } } trait StrUnstable { - fn floor_char_boundary(&self, index: usize) -> usize; + fn floor_char_boundary_polyfill(&self, index: usize) -> usize; } impl StrUnstable for str { - fn floor_char_boundary(&self, index: usize) -> usize { + fn floor_char_boundary_polyfill(&self, index: usize) -> usize { if index >= self.len() { self.len() } else { let lower_bound = index.saturating_sub(3); let new_index = self.as_bytes()[lower_bound..=index] .iter() - .rposition(|b| b.is_utf8_char_boundary()); + .rposition(|b| b.is_utf8_char_boundary_polyfill()); // SAFETY: we know that the character boundary will be within four bytes unsafe { lower_bound + new_index.unwrap_unchecked() } } } } + +pub enum BindingDecorations { + BuiltIn(spirv::BuiltIn, ArrayVec<spirv::Decoration, 2>), + Location { + location: u32, + others: ArrayVec<spirv::Decoration, 5>, + /// If this is `Some`, use Decoration::Index with blend_src as an operand + blend_src: Option<Word>, + }, + None, +} diff --git a/third_party/rust/naga/src/back/spv/instructions.rs b/third_party/rust/naga/src/back/spv/instructions.rs @@ -842,6 +842,12 @@ impl super::Instruction { instruction } + pub(super) fn ray_query_terminate(query: Word) -> Self { + let mut instruction = Self::new(Op::RayQueryTerminateKHR); + instruction.add_operand(query); + instruction + } + // // Conversion Instructions // diff --git a/third_party/rust/naga/src/back/spv/mesh_shader.rs b/third_party/rust/naga/src/back/spv/mesh_shader.rs @@ -0,0 +1,858 @@ +use alloc::vec::Vec; +use spirv::Word; + +use crate::{ + back::spv::{ + helpers::BindingDecorations, writer::FunctionInterface, Block, EntryPointContext, Error, + Instruction, ResultMember, WriterFlags, + }, + non_max_u32::NonMaxU32, + Handle, +}; + +#[derive(Clone)] +pub struct MeshReturnMember { + pub ty_id: u32, + pub binding: crate::Binding, +} + +struct PerOutputTypeMeshReturnInfo { + max_length_constant: Word, + array_type_id: Word, + struct_members: Vec<MeshReturnMember>, + + // * Most builtins must be in the same block. + // * All bindings must be in their own unique block. + // * The primitive indices builtin family needs its own block. + // * Cull primitive doesn't care about having its own block, but + // some older validation layers didn't respect this. + builtin_block: Option<Word>, + bindings: Vec<Word>, +} + +pub struct MeshReturnInfo { + /// Id of the workgroup variable containing the data to be output + out_variable_id: Word, + /// All members of the output variable struct type + out_members: Vec<MeshReturnMember>, + /// Id of the input variable for local invocation id + local_invocation_index_id: Word, + /// Total workgroup size (product) + workgroup_size: u32, + /// Variable to be used later when saving the output as a loop index + loop_counter_vertices: Word, + /// Variable to be used later when saving the output as a loop index + loop_counter_primitives: Word, + /// The id of the label to jump to when `return` is called + pub entry_point_epilogue_id: Word, + + /// Vertex-specific info + vertex_info: PerOutputTypeMeshReturnInfo, + /// Primitive-specific info + primitive_info: PerOutputTypeMeshReturnInfo, + /// Array variable for the primitive indices builtin + primitive_indices: Option<Word>, +} + +impl super::Writer { + pub(super) fn require_mesh_shaders(&mut self) -> Result<(), Error> { + self.use_extension("SPV_EXT_mesh_shader"); + self.require_any("Mesh Shaders", &[spirv::Capability::MeshShadingEXT])?; + let lang_version = self.lang_version(); + if lang_version.0 <= 1 && lang_version.1 < 4 { + return Err(Error::SpirvVersionTooLow(1, 4)); + } + Ok(()) + } + + /// Sets up an output variable that will handle part of the mesh shader output + pub(super) fn write_mesh_return_global_variable( + &mut self, + ty: u32, + array_size_id: u32, + ) -> Result<Word, Error> { + let array_ty = self.id_gen.next(); + Instruction::type_array(array_ty, ty, array_size_id) + .to_words(&mut self.logical_layout.declarations); + let ptr_ty = self.get_pointer_type_id(array_ty, spirv::StorageClass::Output); + let var_id = self.id_gen.next(); + Instruction::variable(ptr_ty, var_id, spirv::StorageClass::Output, None) + .to_words(&mut self.logical_layout.declarations); + Ok(var_id) + } + + /// This does various setup things to allow mesh shader entry points + /// to be properly written, such as creating the output variables + pub(super) fn write_entry_point_mesh_shader_info( + &mut self, + iface: &mut FunctionInterface, + local_invocation_index_id: Option<Word>, + ir_module: &crate::Module, + prelude: &mut Block, + ep_context: &mut EntryPointContext, + ) -> Result<(), Error> { + let Some(ref mesh_info) = iface.mesh_info else { + return Ok(()); + }; + // Collect the members in the output structs + let out_members: Vec<MeshReturnMember> = + match &ir_module.types[ir_module.global_variables[mesh_info.output_variable].ty] { + &crate::Type { + inner: crate::TypeInner::Struct { ref members, .. }, + .. + } => members + .iter() + .map(|a| MeshReturnMember { + ty_id: self.get_handle_type_id(a.ty), + binding: a.binding.clone().unwrap(), + }) + .collect(), + _ => unreachable!(), + }; + let vertex_array_type_id = out_members + .iter() + .find(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::Vertices)) + .unwrap() + .ty_id; + let primitive_array_type_id = out_members + .iter() + .find(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::Primitives)) + .unwrap() + .ty_id; + let vertex_members = match &ir_module.types[mesh_info.vertex_output_type] { + &crate::Type { + inner: crate::TypeInner::Struct { ref members, .. }, + .. + } => members + .iter() + .map(|a| MeshReturnMember { + ty_id: self.get_handle_type_id(a.ty), + binding: a.binding.clone().unwrap(), + }) + .collect(), + _ => unreachable!(), + }; + let primitive_members = match &ir_module.types[mesh_info.primitive_output_type] { + &crate::Type { + inner: crate::TypeInner::Struct { ref members, .. }, + .. + } => members + .iter() + .map(|a| MeshReturnMember { + ty_id: self.get_handle_type_id(a.ty), + binding: a.binding.clone().unwrap(), + }) + .collect(), + _ => unreachable!(), + }; + // In the final return, we do a giant memcpy, for which this is helpful + let local_invocation_index_id = match local_invocation_index_id { + Some(a) => a, + None => { + let u32_id = self.get_u32_type_id(); + let var = self.id_gen.next(); + Instruction::variable( + self.get_pointer_type_id(u32_id, spirv::StorageClass::Input), + var, + spirv::StorageClass::Input, + None, + ) + .to_words(&mut self.logical_layout.declarations); + Instruction::decorate( + var, + spirv::Decoration::BuiltIn, + &[spirv::BuiltIn::LocalInvocationIndex as u32], + ) + .to_words(&mut self.logical_layout.annotations); + iface.varying_ids.push(var); + + let loaded_value = self.id_gen.next(); + prelude + .body + .push(Instruction::load(u32_id, loaded_value, var, None)); + loaded_value + } + }; + let u32_id = self.get_u32_type_id(); + // A general function variable that we guarantee to allow in the final return. It must be + // declared at the top of the function. Currently it is used in the memcpy part to keep + // track of the current index to copy. + let loop_counter_1 = self.id_gen.next(); + let loop_counter_2 = self.id_gen.next(); + prelude.body.insert( + 0, + Instruction::variable( + self.get_pointer_type_id(u32_id, spirv::StorageClass::Function), + loop_counter_1, + spirv::StorageClass::Function, + None, + ), + ); + prelude.body.insert( + 1, + Instruction::variable( + self.get_pointer_type_id(u32_id, spirv::StorageClass::Function), + loop_counter_2, + spirv::StorageClass::Function, + None, + ), + ); + // This is the information that is passed to the function writer + // so that it can write the final return logic + let mut mesh_return_info = MeshReturnInfo { + out_variable_id: self.global_variables[mesh_info.output_variable].var_id, + out_members, + local_invocation_index_id, + workgroup_size: self + .get_constant_scalar(crate::Literal::U32(iface.workgroup_size.iter().product())), + loop_counter_vertices: loop_counter_1, + loop_counter_primitives: loop_counter_2, + entry_point_epilogue_id: self.id_gen.next(), + + vertex_info: PerOutputTypeMeshReturnInfo { + array_type_id: vertex_array_type_id, + struct_members: vertex_members, + max_length_constant: self + .get_constant_scalar(crate::Literal::U32(mesh_info.max_vertices)), + bindings: Vec::new(), + builtin_block: None, + }, + primitive_info: PerOutputTypeMeshReturnInfo { + array_type_id: primitive_array_type_id, + struct_members: primitive_members, + max_length_constant: self + .get_constant_scalar(crate::Literal::U32(mesh_info.max_primitives)), + bindings: Vec::new(), + builtin_block: None, + }, + primitive_indices: None, + }; + let vert_array_size_id = + self.get_constant_scalar(crate::Literal::U32(mesh_info.max_vertices)); + let prim_array_size_id = + self.get_constant_scalar(crate::Literal::U32(mesh_info.max_primitives)); + + // Create the actual output variables and types. + // According to SPIR-V, + // * All builtins must be in the same output `Block` (except builtins for different output types like vertex/primitive) + // * Each member with `location` must be in its own `Block` decorated `struct` + // * Some builtins like CullPrimitiveEXT don't care as much (older validation layers don't know this! Wonderful!) + // * Some builtins like the indices ones need to be in their own output variable without a struct wrapper + + // Write vertex builtin block + if mesh_return_info + .vertex_info + .struct_members + .iter() + .any(|a| matches!(a.binding, crate::Binding::BuiltIn(..))) + { + let builtin_block_ty_id = self.id_gen.next(); + let mut ins = Instruction::type_struct(builtin_block_ty_id, &[]); + let mut bi_index = 0; + let mut decorations = Vec::new(); + for member in &mesh_return_info.vertex_info.struct_members { + if let crate::Binding::BuiltIn(_) = member.binding { + ins.add_operand(member.ty_id); + let binding = self.map_binding( + ir_module, + iface.stage, + spirv::StorageClass::Output, + // Unused except in fragment shaders with other conditions, so we can pass null + Handle::new(NonMaxU32::new(0).unwrap()), + &member.binding, + )?; + match binding { + BindingDecorations::BuiltIn(bi, others) => { + decorations.push(Instruction::member_decorate( + builtin_block_ty_id, + bi_index, + spirv::Decoration::BuiltIn, + &[bi as Word], + )); + for other in others { + decorations.push(Instruction::member_decorate( + builtin_block_ty_id, + bi_index, + other, + &[], + )); + } + } + _ => unreachable!(), + } + bi_index += 1; + } + } + ins.to_words(&mut self.logical_layout.declarations); + decorations.push(Instruction::decorate( + builtin_block_ty_id, + spirv::Decoration::Block, + &[], + )); + for dec in decorations { + dec.to_words(&mut self.logical_layout.annotations); + } + let v = + self.write_mesh_return_global_variable(builtin_block_ty_id, vert_array_size_id)?; + iface.varying_ids.push(v); + if self.flags.contains(WriterFlags::DEBUG) { + self.debugs + .push(Instruction::name(v, "naga_vertex_builtin_outputs")); + } + mesh_return_info.vertex_info.builtin_block = Some(v); + } + // Write primitive builtin block + if mesh_return_info + .primitive_info + .struct_members + .iter() + .any(|a| { + !matches!( + a.binding, + crate::Binding::BuiltIn( + crate::BuiltIn::PointIndex + | crate::BuiltIn::LineIndices + | crate::BuiltIn::TriangleIndices + ) | crate::Binding::Location { .. } + ) + }) + { + let builtin_block_ty_id = self.id_gen.next(); + let mut ins = Instruction::type_struct(builtin_block_ty_id, &[]); + let mut bi_index = 0; + let mut decorations = Vec::new(); + for member in &mesh_return_info.primitive_info.struct_members { + if let crate::Binding::BuiltIn(bi) = member.binding { + // These need to be in their own block, unlike other builtins + if matches!( + bi, + crate::BuiltIn::PointIndex + | crate::BuiltIn::LineIndices + | crate::BuiltIn::TriangleIndices, + ) { + continue; + } + ins.add_operand(member.ty_id); + let binding = self.map_binding( + ir_module, + iface.stage, + spirv::StorageClass::Output, + // Unused except in fragment shaders with other conditions, so we can pass null + Handle::new(NonMaxU32::new(0).unwrap()), + &member.binding, + )?; + match binding { + BindingDecorations::BuiltIn(bi, others) => { + decorations.push(Instruction::member_decorate( + builtin_block_ty_id, + bi_index, + spirv::Decoration::BuiltIn, + &[bi as Word], + )); + for other in others { + decorations.push(Instruction::member_decorate( + builtin_block_ty_id, + bi_index, + other, + &[], + )); + } + } + _ => unreachable!(), + } + bi_index += 1; + } + } + ins.to_words(&mut self.logical_layout.declarations); + decorations.push(Instruction::decorate( + builtin_block_ty_id, + spirv::Decoration::Block, + &[], + )); + for dec in decorations { + dec.to_words(&mut self.logical_layout.annotations); + } + let v = + self.write_mesh_return_global_variable(builtin_block_ty_id, prim_array_size_id)?; + Instruction::decorate(v, spirv::Decoration::PerPrimitiveEXT, &[]) + .to_words(&mut self.logical_layout.annotations); + iface.varying_ids.push(v); + if self.flags.contains(WriterFlags::DEBUG) { + self.debugs + .push(Instruction::name(v, "naga_primitive_builtin_outputs")); + } + mesh_return_info.primitive_info.builtin_block = Some(v); + } + + // Write vertex binding output blocks (1 array per output struct member) + for member in &mesh_return_info.vertex_info.struct_members { + match member.binding { + crate::Binding::Location { location, .. } => { + // Create variable + let v = + self.write_mesh_return_global_variable(member.ty_id, vert_array_size_id)?; + // Decorate the variable with Location + Instruction::decorate(v, spirv::Decoration::Location, &[location]) + .to_words(&mut self.logical_layout.annotations); + iface.varying_ids.push(v); + mesh_return_info.vertex_info.bindings.push(v); + } + crate::Binding::BuiltIn(_) => (), + } + } + // Write primitive binding output blocks (1 array per output struct member) + // Also write indices output block + for member in &mesh_return_info.primitive_info.struct_members { + match member.binding { + crate::Binding::BuiltIn( + crate::BuiltIn::PointIndex + | crate::BuiltIn::LineIndices + | crate::BuiltIn::TriangleIndices, + ) => { + // This is written here instead of as part of the builtin block + let v = + self.write_mesh_return_global_variable(member.ty_id, prim_array_size_id)?; + // This shouldn't be marked as PerPrimitiveEXT + Instruction::decorate( + v, + spirv::Decoration::BuiltIn, + &[match member.binding.to_built_in().unwrap() { + crate::BuiltIn::PointIndex => spirv::BuiltIn::PrimitivePointIndicesEXT, + crate::BuiltIn::LineIndices => spirv::BuiltIn::PrimitiveLineIndicesEXT, + crate::BuiltIn::TriangleIndices => { + spirv::BuiltIn::PrimitiveTriangleIndicesEXT + } + _ => unreachable!(), + } as Word], + ) + .to_words(&mut self.logical_layout.annotations); + iface.varying_ids.push(v); + if self.flags.contains(WriterFlags::DEBUG) { + self.debugs + .push(Instruction::name(v, "naga_primitive_indices_outputs")); + } + mesh_return_info.primitive_indices = Some(v); + } + crate::Binding::Location { location, .. } => { + // Create variable + let v = + self.write_mesh_return_global_variable(member.ty_id, prim_array_size_id)?; + // Decorate the variable with Location + Instruction::decorate(v, spirv::Decoration::Location, &[location]) + .to_words(&mut self.logical_layout.annotations); + // Decorate it with PerPrimitiveEXT + Instruction::decorate(v, spirv::Decoration::PerPrimitiveEXT, &[]) + .to_words(&mut self.logical_layout.annotations); + iface.varying_ids.push(v); + + mesh_return_info.primitive_info.bindings.push(v); + } + crate::Binding::BuiltIn(_) => (), + } + } + + // Store this where it can be read later during function write + ep_context.mesh_state = Some(mesh_return_info); + + Ok(()) + } + + pub(super) fn try_write_entry_point_task_return( + &mut self, + value_id: Word, + ir_result: &crate::FunctionResult, + result_members: &[ResultMember], + body: &mut Vec<Instruction>, + task_payload: Option<Word>, + ) -> Result<Instruction, Error> { + // OpEmitMeshTasksEXT must be called right before exiting (after setting other + // output variables if there are any) + for (index, res_member) in result_members.iter().enumerate() { + if res_member.built_in == Some(crate::BuiltIn::MeshTaskSize) { + self.write_control_barrier(crate::Barrier::WORK_GROUP, body); + // If its a function like `fn a() -> @builtin(...) vec3<u32> ...` + // then just use the output value. If it's a struct, extract the + // value from the struct. + let member_value_id = match ir_result.binding { + Some(_) => value_id, + None => { + let member_value_id = self.id_gen.next(); + body.push(Instruction::composite_extract( + res_member.type_id, + member_value_id, + value_id, + &[index as Word], + )); + member_value_id + } + }; + + // Extract the vec3<u32> into 3 u32's + let values = [self.id_gen.next(), self.id_gen.next(), self.id_gen.next()]; + for (i, &value) in values.iter().enumerate() { + let instruction = Instruction::composite_extract( + self.get_u32_type_id(), + value, + member_value_id, + &[i as Word], + ); + body.push(instruction); + } + // TODO: make this guaranteed to be uniform + let mut instruction = Instruction::new(spirv::Op::EmitMeshTasksEXT); + for id in values { + instruction.add_operand(id); + } + // We have to include the task payload in our call + if let Some(task_payload) = task_payload { + instruction.add_operand(task_payload); + } + return Ok(instruction); + } + } + Ok(Instruction::return_void()) + } + + /// This writes the actual loop + #[allow(clippy::too_many_arguments)] + fn write_mesh_copy_loop( + &mut self, + body: &mut Vec<Instruction>, + mut loop_body_block: Vec<Instruction>, + loop_header: u32, + loop_merge: u32, + count_id: u32, + index_var: u32, + return_info: &MeshReturnInfo, + ) { + let u32_id = self.get_u32_type_id(); + let condition_check = self.id_gen.next(); + let loop_continue = self.id_gen.next(); + let loop_body = self.id_gen.next(); + + // Loop header + { + body.push(Instruction::label(loop_header)); + body.push(Instruction::loop_merge( + loop_merge, + loop_continue, + spirv::SelectionControl::empty(), + )); + body.push(Instruction::branch(condition_check)); + } + // Condition check - check if i is less than num vertices to copy + { + body.push(Instruction::label(condition_check)); + + let val_i = self.id_gen.next(); + body.push(Instruction::load(u32_id, val_i, index_var, None)); + + let cond = self.id_gen.next(); + body.push(Instruction::binary( + spirv::Op::ULessThan, + self.get_bool_type_id(), + cond, + val_i, + count_id, + )); + body.push(Instruction::branch_conditional(cond, loop_body, loop_merge)); + } + // Loop body + { + body.push(Instruction::label(loop_body)); + body.append(&mut loop_body_block); + body.push(Instruction::branch(loop_continue)); + } + // Loop continue - increment i + { + body.push(Instruction::label(loop_continue)); + + let prev_val_i = self.id_gen.next(); + body.push(Instruction::load(u32_id, prev_val_i, index_var, None)); + let new_val_i = self.id_gen.next(); + body.push(Instruction::binary( + spirv::Op::IAdd, + u32_id, + new_val_i, + prev_val_i, + return_info.workgroup_size, + )); + body.push(Instruction::store(index_var, new_val_i, None)); + + body.push(Instruction::branch(loop_header)); + } + } + + /// This generates the instructions used to copy all parts of a single output vertex/primitive + /// to their individual output locations + fn write_mesh_copy_body( + &mut self, + is_primitive: bool, + return_info: &MeshReturnInfo, + index_var: u32, + vert_array_ptr: u32, + prim_array_ptr: u32, + ) -> Vec<Instruction> { + let u32_type_id = self.get_u32_type_id(); + let mut body = Vec::new(); + // Current index to copy + let val_i = self.id_gen.next(); + body.push(Instruction::load(u32_type_id, val_i, index_var, None)); + + let info = if is_primitive { + &return_info.primitive_info + } else { + &return_info.vertex_info + }; + let array_ptr = if is_primitive { + prim_array_ptr + } else { + vert_array_ptr + }; + + let mut builtin_index = 0; + let mut binding_index = 0; + // Write individual members of the vertex + for (member_id, member) in info.struct_members.iter().enumerate() { + let val_to_copy_ptr = self.id_gen.next(); + body.push(Instruction::access_chain( + self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Workgroup), + val_to_copy_ptr, + array_ptr, + &[ + val_i, + self.get_constant_scalar(crate::Literal::U32(member_id as u32)), + ], + )); + let val_to_copy = self.id_gen.next(); + body.push(Instruction::load( + member.ty_id, + val_to_copy, + val_to_copy_ptr, + None, + )); + let mut needs_y_flip = false; + let ptr_to_copy_to = self.id_gen.next(); + // Get a pointer to the struct member to copy + match member.binding { + crate::Binding::BuiltIn( + crate::BuiltIn::PointIndex + | crate::BuiltIn::LineIndices + | crate::BuiltIn::TriangleIndices, + ) => { + body.push(Instruction::access_chain( + self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), + ptr_to_copy_to, + return_info.primitive_indices.unwrap(), + &[val_i], + )); + } + crate::Binding::BuiltIn(bi) => { + body.push(Instruction::access_chain( + self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), + ptr_to_copy_to, + info.builtin_block.unwrap(), + &[ + val_i, + self.get_constant_scalar(crate::Literal::U32(builtin_index)), + ], + )); + needs_y_flip = matches!(bi, crate::BuiltIn::Position { .. }) + && self.flags.contains(WriterFlags::ADJUST_COORDINATE_SPACE); + builtin_index += 1; + } + crate::Binding::Location { .. } => { + body.push(Instruction::access_chain( + self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), + ptr_to_copy_to, + info.bindings[binding_index], + &[val_i], + )); + binding_index += 1; + } + } + body.push(Instruction::store(ptr_to_copy_to, val_to_copy, None)); + // Flip the vertex position y coordinate in some cases + // Can't use epilogue flip because can't read from this storage class + if needs_y_flip { + let prev_y = self.id_gen.next(); + body.push(Instruction::composite_extract( + self.get_f32_type_id(), + prev_y, + val_to_copy, + &[1], + )); + let new_y = self.id_gen.next(); + body.push(Instruction::unary( + spirv::Op::FNegate, + self.get_f32_type_id(), + new_y, + prev_y, + )); + let new_ptr_to_copy_to = self.id_gen.next(); + body.push(Instruction::access_chain( + self.get_f32_pointer_type_id(spirv::StorageClass::Output), + new_ptr_to_copy_to, + ptr_to_copy_to, + &[self.get_constant_scalar(crate::Literal::U32(1))], + )); + body.push(Instruction::store(new_ptr_to_copy_to, new_y, None)); + } + } + body + } + + /// Writes the return call for a mesh shader, which involves copying previously + /// written vertices/primitives into the actual output location. + pub(super) fn write_mesh_shader_return( + &mut self, + return_info: &MeshReturnInfo, + block: &mut Block, + ) -> Result<(), Error> { + // Start with a control barrier so that everything that follows is guaranteed to see the same variables + self.write_control_barrier(crate::Barrier::WORK_GROUP, &mut block.body); + let u32_id = self.get_u32_type_id(); + + // Load the actual vertex and primitive counts + let mut load_u32_by_member_index = + |members: &[MeshReturnMember], bi: crate::BuiltIn, max: u32| { + let member_index = members + .iter() + .position(|a| a.binding == crate::Binding::BuiltIn(bi)) + .unwrap() as u32; + let ptr_id = self.id_gen.next(); + block.body.push(Instruction::access_chain( + self.get_pointer_type_id(u32_id, spirv::StorageClass::Workgroup), + ptr_id, + return_info.out_variable_id, + &[self.get_constant_scalar(crate::Literal::U32(member_index))], + )); + let before_min_id = self.id_gen.next(); + block + .body + .push(Instruction::load(u32_id, before_min_id, ptr_id, None)); + + // Clamp the values + let id = self.id_gen.next(); + block.body.push(Instruction::ext_inst_gl_op( + self.gl450_ext_inst_id, + spirv::GLOp::UMin, + u32_id, + id, + &[before_min_id, max], + )); + id + }; + let vert_count_id = load_u32_by_member_index( + &return_info.out_members, + crate::BuiltIn::VertexCount, + return_info.vertex_info.max_length_constant, + ); + let prim_count_id = load_u32_by_member_index( + &return_info.out_members, + crate::BuiltIn::PrimitiveCount, + return_info.primitive_info.max_length_constant, + ); + + // Get pointers to the arrays of data to extract + let mut get_array_ptr = |bi: crate::BuiltIn, array_type_id: u32| { + let id = self.id_gen.next(); + block.body.push(Instruction::access_chain( + self.get_pointer_type_id(array_type_id, spirv::StorageClass::Workgroup), + id, + return_info.out_variable_id, + &[self.get_constant_scalar(crate::Literal::U32( + return_info + .out_members + .iter() + .position(|a| a.binding == crate::Binding::BuiltIn(bi)) + .unwrap() as u32, + ))], + )); + id + }; + let vert_array_ptr = get_array_ptr( + crate::BuiltIn::Vertices, + return_info.vertex_info.array_type_id, + ); + let prim_array_ptr = get_array_ptr( + crate::BuiltIn::Primitives, + return_info.primitive_info.array_type_id, + ); + + self.write_control_barrier(crate::Barrier::WORK_GROUP, &mut block.body); + + // This must be called exactly once before any other mesh outputs are written + { + let mut ins = Instruction::new(spirv::Op::SetMeshOutputsEXT); + ins.add_operand(vert_count_id); + ins.add_operand(prim_count_id); + block.body.push(ins); + } + + // This is iterating over every returned vertex and splitting + // it out into the multiple per-output arrays. + let vertex_loop_header = self.id_gen.next(); + let prim_loop_header = self.id_gen.next(); + let in_between_loops = self.id_gen.next(); + let func_end = self.id_gen.next(); + + block.body.push(Instruction::store( + return_info.loop_counter_vertices, + return_info.local_invocation_index_id, + None, + )); + block.body.push(Instruction::branch(vertex_loop_header)); + + let vertex_copy_body = self.write_mesh_copy_body( + false, + return_info, + return_info.loop_counter_vertices, + vert_array_ptr, + prim_array_ptr, + ); + // Write vertex copy loop + self.write_mesh_copy_loop( + &mut block.body, + vertex_copy_body, + vertex_loop_header, + in_between_loops, + vert_count_id, + return_info.loop_counter_vertices, + return_info, + ); + + // In between loops, reset the initial index + { + block.body.push(Instruction::label(in_between_loops)); + + block.body.push(Instruction::store( + return_info.loop_counter_primitives, + return_info.local_invocation_index_id, + None, + )); + + block.body.push(Instruction::branch(prim_loop_header)); + } + let primitive_copy_body = self.write_mesh_copy_body( + true, + return_info, + return_info.loop_counter_primitives, + vert_array_ptr, + prim_array_ptr, + ); + // Write primitive copy loop + self.write_mesh_copy_loop( + &mut block.body, + primitive_copy_body, + prim_loop_header, + func_end, + prim_count_id, + return_info.loop_counter_primitives, + return_info, + ); + + block.body.push(Instruction::label(func_end)); + Ok(()) + } +} diff --git a/third_party/rust/naga/src/back/spv/mod.rs b/third_party/rust/naga/src/back/spv/mod.rs @@ -11,12 +11,14 @@ mod image; mod index; mod instructions; mod layout; +mod mesh_shader; mod ray; mod recyclable; mod selection; mod subgroup; mod writer; +pub use mesh_shader::{MeshReturnInfo, MeshReturnMember}; pub use spirv::{Capability, SourceLanguage}; use alloc::{string::String, vec::Vec}; @@ -52,6 +54,7 @@ struct LogicalLayout { function_definitions: Vec<Word>, } +#[derive(Clone)] struct Instruction { op: spirv::Op, wc: u32, @@ -78,6 +81,8 @@ pub enum Error { Override, #[error(transparent)] ResolveArraySizeError(#[from] crate::proc::ResolveArraySizeError), + #[error("module requires SPIRV-{0}.{1}, which isn't supported")] + SpirvVersionTooLow(u8, u8), #[error("mapping of {0:?} is missing")] MissingBinding(crate::ResourceBinding), } @@ -144,6 +149,8 @@ struct ResultMember { struct EntryPointContext { argument_ids: Vec<Word>, results: Vec<ResultMember>, + task_payload_variable_id: Option<Word>, + mesh_state: Option<MeshReturnInfo>, } #[derive(Default)] @@ -459,6 +466,7 @@ enum LookupRayQueryFunction { ConfirmIntersection, GetVertexPositions { committed: bool }, GetIntersection { committed: bool }, + Terminate, } #[derive(Debug)] diff --git a/third_party/rust/naga/src/back/spv/ray.rs b/third_party/rust/naga/src/back/spv/ray.rs @@ -1781,6 +1781,96 @@ impl Writer { func_id } + + fn write_ray_query_terminate(&mut self) -> spirv::Word { + if let Some(&word) = self + .ray_query_functions + .get(&LookupRayQueryFunction::Terminate) + { + return word; + } + + let ray_query_type_id = self.get_ray_query_pointer_id(); + + let u32_ty = self.get_u32_type_id(); + let u32_ptr_ty = self.get_pointer_type_id(u32_ty, spirv::StorageClass::Function); + + let bool_type_id = self.get_bool_type_id(); + + let (func_id, mut function, arg_ids) = + self.write_function_signature(&[ray_query_type_id, u32_ptr_ty], self.void_type); + + let query_id = arg_ids[0]; + let init_tracker_id = arg_ids[1]; + + let block_id = self.id_gen.next(); + let mut block = Block::new(block_id); + + let initialized_tracker_id = self.id_gen.next(); + block.body.push(Instruction::load( + u32_ty, + initialized_tracker_id, + init_tracker_id, + None, + )); + + let merge_id = self.id_gen.next(); + let merge_block = Block::new(merge_id); + + let valid_block_id = self.id_gen.next(); + let mut valid_block = Block::new(valid_block_id); + + let instruction = if self.ray_query_initialization_tracking { + let has_proceeded = write_ray_flags_contains_flags( + self, + &mut block, + initialized_tracker_id, + super::RayQueryPoint::PROCEED.bits(), + ); + + let finished_proceed_id = write_ray_flags_contains_flags( + self, + &mut block, + initialized_tracker_id, + super::RayQueryPoint::FINISHED_TRAVERSAL.bits(), + ); + + let not_finished_id = self.id_gen.next(); + block.body.push(Instruction::unary( + spirv::Op::LogicalNot, + bool_type_id, + not_finished_id, + finished_proceed_id, + )); + + let valid_call = self.write_logical_and(&mut block, not_finished_id, has_proceeded); + + block.body.push(Instruction::selection_merge( + merge_id, + spirv::SelectionControl::NONE, + )); + + Instruction::branch_conditional(valid_call, valid_block_id, merge_id) + } else { + Instruction::branch(valid_block_id) + }; + + function.consume(block, instruction); + + valid_block + .body + .push(Instruction::ray_query_terminate(query_id)); + + function.consume(valid_block, Instruction::branch(merge_id)); + + function.consume(merge_block, Instruction::return_void()); + + function.to_words(&mut self.logical_layout.function_definitions); + + self.ray_query_functions + .insert(LookupRayQueryFunction::Proceed, func_id); + func_id + } } impl BlockContext<'_> { @@ -1863,7 +1953,17 @@ impl BlockContext<'_> { &[query_id, tracker_ids.initialized_tracker], )); } - crate::RayQueryFunction::Terminate => {} + crate::RayQueryFunction::Terminate => { + let id = self.gen_id(); + + let func_id = self.writer.write_ray_query_terminate(); + block.body.push(Instruction::function_call( + self.writer.void_type, + id, + func_id, + &[query_id, tracker_ids.initialized_tracker], + )); + } } } diff --git a/third_party/rust/naga/src/back/spv/writer.rs b/third_party/rust/naga/src/back/spv/writer.rs @@ -1,5 +1,6 @@ use alloc::{string::String, vec, vec::Vec}; +use arrayvec::ArrayVec; use hashbrown::hash_map::Entry; use spirv::Word; @@ -13,14 +14,17 @@ use super::{ }; use crate::{ arena::{Handle, HandleVec, UniqueArena}, - back::spv::{BindingInfo, WrappedFunction}, + back::spv::{helpers::BindingDecorations, BindingInfo, WrappedFunction}, proc::{Alignment, TypeResolution}, valid::{FunctionInfo, ModuleInfo}, }; -struct FunctionInterface<'a> { - varying_ids: &'a mut Vec<Word>, - stage: crate::ShaderStage, +pub struct FunctionInterface<'a> { + pub varying_ids: &'a mut Vec<Word>, + pub stage: crate::ShaderStage, + pub task_payload: Option<Handle<crate::GlobalVariable>>, + pub mesh_info: Option<crate::MeshStageInfo>, + pub workgroup_size: [u32; 3], } impl Function { @@ -754,11 +758,20 @@ impl Writer { let mut ep_context = EntryPointContext { argument_ids: Vec::new(), results: Vec::new(), + task_payload_variable_id: if let Some(ref i) = interface { + i.task_payload.map(|a| self.global_variables[a].var_id) + } else { + None + }, + mesh_state: None, }; let mut local_invocation_id = None; let mut parameter_type_ids = Vec::with_capacity(ir_function.arguments.len()); + + let mut local_invocation_index_id = None; + for argument in ir_function.arguments.iter() { let class = spirv::StorageClass::Input; let handle_ty = ir_module.types[argument.ty].inner.is_handle(); @@ -789,6 +802,10 @@ impl Writer { if binding == &crate::Binding::BuiltIn(crate::BuiltIn::LocalInvocationId) { local_invocation_id = Some(id); + } else if binding + == &crate::Binding::BuiltIn(crate::BuiltIn::LocalInvocationIndex) + { + local_invocation_index_id = Some(id); } id @@ -816,6 +833,10 @@ impl Writer { if binding == &crate::Binding::BuiltIn(crate::BuiltIn::LocalInvocationId) { local_invocation_id = Some(id); + } else if binding + == &crate::Binding::BuiltIn(crate::BuiltIn::LocalInvocationIndex) + { + local_invocation_index_id = Some(id); } } prelude.body.push(Instruction::composite_construct( @@ -864,15 +885,21 @@ impl Writer { has_point_size |= *binding == crate::Binding::BuiltIn(crate::BuiltIn::PointSize); let type_id = self.get_handle_type_id(result.ty); - let varying_id = self.write_varying( - ir_module, - iface.stage, - class, - None, - result.ty, - binding, - )?; - iface.varying_ids.push(varying_id); + let varying_id = + if *binding == crate::Binding::BuiltIn(crate::BuiltIn::MeshTaskSize) { + 0 + } else { + let varying_id = self.write_varying( + ir_module, + iface.stage, + class, + None, + result.ty, + binding, + )?; + iface.varying_ids.push(varying_id); + varying_id + }; ep_context.results.push(ResultMember { id: varying_id, type_id, @@ -887,15 +914,25 @@ impl Writer { let binding = member.binding.as_ref().unwrap(); has_point_size |= *binding == crate::Binding::BuiltIn(crate::BuiltIn::PointSize); - let varying_id = self.write_varying( - ir_module, - iface.stage, - class, - name, - member.ty, - binding, - )?; - iface.varying_ids.push(varying_id); + // This isn't an actual builtin in SPIR-V. It can only appear as the + // output of a task shader and the output is used when writing the + // entry point return, in which case the id is ignored anyway. + let varying_id = if *binding + == crate::Binding::BuiltIn(crate::BuiltIn::MeshTaskSize) + { + 0 + } else { + let varying_id = self.write_varying( + ir_module, + iface.stage, + class, + name, + member.ty, + binding, + )?; + iface.varying_ids.push(varying_id); + varying_id + }; ep_context.results.push(ResultMember { id: varying_id, type_id, @@ -935,6 +972,21 @@ impl Writer { None => self.void_type, }; + if let Some(ref mut iface) = interface { + if let Some(task_payload) = iface.task_payload { + iface + .varying_ids + .push(self.global_variables[task_payload].var_id); + } + self.write_entry_point_mesh_shader_info( + iface, + local_invocation_index_id, + ir_module, + &mut prelude, + &mut ep_context, + )?; + } + let lookup_function_type = LookupFunctionType { parameter_type_ids, return_type_id, @@ -971,7 +1023,7 @@ impl Writer { let mut gv = self.global_variables[handle].clone(); if let Some(ref mut iface) = interface { // Have to include global variables in the interface - if self.physical_layout.version >= 0x10400 { + if self.physical_layout.version >= 0x10400 && iface.task_payload != Some(handle) { iface.varying_ids.push(gv.var_id); } } @@ -1223,6 +1275,9 @@ impl Writer { Some(FunctionInterface { varying_ids: &mut interface_ids, stage: entry_point.stage, + task_payload: entry_point.task_payload, + mesh_info: entry_point.mesh_info.clone(), + workgroup_size: entry_point.workgroup_size, }), debug_info, )?; @@ -1277,7 +1332,6 @@ impl Writer { } crate::ShaderStage::Compute => { let execution_mode = spirv::ExecutionMode::LocalSize; - //self.check(execution_mode.required_capabilities())?; Instruction::execution_mode( function_id, execution_mode, @@ -1286,7 +1340,51 @@ impl Writer { .to_words(&mut self.logical_layout.execution_modes); spirv::ExecutionModel::GLCompute } - crate::ShaderStage::Task | crate::ShaderStage::Mesh => unreachable!(), + crate::ShaderStage::Task => { + let execution_mode = spirv::ExecutionMode::LocalSize; + Instruction::execution_mode( + function_id, + execution_mode, + &entry_point.workgroup_size, + ) + .to_words(&mut self.logical_layout.execution_modes); + spirv::ExecutionModel::TaskEXT + } + crate::ShaderStage::Mesh => { + let execution_mode = spirv::ExecutionMode::LocalSize; + Instruction::execution_mode( + function_id, + execution_mode, + &entry_point.workgroup_size, + ) + .to_words(&mut self.logical_layout.execution_modes); + let mesh_info = entry_point.mesh_info.as_ref().unwrap(); + Instruction::execution_mode( + function_id, + match mesh_info.topology { + crate::MeshOutputTopology::Points => spirv::ExecutionMode::OutputPoints, + crate::MeshOutputTopology::Lines => spirv::ExecutionMode::OutputLinesEXT, + crate::MeshOutputTopology::Triangles => { + spirv::ExecutionMode::OutputTrianglesEXT + } + }, + &[], + ) + .to_words(&mut self.logical_layout.execution_modes); + Instruction::execution_mode( + function_id, + spirv::ExecutionMode::OutputVertices, + core::slice::from_ref(&mesh_info.max_vertices), + ) + .to_words(&mut self.logical_layout.execution_modes); + Instruction::execution_mode( + function_id, + spirv::ExecutionMode::OutputPrimitivesEXT, + core::slice::from_ref(&mesh_info.max_primitives), + ) + .to_words(&mut self.logical_layout.execution_modes); + spirv::ExecutionModel::MeshEXT + } }; //self.check(exec_model.required_capabilities())?; @@ -1812,7 +1910,11 @@ impl Writer { Ok(id) } - pub(super) fn write_control_barrier(&mut self, flags: crate::Barrier, block: &mut Block) { + pub(super) fn write_control_barrier( + &mut self, + flags: crate::Barrier, + body: &mut Vec<Instruction>, + ) { let memory_scope = if flags.contains(crate::Barrier::STORAGE) { spirv::Scope::Device } else if flags.contains(crate::Barrier::SUB_GROUP) { @@ -1844,7 +1946,7 @@ impl Writer { }; let mem_scope_id = self.get_index_constant(memory_scope as u32); let semantics_id = self.get_index_constant(semantics.bits()); - block.body.push(Instruction::control_barrier( + body.push(Instruction::control_barrier( exec_scope_id, mem_scope_id, semantics_id, @@ -1982,7 +2084,7 @@ impl Writer { let mut post_if_block = Block::new(merge_id); - self.write_control_barrier(crate::Barrier::WORK_GROUP, &mut post_if_block); + self.write_control_barrier(crate::Barrier::WORK_GROUP, &mut post_if_block.body); let next_id = self.id_gen.next(); function.consume(post_if_block, Instruction::branch(next_id)); @@ -2017,8 +2119,6 @@ impl Writer { ty: Handle<crate::Type>, binding: &crate::Binding, ) -> Result<Word, Error> { - use crate::TypeInner; - let id = self.id_gen.next(); let ty_inner = &ir_module.types[ty].inner; let needs_polyfill = self.needs_f16_polyfill(ty_inner); @@ -2049,17 +2149,111 @@ impl Writer { } } - use spirv::{BuiltIn, Decoration}; + let binding = self.map_binding(ir_module, stage, class, ty, binding)?; + self.write_binding(id, binding); + + Ok(id) + } + + pub fn write_binding(&mut self, id: Word, binding: BindingDecorations) { + match binding { + BindingDecorations::None => (), + BindingDecorations::BuiltIn(bi, others) => { + self.decorate(id, spirv::Decoration::BuiltIn, &[bi as u32]); + for other in others { + self.decorate(id, other, &[]); + } + } + BindingDecorations::Location { + location, + others, + blend_src, + } => { + self.decorate(id, spirv::Decoration::Location, &[location]); + for other in others { + self.decorate(id, other, &[]); + } + if let Some(blend_src) = blend_src { + self.decorate(id, spirv::Decoration::Index, &[blend_src]); + } + } + } + } + + pub fn write_binding_struct_member( + &mut self, + struct_id: Word, + member_idx: Word, + binding_info: BindingDecorations, + ) { + match binding_info { + BindingDecorations::None => (), + BindingDecorations::BuiltIn(bi, others) => { + self.annotations.push(Instruction::member_decorate( + struct_id, + member_idx, + spirv::Decoration::BuiltIn, + &[bi as Word], + )); + for other in others { + self.annotations.push(Instruction::member_decorate( + struct_id, + member_idx, + other, + &[], + )); + } + } + BindingDecorations::Location { + location, + others, + blend_src, + } => { + self.annotations.push(Instruction::member_decorate( + struct_id, + member_idx, + spirv::Decoration::Location, + &[location], + )); + for other in others { + self.annotations.push(Instruction::member_decorate( + struct_id, + member_idx, + other, + &[], + )); + } + if let Some(blend_src) = blend_src { + self.annotations.push(Instruction::member_decorate( + struct_id, + member_idx, + spirv::Decoration::Index, + &[blend_src], + )); + } + } + } + } + pub fn map_binding( + &mut self, + ir_module: &crate::Module, + stage: crate::ShaderStage, + class: spirv::StorageClass, + ty: Handle<crate::Type>, + binding: &crate::Binding, + ) -> Result<BindingDecorations, Error> { + use spirv::BuiltIn; + use spirv::Decoration; match *binding { crate::Binding::Location { location, interpolation, sampling, blend_src, - per_primitive: _, + per_primitive, } => { - self.decorate(id, Decoration::Location, &[location]); + let mut others = ArrayVec::new(); let no_decorations = // VUID-StandaloneSpirv-Flat-06202 @@ -2076,10 +2270,10 @@ impl Writer { // Perspective-correct interpolation is the default in SPIR-V. None | Some(crate::Interpolation::Perspective) => (), Some(crate::Interpolation::Flat) => { - self.decorate(id, Decoration::Flat, &[]); + others.push(Decoration::Flat); } Some(crate::Interpolation::Linear) => { - self.decorate(id, Decoration::NoPerspective, &[]); + others.push(Decoration::NoPerspective); } } match sampling { @@ -2091,27 +2285,42 @@ impl Writer { | crate::Sampling::Either, ) => (), Some(crate::Sampling::Centroid) => { - self.decorate(id, Decoration::Centroid, &[]); + others.push(Decoration::Centroid); } Some(crate::Sampling::Sample) => { self.require_any( "per-sample interpolation", &[spirv::Capability::SampleRateShading], )?; - self.decorate(id, Decoration::Sample, &[]); + others.push(Decoration::Sample); } } } - if let Some(blend_src) = blend_src { - self.decorate(id, Decoration::Index, &[blend_src]); + if per_primitive && stage == crate::ShaderStage::Fragment { + others.push(Decoration::PerPrimitiveEXT); + self.require_mesh_shaders()?; } + Ok(BindingDecorations::Location { + location, + others, + blend_src, + }) } crate::Binding::BuiltIn(built_in) => { use crate::BuiltIn as Bi; + let mut others = ArrayVec::new(); + + if matches!( + built_in, + Bi::CullPrimitive | Bi::PointIndex | Bi::LineIndices | Bi::TriangleIndices + ) { + self.require_mesh_shaders()?; + } + let built_in = match built_in { Bi::Position { invariant } => { if invariant { - self.decorate(id, Decoration::Invariant, &[]); + others.push(Decoration::Invariant); } if class == spirv::StorageClass::Output { @@ -2154,6 +2363,9 @@ impl Writer { "`primitive_index` built-in", &[spirv::Capability::Geometry], )?; + if stage == crate::ShaderStage::Mesh { + others.push(Decoration::PerPrimitiveEXT); + } BuiltIn::PrimitiveId } Bi::Barycentric => { @@ -2215,19 +2427,31 @@ impl Writer { )?; BuiltIn::SubgroupLocalInvocationId } - Bi::MeshTaskSize - | Bi::CullPrimitive - | Bi::PointIndex - | Bi::LineIndices - | Bi::TriangleIndices - | Bi::VertexCount - | Bi::PrimitiveCount - | Bi::Vertices - | Bi::Primitives => unreachable!(), + Bi::CullPrimitive => { + self.require_mesh_shaders()?; + others.push(Decoration::PerPrimitiveEXT); + BuiltIn::CullPrimitiveEXT + } + Bi::PointIndex => { + self.require_mesh_shaders()?; + BuiltIn::PrimitivePointIndicesEXT + } + Bi::LineIndices => { + self.require_mesh_shaders()?; + BuiltIn::PrimitiveLineIndicesEXT + } + Bi::TriangleIndices => { + self.require_mesh_shaders()?; + BuiltIn::PrimitiveTriangleIndicesEXT + } + // No decoration, this EmitMeshTasksEXT is called at function return + Bi::MeshTaskSize => return Ok(BindingDecorations::None), + // These aren't normal builtins and don't occur in function output + Bi::VertexCount | Bi::Vertices | Bi::PrimitiveCount | Bi::Primitives => { + unreachable!() + } }; - self.decorate(id, Decoration::BuiltIn, &[built_in as u32]); - use crate::ScalarKind as Sk; // Per the Vulkan spec, `VUID-StandaloneSpirv-Flat-04744`: @@ -2237,9 +2461,8 @@ impl Writer { // > shader, must be decorated Flat if class == spirv::StorageClass::Input && stage == crate::ShaderStage::Fragment { let is_flat = match ir_module.types[ty].inner { - TypeInner::Scalar(scalar) | TypeInner::Vector { scalar, .. } => match scalar - .kind - { + crate::TypeInner::Scalar(scalar) + | crate::TypeInner::Vector { scalar, .. } => match scalar.kind { Sk::Uint | Sk::Sint | Sk::Bool => true, Sk::Float => false, Sk::AbstractInt | Sk::AbstractFloat => { @@ -2252,13 +2475,12 @@ impl Writer { }; if is_flat { - self.decorate(id, Decoration::Flat, &[]); + others.push(Decoration::Flat); } } + Ok(BindingDecorations::BuiltIn(built_in, others)) } } - - Ok(id) } /// Load an IO variable, converting from `f32` to `f16` if polyfill is active. @@ -2568,6 +2790,17 @@ impl Writer { | ir_module.special_types.ray_intersection.is_some(); let has_vertex_return = ir_module.special_types.ray_vertex_return.is_some(); + // Ways mesh shaders are required: + // * Mesh entry point used - checked for + // * Mesh function like setVertex used outside mesh entry point, this is handled when those are written + // * Fragment shader with per primitive data - handled in `map_binding` + let has_mesh_shaders = ir_module.entry_points.iter().any(|entry| { + entry.stage == crate::ShaderStage::Mesh || entry.stage == crate::ShaderStage::Task + }) || ir_module + .global_variables + .iter() + .any(|gvar| gvar.1.space == crate::AddressSpace::TaskPayload); + for (_, &crate::Type { ref inner, .. }) in ir_module.types.iter() { // spirv does not know whether these have vertex return - that is done by us if let &crate::TypeInner::AccelerationStructure { .. } @@ -2594,6 +2827,9 @@ impl Writer { Instruction::extension("SPV_KHR_ray_tracing_position_fetch") .to_words(&mut self.logical_layout.extensions); } + if has_mesh_shaders { + self.require_mesh_shaders()?; + } Instruction::type_void(self.void_type).to_words(&mut self.logical_layout.declarations); Instruction::ext_inst_import(self.gl450_ext_inst_id, "GLSL.std.450") .to_words(&mut self.logical_layout.ext_inst_imports); @@ -2666,7 +2902,7 @@ impl Writer { // because the entry point and its callees didn't use them, // then we must skip it. if !ep_info.dominates_global_use(info) { - log::info!("Skip function {:?}", ir_function.name); + log::debug!("Skip function {:?}", ir_function.name); continue; } diff --git a/third_party/rust/naga/src/back/wgsl/writer.rs b/third_party/rust/naga/src/back/wgsl/writer.rs @@ -33,6 +33,9 @@ enum Attribute { BlendSrc(u32), Stage(ShaderStage), WorkGroupSize([u32; 3]), + MeshStage(String), + TaskPayload(String), + PerPrimitive, } /// The WGSL form that `write_expr_with_indirection` should use to render a Naga @@ -135,12 +138,6 @@ impl<W: Write> Writer<W> { } pub fn write(&mut self, module: &Module, info: &valid::ModuleInfo) -> BackendResult { - if !module.overrides.is_empty() { - return Err(Error::Unimplemented( - "Pipeline constants are not yet supported for this back-end".to_string(), - )); - } - self.reset(module); // Write all `enable` declarations @@ -172,6 +169,16 @@ impl<W: Write> Writer<W> { } } + // Write all overrides + let mut overrides = module.overrides.iter().peekable(); + while let Some((handle, _)) = overrides.next() { + self.write_override(module, handle)?; + // Add extra newline for readability on last iteration + if overrides.peek().is_none() { + writeln!(self.out)?; + } + } + // Write all globals for (ty, global) in module.global_variables.iter() { self.write_global(module, global, ty)?; @@ -207,9 +214,37 @@ impl<W: Write> Writer<W> { Attribute::Stage(ShaderStage::Compute), Attribute::WorkGroupSize(ep.workgroup_size), ], - ShaderStage::Mesh | ShaderStage::Task => unreachable!(), + ShaderStage::Mesh => { + let mesh_output_name = module.global_variables + [ep.mesh_info.as_ref().unwrap().output_variable] + .name + .clone() + .unwrap(); + let mut mesh_attrs = vec![ + Attribute::MeshStage(mesh_output_name), + Attribute::WorkGroupSize(ep.workgroup_size), + ]; + if ep.task_payload.is_some() { + let payload_name = module.global_variables[ep.task_payload.unwrap()] + .name + .clone() + .unwrap(); + mesh_attrs.push(Attribute::TaskPayload(payload_name)); + } + mesh_attrs + } + ShaderStage::Task => { + let payload_name = module.global_variables[ep.task_payload.unwrap()] + .name + .clone() + .unwrap(); + vec![ + Attribute::Stage(ShaderStage::Task), + Attribute::TaskPayload(payload_name), + Attribute::WorkGroupSize(ep.workgroup_size), + ] + } }; - self.write_attributes(&attributes)?; // Add a newline after attribute writeln!(self.out)?; @@ -243,6 +278,7 @@ impl<W: Write> Writer<W> { let mut needs_f16 = false; let mut needs_dual_source_blending = false; let mut needs_clip_distances = false; + let mut needs_mesh_shaders = false; // Determine which `enable` declarations are needed for (_, ty) in module.types.iter() { @@ -263,6 +299,25 @@ impl<W: Write> Writer<W> { crate::Binding::BuiltIn(crate::BuiltIn::ClipDistance) => { needs_clip_distances = true; } + crate::Binding::Location { + per_primitive: true, + .. + } => { + needs_mesh_shaders = true; + } + crate::Binding::BuiltIn( + crate::BuiltIn::MeshTaskSize + | crate::BuiltIn::CullPrimitive + | crate::BuiltIn::PointIndex + | crate::BuiltIn::LineIndices + | crate::BuiltIn::TriangleIndices + | crate::BuiltIn::VertexCount + | crate::BuiltIn::Vertices + | crate::BuiltIn::PrimitiveCount + | crate::BuiltIn::Primitives, + ) => { + needs_mesh_shaders = true; + } _ => {} } } @@ -271,6 +326,22 @@ impl<W: Write> Writer<W> { } } + if module + .entry_points + .iter() + .any(|ep| matches!(ep.stage, ShaderStage::Mesh | ShaderStage::Task)) + { + needs_mesh_shaders = true; + } + + if module + .global_variables + .iter() + .any(|gv| gv.1.space == crate::AddressSpace::TaskPayload) + { + needs_mesh_shaders = true; + } + // Write required declarations let mut any_written = false; if needs_f16 { @@ -285,6 +356,10 @@ impl<W: Write> Writer<W> { writeln!(self.out, "enable clip_distances;")?; any_written = true; } + if needs_mesh_shaders { + writeln!(self.out, "enable wgpu_mesh_shader;")?; + any_written = true; + } if any_written { // Empty line for readability writeln!(self.out)?; @@ -403,8 +478,11 @@ impl<W: Write> Writer<W> { ShaderStage::Vertex => "vertex", ShaderStage::Fragment => "fragment", ShaderStage::Compute => "compute", - ShaderStage::Task | ShaderStage::Mesh => unreachable!(), + ShaderStage::Task => "task", + //Handled by another variant in the Attribute enum, so this code should never be hit. + ShaderStage::Mesh => unreachable!(), }; + write!(self.out, "@{stage_str} ")?; } Attribute::WorkGroupSize(size) => { @@ -433,6 +511,13 @@ impl<W: Write> Writer<W> { write!(self.out, "@interpolate({interpolation}) ")?; } } + Attribute::MeshStage(ref name) => { + write!(self.out, "@mesh({name}) ")?; + } + Attribute::TaskPayload(ref payload_name) => { + write!(self.out, "@payload({payload_name}) ")?; + } + Attribute::PerPrimitive => write!(self.out, "@per_primitive ")?, }; } Ok(()) @@ -1205,6 +1290,9 @@ impl<W: Write> Writer<W> { write_expression(self, value)?; write!(self.out, ")")?; } + Expression::Override(handle) => { + write!(self.out, "{}", self.names[&NameKey::Override(handle)])?; + } _ => unreachable!(), } @@ -1255,7 +1343,9 @@ impl<W: Write> Writer<W> { |writer, expr| writer.write_expr(module, expr, func_ctx), )?; } - Expression::Override(_) => unreachable!(), + Expression::Override(handle) => { + write!(self.out, "{}", self.names[&NameKey::Override(handle)])?; + } Expression::FunctionArgument(pos) => { let name_key = func_ctx.argument_key(pos); let name = &self.names[&name_key]; @@ -1770,6 +1860,38 @@ impl<W: Write> Writer<W> { Ok(()) } + /// Helper method used to write overrides + /// + /// # Notes + /// Ends in a newline + fn write_override( + &mut self, + module: &Module, + handle: Handle<crate::Override>, + ) -> BackendResult { + let override_ = &module.overrides[handle]; + let name = &self.names[&NameKey::Override(handle)]; + + // Write @id attribute if present + if let Some(id) = override_.id { + write!(self.out, "@id({id}) ")?; + } + + // Write override declaration + write!(self.out, "override {name}: ")?; + self.write_type(module, override_.ty)?; + + // Write initializer if present + if let Some(init) = override_.init { + write!(self.out, " = ")?; + self.write_const_expression(module, init, &module.global_expressions)?; + } + + writeln!(self.out, ";")?; + + Ok(()) + } + // See https://github.com/rust-lang/rust-clippy/issues/4979. #[allow(clippy::missing_const_for_fn)] pub fn finish(self) -> W { @@ -1795,8 +1917,12 @@ impl TypeContext for WriterTypeContext<'_> { unreachable!("the WGSL back end should always provide type handles"); } - fn write_override<W: Write>(&self, _: Handle<crate::Override>, _: &mut W) -> core::fmt::Result { - unreachable!("overrides should be validated out"); + fn write_override<W: Write>( + &self, + handle: Handle<crate::Override>, + out: &mut W, + ) -> core::fmt::Result { + write!(out, "{}", self.names[&NameKey::Override(handle)]) } fn write_non_wgsl_inner<W: Write>(&self, _: &TypeInner, _: &mut W) -> core::fmt::Result { @@ -1822,21 +1948,33 @@ fn map_binding_to_attribute(binding: &crate::Binding) -> Vec<Attribute> { interpolation, sampling, blend_src: None, - per_primitive: _, - } => vec![ - Attribute::Location(location), - Attribute::Interpolate(interpolation, sampling), - ], + per_primitive, + } => { + let mut attrs = vec![ + Attribute::Location(location), + Attribute::Interpolate(interpolation, sampling), + ]; + if per_primitive { + attrs.push(Attribute::PerPrimitive); + } + attrs + } crate::Binding::Location { location, interpolation, sampling, blend_src: Some(blend_src), - per_primitive: _, - } => vec![ - Attribute::Location(location), - Attribute::BlendSrc(blend_src), - Attribute::Interpolate(interpolation, sampling), - ], + per_primitive, + } => { + let mut attrs = vec![ + Attribute::Location(location), + Attribute::BlendSrc(blend_src), + Attribute::Interpolate(interpolation, sampling), + ]; + if per_primitive { + attrs.push(Attribute::PerPrimitive); + } + attrs + } } } diff --git a/third_party/rust/naga/src/common/mod.rs b/third_party/rust/naga/src/common/mod.rs @@ -8,11 +8,5 @@ pub mod wgsl; pub use diagnostic_debug::{DiagnosticDebug, ForDebug, ForDebugWithTypes}; pub use diagnostic_display::DiagnosticDisplay; -/// Helper function that returns the string corresponding to the [`VectorSize`](crate::VectorSize) -pub const fn vector_size_str(size: crate::VectorSize) -> &'static str { - match size { - crate::VectorSize::Bi => "2", - crate::VectorSize::Tri => "3", - crate::VectorSize::Quad => "4", - } -} +// Re-exported here for backwards compatibility +pub use super::proc::vector_size_str; diff --git a/third_party/rust/naga/src/common/wgsl/to_wgsl.rs b/third_party/rust/naga/src/common/wgsl/to_wgsl.rs @@ -183,22 +183,23 @@ impl TryToWgsl for crate::BuiltIn { Bi::SubgroupInvocationId => "subgroup_invocation_id", // Non-standard built-ins. + Bi::MeshTaskSize => "mesh_task_size", + Bi::TriangleIndices => "triangle_indices", + Bi::LineIndices => "line_indices", + Bi::PointIndex => "point_index", + Bi::Vertices => "vertices", + Bi::Primitives => "primitives", + Bi::VertexCount => "vertex_count", + Bi::PrimitiveCount => "primitive_count", + Bi::CullPrimitive => "cull_primitive", + Bi::BaseInstance | Bi::BaseVertex | Bi::CullDistance | Bi::PointSize | Bi::DrawID | Bi::PointCoord - | Bi::WorkGroupSize - | Bi::CullPrimitive - | Bi::TriangleIndices - | Bi::LineIndices - | Bi::MeshTaskSize - | Bi::PointIndex - | Bi::VertexCount - | Bi::PrimitiveCount - | Bi::Vertices - | Bi::Primitives => return None, + | Bi::WorkGroupSize => return None, }) } } @@ -358,11 +359,11 @@ pub const fn address_space_str( "storage" } } - As::PushConstant => "push_constant", + As::Immediate => "immediate", As::WorkGroup => "workgroup", As::Handle => return (None, None), As::Function => "function", - As::TaskPayload => return (None, None), + As::TaskPayload => "task_payload", }), None, ) diff --git a/third_party/rust/naga/src/front/glsl/ast.rs b/third_party/rust/naga/src/front/glsl/ast.rs @@ -4,13 +4,14 @@ use core::fmt; use super::{builtins::MacroCall, Span}; use crate::{ AddressSpace, BinaryOperator, Binding, Constant, Expression, Function, GlobalVariable, Handle, - Interpolation, Literal, Sampling, StorageAccess, Type, UnaryOperator, + Interpolation, Literal, Override, Sampling, StorageAccess, Type, UnaryOperator, }; #[derive(Debug, Clone, Copy)] pub enum GlobalLookupKind { Variable(Handle<GlobalVariable>), Constant(Handle<Constant>, Handle<Type>), + Override(Handle<Override>, Handle<Type>), BlockSelect(Handle<GlobalVariable>, u32), } diff --git a/third_party/rust/naga/src/front/glsl/builtins.rs b/third_party/rust/naga/src/front/glsl/builtins.rs @@ -1669,7 +1669,7 @@ impl MacroCall { num_args += 1; if shadow { - log::warn!("Assuming LOD {:?} is zero", args[2],); + log::debug!("Assuming LOD {:?} is zero", args[2],); SampleLevel::Zero } else { @@ -1681,7 +1681,7 @@ impl MacroCall { num_args += 2; if shadow { - log::warn!( + log::debug!( "Assuming gradients {:?} and {:?} are not greater than 1", args[2], args[3], diff --git a/third_party/rust/naga/src/front/glsl/context.rs b/third_party/rust/naga/src/front/glsl/context.rs @@ -211,6 +211,14 @@ impl<'a> Context<'a> { Some((v, ty)), ) } + GlobalLookupKind::Override(v, _ty) => { + let span = self.module.overrides.get_span(v); + ( + self.add_expression(Expression::Override(v), span)?, + false, + None, + ) + } }; let var = VariableReference { @@ -1037,7 +1045,17 @@ impl<'a> Context<'a> { if let Some((constant, _)) = self.is_const.then_some(var.constant).flatten() { self.add_expression(Expression::Constant(constant), meta)? } else { - var.expr + // Check if this is an Override expression in const context + if self.is_const { + if let Expression::Override(o) = self.expressions[var.expr] { + // Need to add the Override expression to the global arena + self.add_expression(Expression::Override(o), meta)? + } else { + var.expr + } + } else { + var.expr + } } } }, diff --git a/third_party/rust/naga/src/front/glsl/parser.rs b/third_party/rust/naga/src/front/glsl/parser.rs @@ -436,6 +436,7 @@ impl DeclarationContext<'_, '_, '_> { let expr = match global { GlobalOrConstant::Global(handle) => Expression::GlobalVariable(handle), GlobalOrConstant::Constant(handle) => Expression::Constant(handle), + GlobalOrConstant::Override(handle) => Expression::Override(handle), }; Ok(self.ctx.add_expression(expr, meta)?) } diff --git a/third_party/rust/naga/src/front/glsl/parser/declarations.rs b/third_party/rust/naga/src/front/glsl/parser/declarations.rs @@ -608,6 +608,7 @@ impl ParsingContext<'_> { kind: match global { GlobalOrConstant::Global(handle) => GlobalLookupKind::BlockSelect(handle, i), GlobalOrConstant::Constant(handle) => GlobalLookupKind::Constant(handle, ty), + GlobalOrConstant::Override(handle) => GlobalLookupKind::Override(handle, ty), }, entry_arg: None, mutable: true, diff --git a/third_party/rust/naga/src/front/glsl/variables.rs b/third_party/rust/naga/src/front/glsl/variables.rs @@ -8,8 +8,8 @@ use super::{ }; use crate::{ AddressSpace, Binding, BuiltIn, Constant, Expression, GlobalVariable, Handle, Interpolation, - LocalVariable, ResourceBinding, Scalar, ScalarKind, ShaderStage, SwizzleComponent, Type, - TypeInner, VectorSize, + LocalVariable, Override, ResourceBinding, Scalar, ScalarKind, ShaderStage, SwizzleComponent, + Type, TypeInner, VectorSize, }; pub struct VarDeclaration<'a, 'key> { @@ -35,6 +35,7 @@ struct BuiltInData { pub enum GlobalOrConstant { Global(Handle<GlobalVariable>), Constant(Handle<Constant>), + Override(Handle<Override>), } impl Frontend { @@ -481,25 +482,69 @@ impl Frontend { (GlobalOrConstant::Global(handle), lookup) } StorageQualifier::Const => { - let init = init.ok_or_else(|| Error { - kind: ErrorKind::SemanticError("const values must have an initializer".into()), - meta, - })?; + // Check if this is a specialization constant with constant_id + let constant_id = qualifiers.uint_layout_qualifier("constant_id", &mut self.errors); + + if let Some(id) = constant_id { + // This is a specialization constant - convert to Override + let id: Option<u16> = match id.try_into() { + Ok(v) => Some(v), + Err(_) => { + self.errors.push(Error { + kind: ErrorKind::SemanticError( + format!( + "constant_id value {id} is too high (maximum is {})", + u16::MAX + ) + .into(), + ), + meta, + }); + None + } + }; - let constant = Constant { - name: name.clone(), - ty, - init, - }; - let handle = ctx.module.constants.append(constant, meta); + let override_handle = ctx.module.overrides.append( + Override { + name: name.clone(), + id, + ty, + init, + }, + meta, + ); - let lookup = GlobalLookup { - kind: GlobalLookupKind::Constant(handle, ty), - entry_arg: None, - mutable: false, - }; + let lookup = GlobalLookup { + kind: GlobalLookupKind::Override(override_handle, ty), + entry_arg: None, + mutable: false, + }; + + (GlobalOrConstant::Override(override_handle), lookup) + } else { + // Regular constant + let init = init.ok_or_else(|| Error { + kind: ErrorKind::SemanticError( + "const values must have an initializer".into(), + ), + meta, + })?; - (GlobalOrConstant::Constant(handle), lookup) + let constant = Constant { + name: name.clone(), + ty, + init, + }; + let handle = ctx.module.constants.append(constant, meta); + + let lookup = GlobalLookup { + kind: GlobalLookupKind::Constant(handle, ty), + entry_arg: None, + mutable: false, + }; + + (GlobalOrConstant::Constant(handle), lookup) + } } StorageQualifier::AddressSpace(mut space) => { match space { @@ -555,7 +600,7 @@ impl Frontend { TypeInner::Sampler { .. } => space = AddressSpace::Handle, _ => { if qualifiers.none_layout_qualifier("push_constant", &mut self.errors) { - space = AddressSpace::PushConstant + space = AddressSpace::Immediate } } }, diff --git a/third_party/rust/naga/src/front/spv/convert.rs b/third_party/rust/naga/src/front/spv/convert.rs @@ -182,7 +182,7 @@ pub(super) fn map_storage_class(word: spirv::Word) -> Result<super::ExtendedClas // we expect the `Storage` case to be filtered out before calling this function. Some(Sc::Uniform) => Ec::Global(crate::AddressSpace::Uniform), Some(Sc::Workgroup) => Ec::Global(crate::AddressSpace::WorkGroup), - Some(Sc::PushConstant) => Ec::Global(crate::AddressSpace::PushConstant), + Some(Sc::PushConstant) => Ec::Global(crate::AddressSpace::Immediate), _ => return Err(Error::UnsupportedStorageClass(word)), }) } diff --git a/third_party/rust/naga/src/front/spv/mod.rs b/third_party/rust/naga/src/front/spv/mod.rs @@ -31,11 +31,12 @@ mod convert; mod error; mod function; mod image; +mod next_block; mod null; pub use error::Error; -use alloc::{borrow::ToOwned, format, string::String, vec, vec::Vec}; +use alloc::{borrow::ToOwned, string::String, vec, vec::Vec}; use core::{convert::TryInto, mem, num::NonZeroU32}; use half::f16; @@ -799,7 +800,15 @@ impl<I: Iterator<Item = u32>> Frontend<I> { dec.specialization_constant_id = Some(self.next()?); } other => { - log::warn!("Unknown decoration {other:?}"); + let level = match other { + // Block decorations show up everywhere and we don't + // really care about them, so to prevent log spam + // we demote them to debug level. + spirv::Decoration::Block => log::Level::Debug, + _ => log::Level::Warn, + }; + + log::log!(level, "Unknown decoration {other:?}"); for _ in base_words + 1..inst.wc { let _var = self.next()?; } @@ -1471,3086 +1480,6 @@ impl<I: Iterator<Item = u32>> Frontend<I> { Ok(()) } - /// Add the next SPIR-V block's contents to `block_ctx`. - /// - /// Except for the function's entry block, `block_id` should be the label of - /// a block we've seen mentioned before, with an entry in - /// `block_ctx.body_for_label` to tell us which `Body` it contributes to. - fn next_block(&mut self, block_id: spirv::Word, ctx: &mut BlockContext) -> Result<(), Error> { - // Extend `body` with the correct form for a branch to `target`. - fn merger(body: &mut Body, target: &MergeBlockInformation) { - body.data.push(match *target { - MergeBlockInformation::LoopContinue => BodyFragment::Continue, - MergeBlockInformation::LoopMerge | MergeBlockInformation::SwitchMerge => { - BodyFragment::Break - } - - // Finishing a selection merge means just falling off the end of - // the `accept` or `reject` block of the `If` statement. - MergeBlockInformation::SelectionMerge => return, - }) - } - - let mut emitter = crate::proc::Emitter::default(); - emitter.start(ctx.expressions); - - // Find the `Body` to which this block contributes. - // - // If this is some SPIR-V structured control flow construct's merge - // block, then `body_idx` will refer to the same `Body` as the header, - // so that we simply pick up accumulating the `Body` where the header - // left off. Each of the statements in a block dominates the next, so - // we're sure to encounter their SPIR-V blocks in order, ensuring that - // the `Body` will be assembled in the proper order. - // - // Note that, unlike every other kind of SPIR-V block, we don't know the - // function's first block's label in advance. Thus, we assume that if - // this block has no entry in `ctx.body_for_label`, it must be the - // function's first block. This always has body index zero. - let mut body_idx = *ctx.body_for_label.entry(block_id).or_default(); - - // The Naga IR block this call builds. This will end up as - // `ctx.blocks[&block_id]`, and `ctx.bodies[body_idx]` will refer to it - // via a `BodyFragment::BlockId`. - let mut block = crate::Block::new(); - - // Stores the merge block as defined by a `OpSelectionMerge` otherwise is `None` - // - // This is used in `OpSwitch` to promote the `MergeBlockInformation` from - // `SelectionMerge` to `SwitchMerge` to allow `Break`s this isn't desirable for - // `LoopMerge`s because otherwise `Continue`s wouldn't be allowed - let mut selection_merge_block = None; - - macro_rules! get_expr_handle { - ($id:expr, $lexp:expr) => { - self.get_expr_handle($id, $lexp, ctx, &mut emitter, &mut block, body_idx) - }; - } - macro_rules! parse_expr_op { - ($op:expr, BINARY) => { - self.parse_expr_binary_op(ctx, &mut emitter, &mut block, block_id, body_idx, $op) - }; - - ($op:expr, SHIFT) => { - self.parse_expr_shift_op(ctx, &mut emitter, &mut block, block_id, body_idx, $op) - }; - ($op:expr, UNARY) => { - self.parse_expr_unary_op(ctx, &mut emitter, &mut block, block_id, body_idx, $op) - }; - ($axis:expr, $ctrl:expr, DERIVATIVE) => { - self.parse_expr_derivative( - ctx, - &mut emitter, - &mut block, - block_id, - body_idx, - ($axis, $ctrl), - ) - }; - } - - let terminator = loop { - use spirv::Op; - let start = self.data_offset; - let inst = self.next_inst()?; - let span = crate::Span::from(start..(start + 4 * (inst.wc as usize))); - log::debug!("\t\t{:?} [{}]", inst.op, inst.wc); - - match inst.op { - Op::Line => { - inst.expect(4)?; - let _file_id = self.next()?; - let _row_id = self.next()?; - let _col_id = self.next()?; - } - Op::NoLine => inst.expect(1)?, - Op::Undef => { - inst.expect(3)?; - let type_id = self.next()?; - let id = self.next()?; - let type_lookup = self.lookup_type.lookup(type_id)?; - let ty = type_lookup.handle; - - self.lookup_expression.insert( - id, - LookupExpression { - handle: ctx - .expressions - .append(crate::Expression::ZeroValue(ty), span), - type_id, - block_id, - }, - ); - } - Op::Variable => { - inst.expect_at_least(4)?; - block.extend(emitter.finish(ctx.expressions)); - - let result_type_id = self.next()?; - let result_id = self.next()?; - let _storage_class = self.next()?; - let init = if inst.wc > 4 { - inst.expect(5)?; - let init_id = self.next()?; - let lconst = self.lookup_constant.lookup(init_id)?; - Some(ctx.expressions.append(lconst.inner.to_expr(), span)) - } else { - None - }; - - let name = self - .future_decor - .remove(&result_id) - .and_then(|decor| decor.name); - if let Some(ref name) = name { - log::debug!("\t\t\tid={result_id} name={name}"); - } - let lookup_ty = self.lookup_type.lookup(result_type_id)?; - let var_handle = ctx.local_arena.append( - crate::LocalVariable { - name, - ty: match ctx.module.types[lookup_ty.handle].inner { - crate::TypeInner::Pointer { base, .. } => base, - _ => lookup_ty.handle, - }, - init, - }, - span, - ); - - self.lookup_expression.insert( - result_id, - LookupExpression { - handle: ctx - .expressions - .append(crate::Expression::LocalVariable(var_handle), span), - type_id: result_type_id, - block_id, - }, - ); - emitter.start(ctx.expressions); - } - Op::Phi => { - inst.expect_at_least(3)?; - block.extend(emitter.finish(ctx.expressions)); - - let result_type_id = self.next()?; - let result_id = self.next()?; - - let name = format!("phi_{result_id}"); - let local = ctx.local_arena.append( - crate::LocalVariable { - name: Some(name), - ty: self.lookup_type.lookup(result_type_id)?.handle, - init: None, - }, - self.span_from(start), - ); - let pointer = ctx - .expressions - .append(crate::Expression::LocalVariable(local), span); - - let in_count = (inst.wc - 3) / 2; - let mut phi = PhiExpression { - local, - expressions: Vec::with_capacity(in_count as usize), - }; - for _ in 0..in_count { - let expr = self.next()?; - let block = self.next()?; - phi.expressions.push((expr, block)); - } - - ctx.phis.push(phi); - emitter.start(ctx.expressions); - - // Associate the lookup with an actual value, which is emitted - // into the current block. - self.lookup_expression.insert( - result_id, - LookupExpression { - handle: ctx - .expressions - .append(crate::Expression::Load { pointer }, span), - type_id: result_type_id, - block_id, - }, - ); - } - Op::AccessChain | Op::InBoundsAccessChain => { - struct AccessExpression { - base_handle: Handle<crate::Expression>, - type_id: spirv::Word, - load_override: Option<LookupLoadOverride>, - } - - inst.expect_at_least(4)?; - - let result_type_id = self.next()?; - let result_id = self.next()?; - let base_id = self.next()?; - log::trace!("\t\t\tlooking up expr {base_id:?}"); - - let mut acex = { - let lexp = self.lookup_expression.lookup(base_id)?; - let lty = self.lookup_type.lookup(lexp.type_id)?; - - // HACK `OpAccessChain` and `OpInBoundsAccessChain` - // require for the result type to be a pointer, but if - // we're given a pointer to an image / sampler, it will - // be *already* dereferenced, since we do that early - // during `parse_type_pointer()`. - // - // This can happen only through `BindingArray`, since - // that's the only case where one can obtain a pointer - // to an image / sampler, and so let's match on that: - let dereference = match ctx.module.types[lty.handle].inner { - crate::TypeInner::BindingArray { .. } => false, - _ => true, - }; - - let type_id = if dereference { - lty.base_id.ok_or(Error::InvalidAccessType(lexp.type_id))? - } else { - lexp.type_id - }; - - AccessExpression { - base_handle: get_expr_handle!(base_id, lexp), - type_id, - load_override: self.lookup_load_override.get(&base_id).cloned(), - } - }; - - for _ in 4..inst.wc { - let access_id = self.next()?; - log::trace!("\t\t\tlooking up index expr {access_id:?}"); - let index_expr = self.lookup_expression.lookup(access_id)?.clone(); - let index_expr_handle = get_expr_handle!(access_id, &index_expr); - let index_expr_data = &ctx.expressions[index_expr.handle]; - let index_maybe = match *index_expr_data { - crate::Expression::Constant(const_handle) => Some( - ctx.gctx() - .eval_expr_to_u32(ctx.module.constants[const_handle].init) - .map_err(|_| { - Error::InvalidAccess(crate::Expression::Constant( - const_handle, - )) - })?, - ), - _ => None, - }; - - log::trace!("\t\t\tlooking up type {:?}", acex.type_id); - let type_lookup = self.lookup_type.lookup(acex.type_id)?; - let ty = &ctx.module.types[type_lookup.handle]; - acex = match ty.inner { - // can only index a struct with a constant - crate::TypeInner::Struct { ref members, .. } => { - let index = index_maybe - .ok_or_else(|| Error::InvalidAccess(index_expr_data.clone()))?; - - let lookup_member = self - .lookup_member - .get(&(type_lookup.handle, index)) - .ok_or(Error::InvalidAccessType(acex.type_id))?; - let base_handle = ctx.expressions.append( - crate::Expression::AccessIndex { - base: acex.base_handle, - index, - }, - span, - ); - - if let Some(crate::Binding::BuiltIn(built_in)) = - members[index as usize].binding - { - self.gl_per_vertex_builtin_access.insert(built_in); - } - - AccessExpression { - base_handle, - type_id: lookup_member.type_id, - load_override: if lookup_member.row_major { - debug_assert!(acex.load_override.is_none()); - let sub_type_lookup = - self.lookup_type.lookup(lookup_member.type_id)?; - Some(match ctx.module.types[sub_type_lookup.handle].inner { - // load it transposed, to match column major expectations - crate::TypeInner::Matrix { .. } => { - let loaded = ctx.expressions.append( - crate::Expression::Load { - pointer: base_handle, - }, - span, - ); - let transposed = ctx.expressions.append( - crate::Expression::Math { - fun: crate::MathFunction::Transpose, - arg: loaded, - arg1: None, - arg2: None, - arg3: None, - }, - span, - ); - LookupLoadOverride::Loaded(transposed) - } - _ => LookupLoadOverride::Pending, - }) - } else { - None - }, - } - } - crate::TypeInner::Matrix { .. } => { - let load_override = match acex.load_override { - // We are indexing inside a row-major matrix - Some(LookupLoadOverride::Loaded(load_expr)) => { - let index = index_maybe.ok_or_else(|| { - Error::InvalidAccess(index_expr_data.clone()) - })?; - let sub_handle = ctx.expressions.append( - crate::Expression::AccessIndex { - base: load_expr, - index, - }, - span, - ); - Some(LookupLoadOverride::Loaded(sub_handle)) - } - _ => None, - }; - let sub_expr = match index_maybe { - Some(index) => crate::Expression::AccessIndex { - base: acex.base_handle, - index, - }, - None => crate::Expression::Access { - base: acex.base_handle, - index: index_expr_handle, - }, - }; - AccessExpression { - base_handle: ctx.expressions.append(sub_expr, span), - type_id: type_lookup - .base_id - .ok_or(Error::InvalidAccessType(acex.type_id))?, - load_override, - } - } - // This must be a vector or an array. - _ => { - let base_handle = ctx.expressions.append( - crate::Expression::Access { - base: acex.base_handle, - index: index_expr_handle, - }, - span, - ); - let load_override = match acex.load_override { - // If there is a load override in place, then we always end up - // with a side-loaded value here. - Some(lookup_load_override) => { - let sub_expr = match lookup_load_override { - // We must be indexing into the array of row-major matrices. - // Let's load the result of indexing and transpose it. - LookupLoadOverride::Pending => { - let loaded = ctx.expressions.append( - crate::Expression::Load { - pointer: base_handle, - }, - span, - ); - ctx.expressions.append( - crate::Expression::Math { - fun: crate::MathFunction::Transpose, - arg: loaded, - arg1: None, - arg2: None, - arg3: None, - }, - span, - ) - } - // We are indexing inside a row-major matrix. - LookupLoadOverride::Loaded(load_expr) => { - ctx.expressions.append( - crate::Expression::Access { - base: load_expr, - index: index_expr_handle, - }, - span, - ) - } - }; - Some(LookupLoadOverride::Loaded(sub_expr)) - } - None => None, - }; - AccessExpression { - base_handle, - type_id: type_lookup - .base_id - .ok_or(Error::InvalidAccessType(acex.type_id))?, - load_override, - } - } - }; - } - - if let Some(load_expr) = acex.load_override { - self.lookup_load_override.insert(result_id, load_expr); - } - let lookup_expression = LookupExpression { - handle: acex.base_handle, - type_id: result_type_id, - block_id, - }; - self.lookup_expression.insert(result_id, lookup_expression); - } - Op::VectorExtractDynamic => { - inst.expect(5)?; - - let result_type_id = self.next()?; - let id = self.next()?; - let composite_id = self.next()?; - let index_id = self.next()?; - - let root_lexp = self.lookup_expression.lookup(composite_id)?; - let root_handle = get_expr_handle!(composite_id, root_lexp); - let root_type_lookup = self.lookup_type.lookup(root_lexp.type_id)?; - let index_lexp = self.lookup_expression.lookup(index_id)?; - let index_handle = get_expr_handle!(index_id, index_lexp); - let index_type = self.lookup_type.lookup(index_lexp.type_id)?.handle; - - let num_components = match ctx.module.types[root_type_lookup.handle].inner { - crate::TypeInner::Vector { size, .. } => size as u32, - _ => return Err(Error::InvalidVectorType(root_type_lookup.handle)), - }; - - let mut make_index = |ctx: &mut BlockContext, index: u32| { - make_index_literal( - ctx, - index, - &mut block, - &mut emitter, - index_type, - index_lexp.type_id, - span, - ) - }; - - let index_expr = make_index(ctx, 0)?; - let mut handle = ctx.expressions.append( - crate::Expression::Access { - base: root_handle, - index: index_expr, - }, - span, - ); - for index in 1..num_components { - let index_expr = make_index(ctx, index)?; - let access_expr = ctx.expressions.append( - crate::Expression::Access { - base: root_handle, - index: index_expr, - }, - span, - ); - let cond = ctx.expressions.append( - crate::Expression::Binary { - op: crate::BinaryOperator::Equal, - left: index_expr, - right: index_handle, - }, - span, - ); - handle = ctx.expressions.append( - crate::Expression::Select { - condition: cond, - accept: access_expr, - reject: handle, - }, - span, - ); - } - - self.lookup_expression.insert( - id, - LookupExpression { - handle, - type_id: result_type_id, - block_id, - }, - ); - } - Op::VectorInsertDynamic => { - inst.expect(6)?; - - let result_type_id = self.next()?; - let id = self.next()?; - let composite_id = self.next()?; - let object_id = self.next()?; - let index_id = self.next()?; - - let object_lexp = self.lookup_expression.lookup(object_id)?; - let object_handle = get_expr_handle!(object_id, object_lexp); - let root_lexp = self.lookup_expression.lookup(composite_id)?; - let root_handle = get_expr_handle!(composite_id, root_lexp); - let root_type_lookup = self.lookup_type.lookup(root_lexp.type_id)?; - let index_lexp = self.lookup_expression.lookup(index_id)?; - let index_handle = get_expr_handle!(index_id, index_lexp); - let index_type = self.lookup_type.lookup(index_lexp.type_id)?.handle; - - let num_components = match ctx.module.types[root_type_lookup.handle].inner { - crate::TypeInner::Vector { size, .. } => size as u32, - _ => return Err(Error::InvalidVectorType(root_type_lookup.handle)), - }; - - let mut components = Vec::with_capacity(num_components as usize); - for index in 0..num_components { - let index_expr = make_index_literal( - ctx, - index, - &mut block, - &mut emitter, - index_type, - index_lexp.type_id, - span, - )?; - let access_expr = ctx.expressions.append( - crate::Expression::Access { - base: root_handle, - index: index_expr, - }, - span, - ); - let cond = ctx.expressions.append( - crate::Expression::Binary { - op: crate::BinaryOperator::Equal, - left: index_expr, - right: index_handle, - }, - span, - ); - let handle = ctx.expressions.append( - crate::Expression::Select { - condition: cond, - accept: object_handle, - reject: access_expr, - }, - span, - ); - components.push(handle); - } - let handle = ctx.expressions.append( - crate::Expression::Compose { - ty: root_type_lookup.handle, - components, - }, - span, - ); - - self.lookup_expression.insert( - id, - LookupExpression { - handle, - type_id: result_type_id, - block_id, - }, - ); - } - Op::CompositeExtract => { - inst.expect_at_least(4)?; - - let result_type_id = self.next()?; - let result_id = self.next()?; - let base_id = self.next()?; - log::trace!("\t\t\tlooking up expr {base_id:?}"); - let mut lexp = self.lookup_expression.lookup(base_id)?.clone(); - lexp.handle = get_expr_handle!(base_id, &lexp); - for _ in 4..inst.wc { - let index = self.next()?; - log::trace!("\t\t\tlooking up type {:?}", lexp.type_id); - let type_lookup = self.lookup_type.lookup(lexp.type_id)?; - let type_id = match ctx.module.types[type_lookup.handle].inner { - crate::TypeInner::Struct { .. } => { - self.lookup_member - .get(&(type_lookup.handle, index)) - .ok_or(Error::InvalidAccessType(lexp.type_id))? - .type_id - } - crate::TypeInner::Array { .. } - | crate::TypeInner::Vector { .. } - | crate::TypeInner::Matrix { .. } => type_lookup - .base_id - .ok_or(Error::InvalidAccessType(lexp.type_id))?, - ref other => { - log::warn!("composite type {other:?}"); - return Err(Error::UnsupportedType(type_lookup.handle)); - } - }; - lexp = LookupExpression { - handle: ctx.expressions.append( - crate::Expression::AccessIndex { - base: lexp.handle, - index, - }, - span, - ), - type_id, - block_id, - }; - } - - self.lookup_expression.insert( - result_id, - LookupExpression { - handle: lexp.handle, - type_id: result_type_id, - block_id, - }, - ); - } - Op::CompositeInsert => { - inst.expect_at_least(5)?; - - let result_type_id = self.next()?; - let id = self.next()?; - let object_id = self.next()?; - let composite_id = self.next()?; - let mut selections = Vec::with_capacity(inst.wc as usize - 5); - for _ in 5..inst.wc { - selections.push(self.next()?); - } - - let object_lexp = self.lookup_expression.lookup(object_id)?.clone(); - let object_handle = get_expr_handle!(object_id, &object_lexp); - let root_lexp = self.lookup_expression.lookup(composite_id)?.clone(); - let root_handle = get_expr_handle!(composite_id, &root_lexp); - let handle = self.insert_composite( - root_handle, - result_type_id, - object_handle, - &selections, - &ctx.module.types, - ctx.expressions, - span, - )?; - - self.lookup_expression.insert( - id, - LookupExpression { - handle, - type_id: result_type_id, - block_id, - }, - ); - } - Op::CompositeConstruct => { - inst.expect_at_least(3)?; - - let result_type_id = self.next()?; - let id = self.next()?; - let mut components = Vec::with_capacity(inst.wc as usize - 2); - for _ in 3..inst.wc { - let comp_id = self.next()?; - log::trace!("\t\t\tlooking up expr {comp_id:?}"); - let lexp = self.lookup_expression.lookup(comp_id)?; - let handle = get_expr_handle!(comp_id, lexp); - components.push(handle); - } - let ty = self.lookup_type.lookup(result_type_id)?.handle; - let first = components[0]; - let expr = match ctx.module.types[ty].inner { - // this is an optimization to detect the splat - crate::TypeInner::Vector { size, .. } - if components.len() == size as usize - && components[1..].iter().all(|&c| c == first) => - { - crate::Expression::Splat { size, value: first } - } - _ => crate::Expression::Compose { ty, components }, - }; - self.lookup_expression.insert( - id, - LookupExpression { - handle: ctx.expressions.append(expr, span), - type_id: result_type_id, - block_id, - }, - ); - } - Op::Load => { - inst.expect_at_least(4)?; - - let result_type_id = self.next()?; - let result_id = self.next()?; - let pointer_id = self.next()?; - if inst.wc != 4 { - inst.expect(5)?; - let _memory_access = self.next()?; - } - - let base_lexp = self.lookup_expression.lookup(pointer_id)?; - let base_handle = get_expr_handle!(pointer_id, base_lexp); - let type_lookup = self.lookup_type.lookup(base_lexp.type_id)?; - let handle = match ctx.module.types[type_lookup.handle].inner { - crate::TypeInner::Image { .. } | crate::TypeInner::Sampler { .. } => { - base_handle - } - _ => match self.lookup_load_override.get(&pointer_id) { - Some(&LookupLoadOverride::Loaded(handle)) => handle, - //Note: we aren't handling `LookupLoadOverride::Pending` properly here - _ => ctx.expressions.append( - crate::Expression::Load { - pointer: base_handle, - }, - span, - ), - }, - }; - - self.lookup_expression.insert( - result_id, - LookupExpression { - handle, - type_id: result_type_id, - block_id, - }, - ); - } - Op::Store => { - inst.expect_at_least(3)?; - - let pointer_id = self.next()?; - let value_id = self.next()?; - if inst.wc != 3 { - inst.expect(4)?; - let _memory_access = self.next()?; - } - let base_expr = self.lookup_expression.lookup(pointer_id)?; - let base_handle = get_expr_handle!(pointer_id, base_expr); - let value_expr = self.lookup_expression.lookup(value_id)?; - let value_handle = get_expr_handle!(value_id, value_expr); - - block.extend(emitter.finish(ctx.expressions)); - block.push( - crate::Statement::Store { - pointer: base_handle, - value: value_handle, - }, - span, - ); - emitter.start(ctx.expressions); - } - // Arithmetic Instructions +, -, *, /, % - Op::SNegate | Op::FNegate => { - inst.expect(4)?; - self.parse_expr_unary_op_sign_adjusted( - ctx, - &mut emitter, - &mut block, - block_id, - body_idx, - crate::UnaryOperator::Negate, - )?; - } - Op::IAdd - | Op::ISub - | Op::IMul - | Op::BitwiseOr - | Op::BitwiseXor - | Op::BitwiseAnd - | Op::SDiv - | Op::SRem => { - inst.expect(5)?; - let operator = map_binary_operator(inst.op)?; - self.parse_expr_binary_op_sign_adjusted( - ctx, - &mut emitter, - &mut block, - block_id, - body_idx, - operator, - SignAnchor::Result, - )?; - } - Op::IEqual | Op::INotEqual => { - inst.expect(5)?; - let operator = map_binary_operator(inst.op)?; - self.parse_expr_binary_op_sign_adjusted( - ctx, - &mut emitter, - &mut block, - block_id, - body_idx, - operator, - SignAnchor::Operand, - )?; - } - Op::FAdd => { - inst.expect(5)?; - parse_expr_op!(crate::BinaryOperator::Add, BINARY)?; - } - Op::FSub => { - inst.expect(5)?; - parse_expr_op!(crate::BinaryOperator::Subtract, BINARY)?; - } - Op::FMul => { - inst.expect(5)?; - parse_expr_op!(crate::BinaryOperator::Multiply, BINARY)?; - } - Op::UDiv | Op::FDiv => { - inst.expect(5)?; - parse_expr_op!(crate::BinaryOperator::Divide, BINARY)?; - } - Op::UMod | Op::FRem => { - inst.expect(5)?; - parse_expr_op!(crate::BinaryOperator::Modulo, BINARY)?; - } - Op::SMod => { - inst.expect(5)?; - - // x - y * int(floor(float(x) / float(y))) - - let start = self.data_offset; - let result_type_id = self.next()?; - let result_id = self.next()?; - let p1_id = self.next()?; - let p2_id = self.next()?; - let span = self.span_from_with_op(start); - - let p1_lexp = self.lookup_expression.lookup(p1_id)?; - let left = self.get_expr_handle( - p1_id, - p1_lexp, - ctx, - &mut emitter, - &mut block, - body_idx, - ); - let p2_lexp = self.lookup_expression.lookup(p2_id)?; - let right = self.get_expr_handle( - p2_id, - p2_lexp, - ctx, - &mut emitter, - &mut block, - body_idx, - ); - - let result_ty = self.lookup_type.lookup(result_type_id)?; - let inner = &ctx.module.types[result_ty.handle].inner; - let kind = inner.scalar_kind().unwrap(); - let size = inner.size(ctx.gctx()) as u8; - - let left_cast = ctx.expressions.append( - crate::Expression::As { - expr: left, - kind: crate::ScalarKind::Float, - convert: Some(size), - }, - span, - ); - let right_cast = ctx.expressions.append( - crate::Expression::As { - expr: right, - kind: crate::ScalarKind::Float, - convert: Some(size), - }, - span, - ); - let div = ctx.expressions.append( - crate::Expression::Binary { - op: crate::BinaryOperator::Divide, - left: left_cast, - right: right_cast, - }, - span, - ); - let floor = ctx.expressions.append( - crate::Expression::Math { - fun: crate::MathFunction::Floor, - arg: div, - arg1: None, - arg2: None, - arg3: None, - }, - span, - ); - let cast = ctx.expressions.append( - crate::Expression::As { - expr: floor, - kind, - convert: Some(size), - }, - span, - ); - let mult = ctx.expressions.append( - crate::Expression::Binary { - op: crate::BinaryOperator::Multiply, - left: cast, - right, - }, - span, - ); - let sub = ctx.expressions.append( - crate::Expression::Binary { - op: crate::BinaryOperator::Subtract, - left, - right: mult, - }, - span, - ); - self.lookup_expression.insert( - result_id, - LookupExpression { - handle: sub, - type_id: result_type_id, - block_id, - }, - ); - } - Op::FMod => { - inst.expect(5)?; - - // x - y * floor(x / y) - - let start = self.data_offset; - let span = self.span_from_with_op(start); - - let result_type_id = self.next()?; - let result_id = self.next()?; - let p1_id = self.next()?; - let p2_id = self.next()?; - - let p1_lexp = self.lookup_expression.lookup(p1_id)?; - let left = self.get_expr_handle( - p1_id, - p1_lexp, - ctx, - &mut emitter, - &mut block, - body_idx, - ); - let p2_lexp = self.lookup_expression.lookup(p2_id)?; - let right = self.get_expr_handle( - p2_id, - p2_lexp, - ctx, - &mut emitter, - &mut block, - body_idx, - ); - - let div = ctx.expressions.append( - crate::Expression::Binary { - op: crate::BinaryOperator::Divide, - left, - right, - }, - span, - ); - let floor = ctx.expressions.append( - crate::Expression::Math { - fun: crate::MathFunction::Floor, - arg: div, - arg1: None, - arg2: None, - arg3: None, - }, - span, - ); - let mult = ctx.expressions.append( - crate::Expression::Binary { - op: crate::BinaryOperator::Multiply, - left: floor, - right, - }, - span, - ); - let sub = ctx.expressions.append( - crate::Expression::Binary { - op: crate::BinaryOperator::Subtract, - left, - right: mult, - }, - span, - ); - self.lookup_expression.insert( - result_id, - LookupExpression { - handle: sub, - type_id: result_type_id, - block_id, - }, - ); - } - Op::VectorTimesScalar - | Op::VectorTimesMatrix - | Op::MatrixTimesScalar - | Op::MatrixTimesVector - | Op::MatrixTimesMatrix => { - inst.expect(5)?; - parse_expr_op!(crate::BinaryOperator::Multiply, BINARY)?; - } - Op::Transpose => { - inst.expect(4)?; - - let result_type_id = self.next()?; - let result_id = self.next()?; - let matrix_id = self.next()?; - let matrix_lexp = self.lookup_expression.lookup(matrix_id)?; - let matrix_handle = get_expr_handle!(matrix_id, matrix_lexp); - let expr = crate::Expression::Math { - fun: crate::MathFunction::Transpose, - arg: matrix_handle, - arg1: None, - arg2: None, - arg3: None, - }; - self.lookup_expression.insert( - result_id, - LookupExpression { - handle: ctx.expressions.append(expr, span), - type_id: result_type_id, - block_id, - }, - ); - } - Op::Dot => { - inst.expect(5)?; - - let result_type_id = self.next()?; - let result_id = self.next()?; - let left_id = self.next()?; - let right_id = self.next()?; - let left_lexp = self.lookup_expression.lookup(left_id)?; - let left_handle = get_expr_handle!(left_id, left_lexp); - let right_lexp = self.lookup_expression.lookup(right_id)?; - let right_handle = get_expr_handle!(right_id, right_lexp); - let expr = crate::Expression::Math { - fun: crate::MathFunction::Dot, - arg: left_handle, - arg1: Some(right_handle), - arg2: None, - arg3: None, - }; - self.lookup_expression.insert( - result_id, - LookupExpression { - handle: ctx.expressions.append(expr, span), - type_id: result_type_id, - block_id, - }, - ); - } - Op::BitFieldInsert => { - inst.expect(7)?; - - let start = self.data_offset; - let span = self.span_from_with_op(start); - - let result_type_id = self.next()?; - let result_id = self.next()?; - let base_id = self.next()?; - let insert_id = self.next()?; - let offset_id = self.next()?; - let count_id = self.next()?; - let base_lexp = self.lookup_expression.lookup(base_id)?; - let base_handle = get_expr_handle!(base_id, base_lexp); - let insert_lexp = self.lookup_expression.lookup(insert_id)?; - let insert_handle = get_expr_handle!(insert_id, insert_lexp); - let offset_lexp = self.lookup_expression.lookup(offset_id)?; - let offset_handle = get_expr_handle!(offset_id, offset_lexp); - let offset_lookup_ty = self.lookup_type.lookup(offset_lexp.type_id)?; - let count_lexp = self.lookup_expression.lookup(count_id)?; - let count_handle = get_expr_handle!(count_id, count_lexp); - let count_lookup_ty = self.lookup_type.lookup(count_lexp.type_id)?; - - let offset_kind = ctx.module.types[offset_lookup_ty.handle] - .inner - .scalar_kind() - .unwrap(); - let count_kind = ctx.module.types[count_lookup_ty.handle] - .inner - .scalar_kind() - .unwrap(); - - let offset_cast_handle = if offset_kind != crate::ScalarKind::Uint { - ctx.expressions.append( - crate::Expression::As { - expr: offset_handle, - kind: crate::ScalarKind::Uint, - convert: None, - }, - span, - ) - } else { - offset_handle - }; - - let count_cast_handle = if count_kind != crate::ScalarKind::Uint { - ctx.expressions.append( - crate::Expression::As { - expr: count_handle, - kind: crate::ScalarKind::Uint, - convert: None, - }, - span, - ) - } else { - count_handle - }; - - let expr = crate::Expression::Math { - fun: crate::MathFunction::InsertBits, - arg: base_handle, - arg1: Some(insert_handle), - arg2: Some(offset_cast_handle), - arg3: Some(count_cast_handle), - }; - self.lookup_expression.insert( - result_id, - LookupExpression { - handle: ctx.expressions.append(expr, span), - type_id: result_type_id, - block_id, - }, - ); - } - Op::BitFieldSExtract | Op::BitFieldUExtract => { - inst.expect(6)?; - - let result_type_id = self.next()?; - let result_id = self.next()?; - let base_id = self.next()?; - let offset_id = self.next()?; - let count_id = self.next()?; - let base_lexp = self.lookup_expression.lookup(base_id)?; - let base_handle = get_expr_handle!(base_id, base_lexp); - let offset_lexp = self.lookup_expression.lookup(offset_id)?; - let offset_handle = get_expr_handle!(offset_id, offset_lexp); - let offset_lookup_ty = self.lookup_type.lookup(offset_lexp.type_id)?; - let count_lexp = self.lookup_expression.lookup(count_id)?; - let count_handle = get_expr_handle!(count_id, count_lexp); - let count_lookup_ty = self.lookup_type.lookup(count_lexp.type_id)?; - - let offset_kind = ctx.module.types[offset_lookup_ty.handle] - .inner - .scalar_kind() - .unwrap(); - let count_kind = ctx.module.types[count_lookup_ty.handle] - .inner - .scalar_kind() - .unwrap(); - - let offset_cast_handle = if offset_kind != crate::ScalarKind::Uint { - ctx.expressions.append( - crate::Expression::As { - expr: offset_handle, - kind: crate::ScalarKind::Uint, - convert: None, - }, - span, - ) - } else { - offset_handle - }; - - let count_cast_handle = if count_kind != crate::ScalarKind::Uint { - ctx.expressions.append( - crate::Expression::As { - expr: count_handle, - kind: crate::ScalarKind::Uint, - convert: None, - }, - span, - ) - } else { - count_handle - }; - - let expr = crate::Expression::Math { - fun: crate::MathFunction::ExtractBits, - arg: base_handle, - arg1: Some(offset_cast_handle), - arg2: Some(count_cast_handle), - arg3: None, - }; - self.lookup_expression.insert( - result_id, - LookupExpression { - handle: ctx.expressions.append(expr, span), - type_id: result_type_id, - block_id, - }, - ); - } - Op::BitReverse | Op::BitCount => { - inst.expect(4)?; - - let result_type_id = self.next()?; - let result_id = self.next()?; - let base_id = self.next()?; - let base_lexp = self.lookup_expression.lookup(base_id)?; - let base_handle = get_expr_handle!(base_id, base_lexp); - let expr = crate::Expression::Math { - fun: match inst.op { - Op::BitReverse => crate::MathFunction::ReverseBits, - Op::BitCount => crate::MathFunction::CountOneBits, - _ => unreachable!(), - }, - arg: base_handle, - arg1: None, - arg2: None, - arg3: None, - }; - self.lookup_expression.insert( - result_id, - LookupExpression { - handle: ctx.expressions.append(expr, span), - type_id: result_type_id, - block_id, - }, - ); - } - Op::OuterProduct => { - inst.expect(5)?; - - let result_type_id = self.next()?; - let result_id = self.next()?; - let left_id = self.next()?; - let right_id = self.next()?; - let left_lexp = self.lookup_expression.lookup(left_id)?; - let left_handle = get_expr_handle!(left_id, left_lexp); - let right_lexp = self.lookup_expression.lookup(right_id)?; - let right_handle = get_expr_handle!(right_id, right_lexp); - let expr = crate::Expression::Math { - fun: crate::MathFunction::Outer, - arg: left_handle, - arg1: Some(right_handle), - arg2: None, - arg3: None, - }; - self.lookup_expression.insert( - result_id, - LookupExpression { - handle: ctx.expressions.append(expr, span), - type_id: result_type_id, - block_id, - }, - ); - } - // Bitwise instructions - Op::Not => { - inst.expect(4)?; - self.parse_expr_unary_op_sign_adjusted( - ctx, - &mut emitter, - &mut block, - block_id, - body_idx, - crate::UnaryOperator::BitwiseNot, - )?; - } - Op::ShiftRightLogical => { - inst.expect(5)?; - //TODO: convert input and result to unsigned - parse_expr_op!(crate::BinaryOperator::ShiftRight, SHIFT)?; - } - Op::ShiftRightArithmetic => { - inst.expect(5)?; - //TODO: convert input and result to signed - parse_expr_op!(crate::BinaryOperator::ShiftRight, SHIFT)?; - } - Op::ShiftLeftLogical => { - inst.expect(5)?; - parse_expr_op!(crate::BinaryOperator::ShiftLeft, SHIFT)?; - } - // Sampling - Op::Image => { - inst.expect(4)?; - self.parse_image_uncouple(block_id)?; - } - Op::SampledImage => { - inst.expect(5)?; - self.parse_image_couple()?; - } - Op::ImageWrite => { - let extra = inst.expect_at_least(4)?; - let stmt = - self.parse_image_write(extra, ctx, &mut emitter, &mut block, body_idx)?; - block.extend(emitter.finish(ctx.expressions)); - block.push(stmt, span); - emitter.start(ctx.expressions); - } - Op::ImageFetch | Op::ImageRead => { - let extra = inst.expect_at_least(5)?; - self.parse_image_load( - extra, - ctx, - &mut emitter, - &mut block, - block_id, - body_idx, - )?; - } - Op::ImageSampleImplicitLod | Op::ImageSampleExplicitLod => { - let extra = inst.expect_at_least(5)?; - let options = image::SamplingOptions { - compare: false, - project: false, - gather: false, - }; - self.parse_image_sample( - extra, - options, - ctx, - &mut emitter, - &mut block, - block_id, - body_idx, - )?; - } - Op::ImageSampleProjImplicitLod | Op::ImageSampleProjExplicitLod => { - let extra = inst.expect_at_least(5)?; - let options = image::SamplingOptions { - compare: false, - project: true, - gather: false, - }; - self.parse_image_sample( - extra, - options, - ctx, - &mut emitter, - &mut block, - block_id, - body_idx, - )?; - } - Op::ImageSampleDrefImplicitLod | Op::ImageSampleDrefExplicitLod => { - let extra = inst.expect_at_least(6)?; - let options = image::SamplingOptions { - compare: true, - project: false, - gather: false, - }; - self.parse_image_sample( - extra, - options, - ctx, - &mut emitter, - &mut block, - block_id, - body_idx, - )?; - } - Op::ImageSampleProjDrefImplicitLod | Op::ImageSampleProjDrefExplicitLod => { - let extra = inst.expect_at_least(6)?; - let options = image::SamplingOptions { - compare: true, - project: true, - gather: false, - }; - self.parse_image_sample( - extra, - options, - ctx, - &mut emitter, - &mut block, - block_id, - body_idx, - )?; - } - Op::ImageGather => { - let extra = inst.expect_at_least(6)?; - let options = image::SamplingOptions { - compare: false, - project: false, - gather: true, - }; - self.parse_image_sample( - extra, - options, - ctx, - &mut emitter, - &mut block, - block_id, - body_idx, - )?; - } - Op::ImageDrefGather => { - let extra = inst.expect_at_least(6)?; - let options = image::SamplingOptions { - compare: true, - project: false, - gather: true, - }; - self.parse_image_sample( - extra, - options, - ctx, - &mut emitter, - &mut block, - block_id, - body_idx, - )?; - } - Op::ImageQuerySize => { - inst.expect(4)?; - self.parse_image_query_size( - false, - ctx, - &mut emitter, - &mut block, - block_id, - body_idx, - )?; - } - Op::ImageQuerySizeLod => { - inst.expect(5)?; - self.parse_image_query_size( - true, - ctx, - &mut emitter, - &mut block, - block_id, - body_idx, - )?; - } - Op::ImageQueryLevels => { - inst.expect(4)?; - self.parse_image_query_other(crate::ImageQuery::NumLevels, ctx, block_id)?; - } - Op::ImageQuerySamples => { - inst.expect(4)?; - self.parse_image_query_other(crate::ImageQuery::NumSamples, ctx, block_id)?; - } - // other ops - Op::Select => { - inst.expect(6)?; - let result_type_id = self.next()?; - let result_id = self.next()?; - let condition = self.next()?; - let o1_id = self.next()?; - let o2_id = self.next()?; - - let cond_lexp = self.lookup_expression.lookup(condition)?; - let cond_handle = get_expr_handle!(condition, cond_lexp); - let o1_lexp = self.lookup_expression.lookup(o1_id)?; - let o1_handle = get_expr_handle!(o1_id, o1_lexp); - let o2_lexp = self.lookup_expression.lookup(o2_id)?; - let o2_handle = get_expr_handle!(o2_id, o2_lexp); - - let expr = crate::Expression::Select { - condition: cond_handle, - accept: o1_handle, - reject: o2_handle, - }; - self.lookup_expression.insert( - result_id, - LookupExpression { - handle: ctx.expressions.append(expr, span), - type_id: result_type_id, - block_id, - }, - ); - } - Op::VectorShuffle => { - inst.expect_at_least(5)?; - let result_type_id = self.next()?; - let result_id = self.next()?; - let v1_id = self.next()?; - let v2_id = self.next()?; - - let v1_lexp = self.lookup_expression.lookup(v1_id)?; - let v1_lty = self.lookup_type.lookup(v1_lexp.type_id)?; - let v1_handle = get_expr_handle!(v1_id, v1_lexp); - let n1 = match ctx.module.types[v1_lty.handle].inner { - crate::TypeInner::Vector { size, .. } => size as u32, - _ => return Err(Error::InvalidInnerType(v1_lexp.type_id)), - }; - let v2_lexp = self.lookup_expression.lookup(v2_id)?; - let v2_lty = self.lookup_type.lookup(v2_lexp.type_id)?; - let v2_handle = get_expr_handle!(v2_id, v2_lexp); - let n2 = match ctx.module.types[v2_lty.handle].inner { - crate::TypeInner::Vector { size, .. } => size as u32, - _ => return Err(Error::InvalidInnerType(v2_lexp.type_id)), - }; - - self.temp_bytes.clear(); - let mut max_component = 0; - for _ in 5..inst.wc as usize { - let mut index = self.next()?; - if index == u32::MAX { - // treat Undefined as X - index = 0; - } - max_component = max_component.max(index); - self.temp_bytes.push(index as u8); - } - - // Check for swizzle first. - let expr = if max_component < n1 { - use crate::SwizzleComponent as Sc; - let size = match self.temp_bytes.len() { - 2 => crate::VectorSize::Bi, - 3 => crate::VectorSize::Tri, - _ => crate::VectorSize::Quad, - }; - let mut pattern = [Sc::X; 4]; - for (pat, index) in pattern.iter_mut().zip(self.temp_bytes.drain(..)) { - *pat = match index { - 0 => Sc::X, - 1 => Sc::Y, - 2 => Sc::Z, - _ => Sc::W, - }; - } - crate::Expression::Swizzle { - size, - vector: v1_handle, - pattern, - } - } else { - // Fall back to access + compose - let mut components = Vec::with_capacity(self.temp_bytes.len()); - for index in self.temp_bytes.drain(..).map(|i| i as u32) { - let expr = if index < n1 { - crate::Expression::AccessIndex { - base: v1_handle, - index, - } - } else if index < n1 + n2 { - crate::Expression::AccessIndex { - base: v2_handle, - index: index - n1, - } - } else { - return Err(Error::InvalidAccessIndex(index)); - }; - components.push(ctx.expressions.append(expr, span)); - } - crate::Expression::Compose { - ty: self.lookup_type.lookup(result_type_id)?.handle, - components, - } - }; - - self.lookup_expression.insert( - result_id, - LookupExpression { - handle: ctx.expressions.append(expr, span), - type_id: result_type_id, - block_id, - }, - ); - } - Op::Bitcast - | Op::ConvertSToF - | Op::ConvertUToF - | Op::ConvertFToU - | Op::ConvertFToS - | Op::FConvert - | Op::UConvert - | Op::SConvert => { - inst.expect(4)?; - let result_type_id = self.next()?; - let result_id = self.next()?; - let value_id = self.next()?; - - let value_lexp = self.lookup_expression.lookup(value_id)?; - let ty_lookup = self.lookup_type.lookup(result_type_id)?; - let scalar = match ctx.module.types[ty_lookup.handle].inner { - crate::TypeInner::Scalar(scalar) - | crate::TypeInner::Vector { scalar, .. } - | crate::TypeInner::Matrix { scalar, .. } => scalar, - _ => return Err(Error::InvalidAsType(ty_lookup.handle)), - }; - - let expr = crate::Expression::As { - expr: get_expr_handle!(value_id, value_lexp), - kind: scalar.kind, - convert: if scalar.kind == crate::ScalarKind::Bool { - Some(crate::BOOL_WIDTH) - } else if inst.op == Op::Bitcast { - None - } else { - Some(scalar.width) - }, - }; - self.lookup_expression.insert( - result_id, - LookupExpression { - handle: ctx.expressions.append(expr, span), - type_id: result_type_id, - block_id, - }, - ); - } - Op::FunctionCall => { - inst.expect_at_least(4)?; - - let result_type_id = self.next()?; - let result_id = self.next()?; - let func_id = self.next()?; - - let mut arguments = Vec::with_capacity(inst.wc as usize - 4); - for _ in 0..arguments.capacity() { - let arg_id = self.next()?; - let lexp = self.lookup_expression.lookup(arg_id)?; - arguments.push(get_expr_handle!(arg_id, lexp)); - } - - block.extend(emitter.finish(ctx.expressions)); - - // We just need an unique handle here, nothing more. - let function = self.add_call(ctx.function_id, func_id); - - let result = if self.lookup_void_type == Some(result_type_id) { - None - } else { - let expr_handle = ctx - .expressions - .append(crate::Expression::CallResult(function), span); - self.lookup_expression.insert( - result_id, - LookupExpression { - handle: expr_handle, - type_id: result_type_id, - block_id, - }, - ); - Some(expr_handle) - }; - block.push( - crate::Statement::Call { - function, - arguments, - result, - }, - span, - ); - emitter.start(ctx.expressions); - } - Op::ExtInst => { - use crate::MathFunction as Mf; - use spirv::GLOp as Glo; - - let base_wc = 5; - inst.expect_at_least(base_wc)?; - - let result_type_id = self.next()?; - let result_id = self.next()?; - let set_id = self.next()?; - if Some(set_id) != self.ext_glsl_id { - return Err(Error::UnsupportedExtInstSet(set_id)); - } - let inst_id = self.next()?; - let gl_op = Glo::from_u32(inst_id).ok_or(Error::UnsupportedExtInst(inst_id))?; - - let fun = match gl_op { - Glo::Round => Mf::Round, - Glo::RoundEven => Mf::Round, - Glo::Trunc => Mf::Trunc, - Glo::FAbs | Glo::SAbs => Mf::Abs, - Glo::FSign | Glo::SSign => Mf::Sign, - Glo::Floor => Mf::Floor, - Glo::Ceil => Mf::Ceil, - Glo::Fract => Mf::Fract, - Glo::Sin => Mf::Sin, - Glo::Cos => Mf::Cos, - Glo::Tan => Mf::Tan, - Glo::Asin => Mf::Asin, - Glo::Acos => Mf::Acos, - Glo::Atan => Mf::Atan, - Glo::Sinh => Mf::Sinh, - Glo::Cosh => Mf::Cosh, - Glo::Tanh => Mf::Tanh, - Glo::Atan2 => Mf::Atan2, - Glo::Asinh => Mf::Asinh, - Glo::Acosh => Mf::Acosh, - Glo::Atanh => Mf::Atanh, - Glo::Radians => Mf::Radians, - Glo::Degrees => Mf::Degrees, - Glo::Pow => Mf::Pow, - Glo::Exp => Mf::Exp, - Glo::Log => Mf::Log, - Glo::Exp2 => Mf::Exp2, - Glo::Log2 => Mf::Log2, - Glo::Sqrt => Mf::Sqrt, - Glo::InverseSqrt => Mf::InverseSqrt, - Glo::MatrixInverse => Mf::Inverse, - Glo::Determinant => Mf::Determinant, - Glo::ModfStruct => Mf::Modf, - Glo::FMin | Glo::UMin | Glo::SMin | Glo::NMin => Mf::Min, - Glo::FMax | Glo::UMax | Glo::SMax | Glo::NMax => Mf::Max, - Glo::FClamp | Glo::UClamp | Glo::SClamp | Glo::NClamp => Mf::Clamp, - Glo::FMix => Mf::Mix, - Glo::Step => Mf::Step, - Glo::SmoothStep => Mf::SmoothStep, - Glo::Fma => Mf::Fma, - Glo::FrexpStruct => Mf::Frexp, - Glo::Ldexp => Mf::Ldexp, - Glo::Length => Mf::Length, - Glo::Distance => Mf::Distance, - Glo::Cross => Mf::Cross, - Glo::Normalize => Mf::Normalize, - Glo::FaceForward => Mf::FaceForward, - Glo::Reflect => Mf::Reflect, - Glo::Refract => Mf::Refract, - Glo::PackUnorm4x8 => Mf::Pack4x8unorm, - Glo::PackSnorm4x8 => Mf::Pack4x8snorm, - Glo::PackHalf2x16 => Mf::Pack2x16float, - Glo::PackUnorm2x16 => Mf::Pack2x16unorm, - Glo::PackSnorm2x16 => Mf::Pack2x16snorm, - Glo::UnpackUnorm4x8 => Mf::Unpack4x8unorm, - Glo::UnpackSnorm4x8 => Mf::Unpack4x8snorm, - Glo::UnpackHalf2x16 => Mf::Unpack2x16float, - Glo::UnpackUnorm2x16 => Mf::Unpack2x16unorm, - Glo::UnpackSnorm2x16 => Mf::Unpack2x16snorm, - Glo::FindILsb => Mf::FirstTrailingBit, - Glo::FindUMsb | Glo::FindSMsb => Mf::FirstLeadingBit, - // TODO: https://github.com/gfx-rs/naga/issues/2526 - Glo::Modf | Glo::Frexp => return Err(Error::UnsupportedExtInst(inst_id)), - Glo::IMix - | Glo::PackDouble2x32 - | Glo::UnpackDouble2x32 - | Glo::InterpolateAtCentroid - | Glo::InterpolateAtSample - | Glo::InterpolateAtOffset => { - return Err(Error::UnsupportedExtInst(inst_id)) - } - }; - - let arg_count = fun.argument_count(); - inst.expect(base_wc + arg_count as u16)?; - let arg = { - let arg_id = self.next()?; - let lexp = self.lookup_expression.lookup(arg_id)?; - get_expr_handle!(arg_id, lexp) - }; - let arg1 = if arg_count > 1 { - let arg_id = self.next()?; - let lexp = self.lookup_expression.lookup(arg_id)?; - Some(get_expr_handle!(arg_id, lexp)) - } else { - None - }; - let arg2 = if arg_count > 2 { - let arg_id = self.next()?; - let lexp = self.lookup_expression.lookup(arg_id)?; - Some(get_expr_handle!(arg_id, lexp)) - } else { - None - }; - let arg3 = if arg_count > 3 { - let arg_id = self.next()?; - let lexp = self.lookup_expression.lookup(arg_id)?; - Some(get_expr_handle!(arg_id, lexp)) - } else { - None - }; - - let expr = crate::Expression::Math { - fun, - arg, - arg1, - arg2, - arg3, - }; - self.lookup_expression.insert( - result_id, - LookupExpression { - handle: ctx.expressions.append(expr, span), - type_id: result_type_id, - block_id, - }, - ); - } - // Relational and Logical Instructions - Op::LogicalNot => { - inst.expect(4)?; - parse_expr_op!(crate::UnaryOperator::LogicalNot, UNARY)?; - } - Op::LogicalOr => { - inst.expect(5)?; - parse_expr_op!(crate::BinaryOperator::LogicalOr, BINARY)?; - } - Op::LogicalAnd => { - inst.expect(5)?; - parse_expr_op!(crate::BinaryOperator::LogicalAnd, BINARY)?; - } - Op::SGreaterThan | Op::SGreaterThanEqual | Op::SLessThan | Op::SLessThanEqual => { - inst.expect(5)?; - self.parse_expr_int_comparison( - ctx, - &mut emitter, - &mut block, - block_id, - body_idx, - map_binary_operator(inst.op)?, - crate::ScalarKind::Sint, - )?; - } - Op::UGreaterThan | Op::UGreaterThanEqual | Op::ULessThan | Op::ULessThanEqual => { - inst.expect(5)?; - self.parse_expr_int_comparison( - ctx, - &mut emitter, - &mut block, - block_id, - body_idx, - map_binary_operator(inst.op)?, - crate::ScalarKind::Uint, - )?; - } - Op::FOrdEqual - | Op::FUnordEqual - | Op::FOrdNotEqual - | Op::FUnordNotEqual - | Op::FOrdLessThan - | Op::FUnordLessThan - | Op::FOrdGreaterThan - | Op::FUnordGreaterThan - | Op::FOrdLessThanEqual - | Op::FUnordLessThanEqual - | Op::FOrdGreaterThanEqual - | Op::FUnordGreaterThanEqual - | Op::LogicalEqual - | Op::LogicalNotEqual => { - inst.expect(5)?; - let operator = map_binary_operator(inst.op)?; - parse_expr_op!(operator, BINARY)?; - } - Op::Any | Op::All | Op::IsNan | Op::IsInf | Op::IsFinite | Op::IsNormal => { - inst.expect(4)?; - let result_type_id = self.next()?; - let result_id = self.next()?; - let arg_id = self.next()?; - - let arg_lexp = self.lookup_expression.lookup(arg_id)?; - let arg_handle = get_expr_handle!(arg_id, arg_lexp); - - let expr = crate::Expression::Relational { - fun: map_relational_fun(inst.op)?, - argument: arg_handle, - }; - self.lookup_expression.insert( - result_id, - LookupExpression { - handle: ctx.expressions.append(expr, span), - type_id: result_type_id, - block_id, - }, - ); - } - Op::Kill => { - inst.expect(1)?; - break Some(crate::Statement::Kill); - } - Op::Unreachable => { - inst.expect(1)?; - break None; - } - Op::Return => { - inst.expect(1)?; - break Some(crate::Statement::Return { value: None }); - } - Op::ReturnValue => { - inst.expect(2)?; - let value_id = self.next()?; - let value_lexp = self.lookup_expression.lookup(value_id)?; - let value_handle = get_expr_handle!(value_id, value_lexp); - break Some(crate::Statement::Return { - value: Some(value_handle), - }); - } - Op::Branch => { - inst.expect(2)?; - let target_id = self.next()?; - - // If this is a branch to a merge or continue block, then - // that ends the current body. - // - // Why can we count on finding an entry here when it's - // needed? SPIR-V requires dominators to appear before - // blocks they dominate, so we will have visited a - // structured control construct's header block before - // anything that could exit it. - if let Some(info) = ctx.mergers.get(&target_id) { - block.extend(emitter.finish(ctx.expressions)); - ctx.blocks.insert(block_id, block); - let body = &mut ctx.bodies[body_idx]; - body.data.push(BodyFragment::BlockId(block_id)); - - merger(body, info); - - return Ok(()); - } - - // If `target_id` has no entry in `ctx.body_for_label`, then - // this must be the only branch to it: - // - // - We've already established that it's not anybody's merge - // block. - // - // - It can't be a switch case. Only switch header blocks - // and other switch cases can branch to a switch case. - // Switch header blocks must dominate all their cases, so - // they must appear in the file before them, and when we - // see `Op::Switch` we populate `ctx.body_for_label` for - // every switch case. - // - // Thus, `target_id` must be a simple extension of the - // current block, which we dominate, so we know we'll - // encounter it later in the file. - ctx.body_for_label.entry(target_id).or_insert(body_idx); - - break None; - } - Op::BranchConditional => { - inst.expect_at_least(4)?; - - let condition = { - let condition_id = self.next()?; - let lexp = self.lookup_expression.lookup(condition_id)?; - get_expr_handle!(condition_id, lexp) - }; - - // HACK(eddyb) Naga doesn't seem to have this helper, - // so it's declared on the fly here for convenience. - #[derive(Copy, Clone)] - struct BranchTarget { - label_id: spirv::Word, - merge_info: Option<MergeBlockInformation>, - } - let branch_target = |label_id| BranchTarget { - label_id, - merge_info: ctx.mergers.get(&label_id).copied(), - }; - - let true_target = branch_target(self.next()?); - let false_target = branch_target(self.next()?); - - // Consume branch weights - for _ in 4..inst.wc { - let _ = self.next()?; - } - - // Handle `OpBranchConditional`s used at the end of a loop - // body's "continuing" section as a "conditional backedge", - // i.e. a `do`-`while` condition, or `break if` in WGSL. - - // HACK(eddyb) this has to go to the parent *twice*, because - // `OpLoopMerge` left the "continuing" section nested in the - // loop body in terms of `parent`, but not `BodyFragment`. - let parent_body_idx = ctx.bodies[body_idx].parent; - let parent_parent_body_idx = ctx.bodies[parent_body_idx].parent; - match ctx.bodies[parent_parent_body_idx].data[..] { - // The `OpLoopMerge`'s `continuing` block and the loop's - // backedge block may not be the same, but they'll both - // belong to the same body. - [.., BodyFragment::Loop { - body: loop_body_idx, - continuing: loop_continuing_idx, - break_if: ref mut break_if_slot @ None, - }] if body_idx == loop_continuing_idx => { - // Try both orderings of break-vs-backedge, because - // SPIR-V is symmetrical here, unlike WGSL `break if`. - let break_if_cond = [true, false].into_iter().find_map(|true_breaks| { - let (break_candidate, backedge_candidate) = if true_breaks { - (true_target, false_target) - } else { - (false_target, true_target) - }; - - if break_candidate.merge_info - != Some(MergeBlockInformation::LoopMerge) - { - return None; - } - - // HACK(eddyb) since Naga doesn't explicitly track - // backedges, this is checking for the outcome of - // `OpLoopMerge` below (even if it looks weird). - let backedge_candidate_is_backedge = - backedge_candidate.merge_info.is_none() - && ctx.body_for_label.get(&backedge_candidate.label_id) - == Some(&loop_body_idx); - if !backedge_candidate_is_backedge { - return None; - } - - Some(if true_breaks { - condition - } else { - ctx.expressions.append( - crate::Expression::Unary { - op: crate::UnaryOperator::LogicalNot, - expr: condition, - }, - span, - ) - }) - }); - - if let Some(break_if_cond) = break_if_cond { - *break_if_slot = Some(break_if_cond); - - // This `OpBranchConditional` ends the "continuing" - // section of the loop body as normal, with the - // `break if` condition having been stashed above. - break None; - } - } - _ => {} - } - - block.extend(emitter.finish(ctx.expressions)); - ctx.blocks.insert(block_id, block); - let body = &mut ctx.bodies[body_idx]; - body.data.push(BodyFragment::BlockId(block_id)); - - let same_target = true_target.label_id == false_target.label_id; - - // Start a body block for the `accept` branch. - let accept = ctx.bodies.len(); - let mut accept_block = Body::with_parent(body_idx); - - // If the `OpBranchConditional` target is somebody else's - // merge or continue block, then put a `Break` or `Continue` - // statement in this new body block. - if let Some(info) = true_target.merge_info { - merger( - match same_target { - true => &mut ctx.bodies[body_idx], - false => &mut accept_block, - }, - &info, - ) - } else { - // Note the body index for the block we're branching to. - let prev = ctx.body_for_label.insert( - true_target.label_id, - match same_target { - true => body_idx, - false => accept, - }, - ); - debug_assert!(prev.is_none()); - } - - if same_target { - return Ok(()); - } - - ctx.bodies.push(accept_block); - - // Handle the `reject` branch just like the `accept` block. - let reject = ctx.bodies.len(); - let mut reject_block = Body::with_parent(body_idx); - - if let Some(info) = false_target.merge_info { - merger(&mut reject_block, &info) - } else { - let prev = ctx.body_for_label.insert(false_target.label_id, reject); - debug_assert!(prev.is_none()); - } - - ctx.bodies.push(reject_block); - - let body = &mut ctx.bodies[body_idx]; - body.data.push(BodyFragment::If { - condition, - accept, - reject, - }); - - return Ok(()); - } - Op::Switch => { - inst.expect_at_least(3)?; - let selector = self.next()?; - let default_id = self.next()?; - - // If the previous instruction was a `OpSelectionMerge` then we must - // promote the `MergeBlockInformation` to a `SwitchMerge` - if let Some(merge) = selection_merge_block { - ctx.mergers - .insert(merge, MergeBlockInformation::SwitchMerge); - } - - let default = ctx.bodies.len(); - ctx.bodies.push(Body::with_parent(body_idx)); - ctx.body_for_label.entry(default_id).or_insert(default); - - let selector_lexp = &self.lookup_expression[&selector]; - let selector_lty = self.lookup_type.lookup(selector_lexp.type_id)?; - let selector_handle = get_expr_handle!(selector, selector_lexp); - let selector = match ctx.module.types[selector_lty.handle].inner { - crate::TypeInner::Scalar(crate::Scalar { - kind: crate::ScalarKind::Uint, - width: _, - }) => { - // IR expects a signed integer, so do a bitcast - ctx.expressions.append( - crate::Expression::As { - kind: crate::ScalarKind::Sint, - expr: selector_handle, - convert: None, - }, - span, - ) - } - crate::TypeInner::Scalar(crate::Scalar { - kind: crate::ScalarKind::Sint, - width: _, - }) => selector_handle, - ref other => unimplemented!("Unexpected selector {:?}", other), - }; - - // Clear past switch cases to prevent them from entering this one - self.switch_cases.clear(); - - for _ in 0..(inst.wc - 3) / 2 { - let literal = self.next()?; - let target = self.next()?; - - let case_body_idx = ctx.bodies.len(); - - // Check if any previous case already used this target block id, if so - // group them together to reorder them later so that no weird - // fallthrough cases happen. - if let Some(&mut (_, ref mut literals)) = self.switch_cases.get_mut(&target) - { - literals.push(literal as i32); - continue; - } - - let mut body = Body::with_parent(body_idx); - - if let Some(info) = ctx.mergers.get(&target) { - merger(&mut body, info); - } - - ctx.bodies.push(body); - ctx.body_for_label.entry(target).or_insert(case_body_idx); - - // Register this target block id as already having been processed and - // the respective body index assigned and the first case value - self.switch_cases - .insert(target, (case_body_idx, vec![literal as i32])); - } - - // Loop through the collected target blocks creating a new case for each - // literal pointing to it, only one case will have the true body and all the - // others will be empty fallthrough so that they all execute the same body - // without duplicating code. - // - // Since `switch_cases` is an indexmap the order of insertion is preserved - // this is needed because spir-v defines fallthrough order in the switch - // instruction. - let mut cases = Vec::with_capacity((inst.wc as usize - 3) / 2); - for &(case_body_idx, ref literals) in self.switch_cases.values() { - let value = literals[0]; - - for &literal in literals.iter().skip(1) { - let empty_body_idx = ctx.bodies.len(); - let body = Body::with_parent(body_idx); - - ctx.bodies.push(body); - - cases.push((literal, empty_body_idx)); - } - - cases.push((value, case_body_idx)); - } - - block.extend(emitter.finish(ctx.expressions)); - - let body = &mut ctx.bodies[body_idx]; - ctx.blocks.insert(block_id, block); - // Make sure the vector has space for at least two more allocations - body.data.reserve(2); - body.data.push(BodyFragment::BlockId(block_id)); - body.data.push(BodyFragment::Switch { - selector, - cases, - default, - }); - - return Ok(()); - } - Op::SelectionMerge => { - inst.expect(3)?; - let merge_block_id = self.next()?; - // TODO: Selection Control Mask - let _selection_control = self.next()?; - - // Indicate that the merge block is a continuation of the - // current `Body`. - ctx.body_for_label.entry(merge_block_id).or_insert(body_idx); - - // Let subsequent branches to the merge block know that - // they've reached the end of the selection construct. - ctx.mergers - .insert(merge_block_id, MergeBlockInformation::SelectionMerge); - - selection_merge_block = Some(merge_block_id); - } - Op::LoopMerge => { - inst.expect_at_least(4)?; - let merge_block_id = self.next()?; - let continuing = self.next()?; - - // TODO: Loop Control Parameters - for _ in 0..inst.wc - 3 { - self.next()?; - } - - // Indicate that the merge block is a continuation of the - // current `Body`. - ctx.body_for_label.entry(merge_block_id).or_insert(body_idx); - // Let subsequent branches to the merge block know that - // they're `Break` statements. - ctx.mergers - .insert(merge_block_id, MergeBlockInformation::LoopMerge); - - let loop_body_idx = ctx.bodies.len(); - ctx.bodies.push(Body::with_parent(body_idx)); - - let continue_idx = ctx.bodies.len(); - // The continue block inherits the scope of the loop body - ctx.bodies.push(Body::with_parent(loop_body_idx)); - ctx.body_for_label.entry(continuing).or_insert(continue_idx); - // Let subsequent branches to the continue block know that - // they're `Continue` statements. - ctx.mergers - .insert(continuing, MergeBlockInformation::LoopContinue); - - // The loop header always belongs to the loop body - ctx.body_for_label.insert(block_id, loop_body_idx); - - let parent_body = &mut ctx.bodies[body_idx]; - parent_body.data.push(BodyFragment::Loop { - body: loop_body_idx, - continuing: continue_idx, - break_if: None, - }); - body_idx = loop_body_idx; - } - Op::DPdxCoarse => { - parse_expr_op!( - crate::DerivativeAxis::X, - crate::DerivativeControl::Coarse, - DERIVATIVE - )?; - } - Op::DPdyCoarse => { - parse_expr_op!( - crate::DerivativeAxis::Y, - crate::DerivativeControl::Coarse, - DERIVATIVE - )?; - } - Op::FwidthCoarse => { - parse_expr_op!( - crate::DerivativeAxis::Width, - crate::DerivativeControl::Coarse, - DERIVATIVE - )?; - } - Op::DPdxFine => { - parse_expr_op!( - crate::DerivativeAxis::X, - crate::DerivativeControl::Fine, - DERIVATIVE - )?; - } - Op::DPdyFine => { - parse_expr_op!( - crate::DerivativeAxis::Y, - crate::DerivativeControl::Fine, - DERIVATIVE - )?; - } - Op::FwidthFine => { - parse_expr_op!( - crate::DerivativeAxis::Width, - crate::DerivativeControl::Fine, - DERIVATIVE - )?; - } - Op::DPdx => { - parse_expr_op!( - crate::DerivativeAxis::X, - crate::DerivativeControl::None, - DERIVATIVE - )?; - } - Op::DPdy => { - parse_expr_op!( - crate::DerivativeAxis::Y, - crate::DerivativeControl::None, - DERIVATIVE - )?; - } - Op::Fwidth => { - parse_expr_op!( - crate::DerivativeAxis::Width, - crate::DerivativeControl::None, - DERIVATIVE - )?; - } - Op::ArrayLength => { - inst.expect(5)?; - let result_type_id = self.next()?; - let result_id = self.next()?; - let structure_id = self.next()?; - let member_index = self.next()?; - - // We're assuming that the validation pass, if it's run, will catch if the - // wrong types or parameters are supplied here. - - let structure_ptr = self.lookup_expression.lookup(structure_id)?; - let structure_handle = get_expr_handle!(structure_id, structure_ptr); - - let member_ptr = ctx.expressions.append( - crate::Expression::AccessIndex { - base: structure_handle, - index: member_index, - }, - span, - ); - - let length = ctx - .expressions - .append(crate::Expression::ArrayLength(member_ptr), span); - - self.lookup_expression.insert( - result_id, - LookupExpression { - handle: length, - type_id: result_type_id, - block_id, - }, - ); - } - Op::CopyMemory => { - inst.expect_at_least(3)?; - let target_id = self.next()?; - let source_id = self.next()?; - let _memory_access = if inst.wc != 3 { - inst.expect(4)?; - spirv::MemoryAccess::from_bits(self.next()?) - .ok_or(Error::InvalidParameter(Op::CopyMemory))? - } else { - spirv::MemoryAccess::NONE - }; - - // TODO: check if the source and target types are the same? - let target = self.lookup_expression.lookup(target_id)?; - let target_handle = get_expr_handle!(target_id, target); - let source = self.lookup_expression.lookup(source_id)?; - let source_handle = get_expr_handle!(source_id, source); - - // This operation is practically the same as loading and then storing, I think. - let value_expr = ctx.expressions.append( - crate::Expression::Load { - pointer: source_handle, - }, - span, - ); - - block.extend(emitter.finish(ctx.expressions)); - block.push( - crate::Statement::Store { - pointer: target_handle, - value: value_expr, - }, - span, - ); - - emitter.start(ctx.expressions); - } - Op::ControlBarrier => { - inst.expect(4)?; - let exec_scope_id = self.next()?; - let _mem_scope_raw = self.next()?; - let semantics_id = self.next()?; - let exec_scope_const = self.lookup_constant.lookup(exec_scope_id)?; - let semantics_const = self.lookup_constant.lookup(semantics_id)?; - - let exec_scope = resolve_constant(ctx.gctx(), &exec_scope_const.inner) - .ok_or(Error::InvalidBarrierScope(exec_scope_id))?; - let semantics = resolve_constant(ctx.gctx(), &semantics_const.inner) - .ok_or(Error::InvalidBarrierMemorySemantics(semantics_id))?; - - if exec_scope == spirv::Scope::Workgroup as u32 - || exec_scope == spirv::Scope::Subgroup as u32 - { - let mut flags = crate::Barrier::empty(); - flags.set( - crate::Barrier::STORAGE, - semantics & spirv::MemorySemantics::UNIFORM_MEMORY.bits() != 0, - ); - flags.set( - crate::Barrier::WORK_GROUP, - semantics & (spirv::MemorySemantics::WORKGROUP_MEMORY).bits() != 0, - ); - flags.set( - crate::Barrier::SUB_GROUP, - semantics & spirv::MemorySemantics::SUBGROUP_MEMORY.bits() != 0, - ); - flags.set( - crate::Barrier::TEXTURE, - semantics & spirv::MemorySemantics::IMAGE_MEMORY.bits() != 0, - ); - - block.extend(emitter.finish(ctx.expressions)); - block.push(crate::Statement::ControlBarrier(flags), span); - emitter.start(ctx.expressions); - } else { - log::warn!("Unsupported barrier execution scope: {exec_scope}"); - } - } - Op::MemoryBarrier => { - inst.expect(3)?; - let mem_scope_id = self.next()?; - let semantics_id = self.next()?; - let mem_scope_const = self.lookup_constant.lookup(mem_scope_id)?; - let semantics_const = self.lookup_constant.lookup(semantics_id)?; - - let mem_scope = resolve_constant(ctx.gctx(), &mem_scope_const.inner) - .ok_or(Error::InvalidBarrierScope(mem_scope_id))?; - let semantics = resolve_constant(ctx.gctx(), &semantics_const.inner) - .ok_or(Error::InvalidBarrierMemorySemantics(semantics_id))?; - - let mut flags = if mem_scope == spirv::Scope::Device as u32 { - crate::Barrier::STORAGE - } else if mem_scope == spirv::Scope::Workgroup as u32 { - crate::Barrier::WORK_GROUP - } else if mem_scope == spirv::Scope::Subgroup as u32 { - crate::Barrier::SUB_GROUP - } else { - crate::Barrier::empty() - }; - flags.set( - crate::Barrier::STORAGE, - semantics & spirv::MemorySemantics::UNIFORM_MEMORY.bits() != 0, - ); - flags.set( - crate::Barrier::WORK_GROUP, - semantics & (spirv::MemorySemantics::WORKGROUP_MEMORY).bits() != 0, - ); - flags.set( - crate::Barrier::SUB_GROUP, - semantics & spirv::MemorySemantics::SUBGROUP_MEMORY.bits() != 0, - ); - flags.set( - crate::Barrier::TEXTURE, - semantics & spirv::MemorySemantics::IMAGE_MEMORY.bits() != 0, - ); - - block.extend(emitter.finish(ctx.expressions)); - block.push(crate::Statement::MemoryBarrier(flags), span); - emitter.start(ctx.expressions); - } - Op::CopyObject => { - inst.expect(4)?; - let result_type_id = self.next()?; - let result_id = self.next()?; - let operand_id = self.next()?; - - let lookup = self.lookup_expression.lookup(operand_id)?; - let handle = get_expr_handle!(operand_id, lookup); - - self.lookup_expression.insert( - result_id, - LookupExpression { - handle, - type_id: result_type_id, - block_id, - }, - ); - } - Op::GroupNonUniformBallot => { - inst.expect(5)?; - block.extend(emitter.finish(ctx.expressions)); - let result_type_id = self.next()?; - let result_id = self.next()?; - let exec_scope_id = self.next()?; - let predicate_id = self.next()?; - - let exec_scope_const = self.lookup_constant.lookup(exec_scope_id)?; - let _exec_scope = resolve_constant(ctx.gctx(), &exec_scope_const.inner) - .filter(|exec_scope| *exec_scope == spirv::Scope::Subgroup as u32) - .ok_or(Error::InvalidBarrierScope(exec_scope_id))?; - - let predicate = if self - .lookup_constant - .lookup(predicate_id) - .ok() - .filter(|predicate_const| match predicate_const.inner { - Constant::Constant(constant) => matches!( - ctx.gctx().global_expressions[ctx.gctx().constants[constant].init], - crate::Expression::Literal(crate::Literal::Bool(true)), - ), - Constant::Override(_) => false, - }) - .is_some() - { - None - } else { - let predicate_lookup = self.lookup_expression.lookup(predicate_id)?; - let predicate_handle = get_expr_handle!(predicate_id, predicate_lookup); - Some(predicate_handle) - }; - - let result_handle = ctx - .expressions - .append(crate::Expression::SubgroupBallotResult, span); - self.lookup_expression.insert( - result_id, - LookupExpression { - handle: result_handle, - type_id: result_type_id, - block_id, - }, - ); - - block.push( - crate::Statement::SubgroupBallot { - result: result_handle, - predicate, - }, - span, - ); - emitter.start(ctx.expressions); - } - Op::GroupNonUniformAll - | Op::GroupNonUniformAny - | Op::GroupNonUniformIAdd - | Op::GroupNonUniformFAdd - | Op::GroupNonUniformIMul - | Op::GroupNonUniformFMul - | Op::GroupNonUniformSMax - | Op::GroupNonUniformUMax - | Op::GroupNonUniformFMax - | Op::GroupNonUniformSMin - | Op::GroupNonUniformUMin - | Op::GroupNonUniformFMin - | Op::GroupNonUniformBitwiseAnd - | Op::GroupNonUniformBitwiseOr - | Op::GroupNonUniformBitwiseXor - | Op::GroupNonUniformLogicalAnd - | Op::GroupNonUniformLogicalOr - | Op::GroupNonUniformLogicalXor => { - block.extend(emitter.finish(ctx.expressions)); - inst.expect( - if matches!(inst.op, Op::GroupNonUniformAll | Op::GroupNonUniformAny) { - 5 - } else { - 6 - }, - )?; - let result_type_id = self.next()?; - let result_id = self.next()?; - let exec_scope_id = self.next()?; - let collective_op_id = match inst.op { - Op::GroupNonUniformAll | Op::GroupNonUniformAny => { - crate::CollectiveOperation::Reduce - } - _ => { - let group_op_id = self.next()?; - match spirv::GroupOperation::from_u32(group_op_id) { - Some(spirv::GroupOperation::Reduce) => { - crate::CollectiveOperation::Reduce - } - Some(spirv::GroupOperation::InclusiveScan) => { - crate::CollectiveOperation::InclusiveScan - } - Some(spirv::GroupOperation::ExclusiveScan) => { - crate::CollectiveOperation::ExclusiveScan - } - _ => return Err(Error::UnsupportedGroupOperation(group_op_id)), - } - } - }; - let argument_id = self.next()?; - - let argument_lookup = self.lookup_expression.lookup(argument_id)?; - let argument_handle = get_expr_handle!(argument_id, argument_lookup); - - let exec_scope_const = self.lookup_constant.lookup(exec_scope_id)?; - let _exec_scope = resolve_constant(ctx.gctx(), &exec_scope_const.inner) - .filter(|exec_scope| *exec_scope == spirv::Scope::Subgroup as u32) - .ok_or(Error::InvalidBarrierScope(exec_scope_id))?; - - let op_id = match inst.op { - Op::GroupNonUniformAll => crate::SubgroupOperation::All, - Op::GroupNonUniformAny => crate::SubgroupOperation::Any, - Op::GroupNonUniformIAdd | Op::GroupNonUniformFAdd => { - crate::SubgroupOperation::Add - } - Op::GroupNonUniformIMul | Op::GroupNonUniformFMul => { - crate::SubgroupOperation::Mul - } - Op::GroupNonUniformSMax - | Op::GroupNonUniformUMax - | Op::GroupNonUniformFMax => crate::SubgroupOperation::Max, - Op::GroupNonUniformSMin - | Op::GroupNonUniformUMin - | Op::GroupNonUniformFMin => crate::SubgroupOperation::Min, - Op::GroupNonUniformBitwiseAnd | Op::GroupNonUniformLogicalAnd => { - crate::SubgroupOperation::And - } - Op::GroupNonUniformBitwiseOr | Op::GroupNonUniformLogicalOr => { - crate::SubgroupOperation::Or - } - Op::GroupNonUniformBitwiseXor | Op::GroupNonUniformLogicalXor => { - crate::SubgroupOperation::Xor - } - _ => unreachable!(), - }; - - let result_type = self.lookup_type.lookup(result_type_id)?; - - let result_handle = ctx.expressions.append( - crate::Expression::SubgroupOperationResult { - ty: result_type.handle, - }, - span, - ); - self.lookup_expression.insert( - result_id, - LookupExpression { - handle: result_handle, - type_id: result_type_id, - block_id, - }, - ); - - block.push( - crate::Statement::SubgroupCollectiveOperation { - result: result_handle, - op: op_id, - collective_op: collective_op_id, - argument: argument_handle, - }, - span, - ); - emitter.start(ctx.expressions); - } - Op::GroupNonUniformBroadcastFirst - | Op::GroupNonUniformBroadcast - | Op::GroupNonUniformShuffle - | Op::GroupNonUniformShuffleDown - | Op::GroupNonUniformShuffleUp - | Op::GroupNonUniformShuffleXor - | Op::GroupNonUniformQuadBroadcast => { - inst.expect(if matches!(inst.op, Op::GroupNonUniformBroadcastFirst) { - 5 - } else { - 6 - })?; - block.extend(emitter.finish(ctx.expressions)); - let result_type_id = self.next()?; - let result_id = self.next()?; - let exec_scope_id = self.next()?; - let argument_id = self.next()?; - - let argument_lookup = self.lookup_expression.lookup(argument_id)?; - let argument_handle = get_expr_handle!(argument_id, argument_lookup); - - let exec_scope_const = self.lookup_constant.lookup(exec_scope_id)?; - let _exec_scope = resolve_constant(ctx.gctx(), &exec_scope_const.inner) - .filter(|exec_scope| *exec_scope == spirv::Scope::Subgroup as u32) - .ok_or(Error::InvalidBarrierScope(exec_scope_id))?; - - let mode = if matches!(inst.op, Op::GroupNonUniformBroadcastFirst) { - crate::GatherMode::BroadcastFirst - } else { - let index_id = self.next()?; - let index_lookup = self.lookup_expression.lookup(index_id)?; - let index_handle = get_expr_handle!(index_id, index_lookup); - match inst.op { - Op::GroupNonUniformBroadcast => { - crate::GatherMode::Broadcast(index_handle) - } - Op::GroupNonUniformShuffle => crate::GatherMode::Shuffle(index_handle), - Op::GroupNonUniformShuffleDown => { - crate::GatherMode::ShuffleDown(index_handle) - } - Op::GroupNonUniformShuffleUp => { - crate::GatherMode::ShuffleUp(index_handle) - } - Op::GroupNonUniformShuffleXor => { - crate::GatherMode::ShuffleXor(index_handle) - } - Op::GroupNonUniformQuadBroadcast => { - crate::GatherMode::QuadBroadcast(index_handle) - } - _ => unreachable!(), - } - }; - - let result_type = self.lookup_type.lookup(result_type_id)?; - - let result_handle = ctx.expressions.append( - crate::Expression::SubgroupOperationResult { - ty: result_type.handle, - }, - span, - ); - self.lookup_expression.insert( - result_id, - LookupExpression { - handle: result_handle, - type_id: result_type_id, - block_id, - }, - ); - - block.push( - crate::Statement::SubgroupGather { - result: result_handle, - mode, - argument: argument_handle, - }, - span, - ); - emitter.start(ctx.expressions); - } - Op::GroupNonUniformQuadSwap => { - inst.expect(6)?; - block.extend(emitter.finish(ctx.expressions)); - let result_type_id = self.next()?; - let result_id = self.next()?; - let exec_scope_id = self.next()?; - let argument_id = self.next()?; - let direction_id = self.next()?; - - let argument_lookup = self.lookup_expression.lookup(argument_id)?; - let argument_handle = get_expr_handle!(argument_id, argument_lookup); - - let exec_scope_const = self.lookup_constant.lookup(exec_scope_id)?; - let _exec_scope = resolve_constant(ctx.gctx(), &exec_scope_const.inner) - .filter(|exec_scope| *exec_scope == spirv::Scope::Subgroup as u32) - .ok_or(Error::InvalidBarrierScope(exec_scope_id))?; - - let direction_const = self.lookup_constant.lookup(direction_id)?; - let direction_const = resolve_constant(ctx.gctx(), &direction_const.inner) - .ok_or(Error::InvalidOperand)?; - let direction = match direction_const { - 0 => crate::Direction::X, - 1 => crate::Direction::Y, - 2 => crate::Direction::Diagonal, - _ => unreachable!(), - }; - - let result_type = self.lookup_type.lookup(result_type_id)?; - - let result_handle = ctx.expressions.append( - crate::Expression::SubgroupOperationResult { - ty: result_type.handle, - }, - span, - ); - self.lookup_expression.insert( - result_id, - LookupExpression { - handle: result_handle, - type_id: result_type_id, - block_id, - }, - ); - - block.push( - crate::Statement::SubgroupGather { - mode: crate::GatherMode::QuadSwap(direction), - result: result_handle, - argument: argument_handle, - }, - span, - ); - emitter.start(ctx.expressions); - } - Op::AtomicLoad => { - inst.expect(6)?; - let start = self.data_offset; - let result_type_id = self.next()?; - let result_id = self.next()?; - let pointer_id = self.next()?; - let _scope_id = self.next()?; - let _memory_semantics_id = self.next()?; - let span = self.span_from_with_op(start); - - log::trace!("\t\t\tlooking up expr {pointer_id:?}"); - let p_lexp_handle = - get_expr_handle!(pointer_id, self.lookup_expression.lookup(pointer_id)?); - - // Create an expression for our result - let expr = crate::Expression::Load { - pointer: p_lexp_handle, - }; - let handle = ctx.expressions.append(expr, span); - self.lookup_expression.insert( - result_id, - LookupExpression { - handle, - type_id: result_type_id, - block_id, - }, - ); - - // Store any associated global variables so we can upgrade their types later - self.record_atomic_access(ctx, p_lexp_handle)?; - } - Op::AtomicStore => { - inst.expect(5)?; - let start = self.data_offset; - let pointer_id = self.next()?; - let _scope_id = self.next()?; - let _memory_semantics_id = self.next()?; - let value_id = self.next()?; - let span = self.span_from_with_op(start); - - log::trace!("\t\t\tlooking up pointer expr {pointer_id:?}"); - let p_lexp_handle = - get_expr_handle!(pointer_id, self.lookup_expression.lookup(pointer_id)?); - - log::trace!("\t\t\tlooking up value expr {pointer_id:?}"); - let v_lexp_handle = - get_expr_handle!(value_id, self.lookup_expression.lookup(value_id)?); - - block.extend(emitter.finish(ctx.expressions)); - // Create a statement for the op itself - let stmt = crate::Statement::Store { - pointer: p_lexp_handle, - value: v_lexp_handle, - }; - block.push(stmt, span); - emitter.start(ctx.expressions); - - // Store any associated global variables so we can upgrade their types later - self.record_atomic_access(ctx, p_lexp_handle)?; - } - Op::AtomicIIncrement | Op::AtomicIDecrement => { - inst.expect(6)?; - let start = self.data_offset; - let result_type_id = self.next()?; - let result_id = self.next()?; - let pointer_id = self.next()?; - let _scope_id = self.next()?; - let _memory_semantics_id = self.next()?; - let span = self.span_from_with_op(start); - - let (p_exp_h, p_base_ty_h) = self.get_exp_and_base_ty_handles( - pointer_id, - ctx, - &mut emitter, - &mut block, - body_idx, - )?; - - block.extend(emitter.finish(ctx.expressions)); - // Create an expression for our result - let r_lexp_handle = { - let expr = crate::Expression::AtomicResult { - ty: p_base_ty_h, - comparison: false, - }; - let handle = ctx.expressions.append(expr, span); - self.lookup_expression.insert( - result_id, - LookupExpression { - handle, - type_id: result_type_id, - block_id, - }, - ); - handle - }; - emitter.start(ctx.expressions); - - // Create a literal "1" to use as our value - let one_lexp_handle = make_index_literal( - ctx, - 1, - &mut block, - &mut emitter, - p_base_ty_h, - result_type_id, - span, - )?; - - // Create a statement for the op itself - let stmt = crate::Statement::Atomic { - pointer: p_exp_h, - fun: match inst.op { - Op::AtomicIIncrement => crate::AtomicFunction::Add, - _ => crate::AtomicFunction::Subtract, - }, - value: one_lexp_handle, - result: Some(r_lexp_handle), - }; - block.push(stmt, span); - - // Store any associated global variables so we can upgrade their types later - self.record_atomic_access(ctx, p_exp_h)?; - } - Op::AtomicCompareExchange => { - inst.expect(9)?; - - let start = self.data_offset; - let span = self.span_from_with_op(start); - let result_type_id = self.next()?; - let result_id = self.next()?; - let pointer_id = self.next()?; - let _memory_scope_id = self.next()?; - let _equal_memory_semantics_id = self.next()?; - let _unequal_memory_semantics_id = self.next()?; - let value_id = self.next()?; - let comparator_id = self.next()?; - - let (p_exp_h, p_base_ty_h) = self.get_exp_and_base_ty_handles( - pointer_id, - ctx, - &mut emitter, - &mut block, - body_idx, - )?; - - log::trace!("\t\t\tlooking up value expr {value_id:?}"); - let v_lexp_handle = - get_expr_handle!(value_id, self.lookup_expression.lookup(value_id)?); - - log::trace!("\t\t\tlooking up comparator expr {value_id:?}"); - let c_lexp_handle = get_expr_handle!( - comparator_id, - self.lookup_expression.lookup(comparator_id)? - ); - - // We know from the SPIR-V spec that the result type must be an integer - // scalar, and we'll need the type itself to get a handle to the atomic - // result struct. - let crate::TypeInner::Scalar(scalar) = ctx.module.types[p_base_ty_h].inner - else { - return Err( - crate::front::atomic_upgrade::Error::CompareExchangeNonScalarBaseType - .into(), - ); - }; - - // Get a handle to the atomic result struct type. - let atomic_result_struct_ty_h = ctx.module.generate_predeclared_type( - crate::PredeclaredType::AtomicCompareExchangeWeakResult(scalar), - ); - - block.extend(emitter.finish(ctx.expressions)); - - // Create an expression for our atomic result - let atomic_lexp_handle = { - let expr = crate::Expression::AtomicResult { - ty: atomic_result_struct_ty_h, - comparison: true, - }; - ctx.expressions.append(expr, span) - }; - - // Create an dot accessor to extract the value from the - // result struct __atomic_compare_exchange_result<T> and use that - // as the expression for the result_id - { - let expr = crate::Expression::AccessIndex { - base: atomic_lexp_handle, - index: 0, - }; - let handle = ctx.expressions.append(expr, span); - // Use this dot accessor as the result id's expression - let _ = self.lookup_expression.insert( - result_id, - LookupExpression { - handle, - type_id: result_type_id, - block_id, - }, - ); - } - - emitter.start(ctx.expressions); - - // Create a statement for the op itself - let stmt = crate::Statement::Atomic { - pointer: p_exp_h, - fun: crate::AtomicFunction::Exchange { - compare: Some(c_lexp_handle), - }, - value: v_lexp_handle, - result: Some(atomic_lexp_handle), - }; - block.push(stmt, span); - - // Store any associated global variables so we can upgrade their types later - self.record_atomic_access(ctx, p_exp_h)?; - } - Op::AtomicExchange - | Op::AtomicIAdd - | Op::AtomicISub - | Op::AtomicSMin - | Op::AtomicUMin - | Op::AtomicSMax - | Op::AtomicUMax - | Op::AtomicAnd - | Op::AtomicOr - | Op::AtomicXor - | Op::AtomicFAddEXT => self.parse_atomic_expr_with_value( - inst, - &mut emitter, - ctx, - &mut block, - block_id, - body_idx, - match inst.op { - Op::AtomicExchange => crate::AtomicFunction::Exchange { compare: None }, - Op::AtomicIAdd | Op::AtomicFAddEXT => crate::AtomicFunction::Add, - Op::AtomicISub => crate::AtomicFunction::Subtract, - Op::AtomicSMin => crate::AtomicFunction::Min, - Op::AtomicUMin => crate::AtomicFunction::Min, - Op::AtomicSMax => crate::AtomicFunction::Max, - Op::AtomicUMax => crate::AtomicFunction::Max, - Op::AtomicAnd => crate::AtomicFunction::And, - Op::AtomicOr => crate::AtomicFunction::InclusiveOr, - Op::AtomicXor => crate::AtomicFunction::ExclusiveOr, - _ => unreachable!(), - }, - )?, - - _ => { - return Err(Error::UnsupportedInstruction(self.state, inst.op)); - } - } - }; - - block.extend(emitter.finish(ctx.expressions)); - if let Some(stmt) = terminator { - block.push(stmt, crate::Span::default()); - } - - // Save this block fragment in `block_ctx.blocks`, and mark it to be - // incorporated into the current body at `Statement` assembly time. - ctx.blocks.insert(block_id, block); - let body = &mut ctx.bodies[body_idx]; - body.data.push(BodyFragment::BlockId(block_id)); - Ok(()) - } - fn make_expression_storage( &mut self, globals: &Arena<crate::GlobalVariable>, @@ -4746,7 +1675,7 @@ impl<I: Iterator<Item = u32>> Frontend<I> { let generator = self.next()?; let _bound = self.next()?; let _schema = self.next()?; - log::info!("Generated by {generator} version {version_raw:x}"); + log::debug!("Generated by {generator} version {version_raw:x}"); crate::Module::default() }; @@ -4816,7 +1745,7 @@ impl<I: Iterator<Item = u32>> Frontend<I> { } if !self.upgrade_atomics.is_empty() { - log::info!("Upgrading atomic pointers..."); + log::debug!("Upgrading atomic pointers..."); module.upgrade_atomics(&self.upgrade_atomics)?; } @@ -4826,7 +1755,7 @@ impl<I: Iterator<Item = u32>> Frontend<I> { self.process_entry_point(&mut module, ep, fun_id)?; } - log::info!("Patching..."); + log::debug!("Patching..."); { let mut nodes = petgraph::algo::toposort(&self.function_call_graph, None) .map_err(|cycle| Error::FunctionCallCycle(cycle.node_id()))?; @@ -4866,11 +1795,11 @@ impl<I: Iterator<Item = u32>> Frontend<I> { } if !self.future_decor.is_empty() { - log::warn!("Unused item decorations: {:?}", self.future_decor); + log::debug!("Unused item decorations: {:?}", self.future_decor); self.future_decor.clear(); } if !self.future_member_decor.is_empty() { - log::warn!("Unused member decorations: {:?}", self.future_member_decor); + log::debug!("Unused member decorations: {:?}", self.future_member_decor); self.future_member_decor.clear(); } @@ -6203,30 +3132,6 @@ impl<I: Iterator<Item = u32>> Frontend<I> { } } -fn make_index_literal( - ctx: &mut BlockContext, - index: u32, - block: &mut crate::Block, - emitter: &mut crate::proc::Emitter, - index_type: Handle<crate::Type>, - index_type_id: spirv::Word, - span: crate::Span, -) -> Result<Handle<crate::Expression>, Error> { - block.extend(emitter.finish(ctx.expressions)); - - let literal = match ctx.module.types[index_type].inner.scalar_kind() { - Some(crate::ScalarKind::Uint) => crate::Literal::U32(index), - Some(crate::ScalarKind::Sint) => crate::Literal::I32(index as i32), - _ => return Err(Error::InvalidIndexType(index_type_id)), - }; - let expr = ctx - .expressions - .append(crate::Expression::Literal(literal), span); - - emitter.start(ctx.expressions); - Ok(expr) -} - fn resolve_constant(gctx: crate::proc::GlobalCtx, constant: &Constant) -> Option<u32> { let constant = match *constant { Constant::Constant(constant) => constant, diff --git a/third_party/rust/naga/src/front/spv/next_block.rs b/third_party/rust/naga/src/front/spv/next_block.rs @@ -0,0 +1,3123 @@ +//! Implementation of [`Frontend::next_block()`]. +//! +//! This method is split out into its own module purely because it is so long. + +use alloc::{format, vec, vec::Vec}; + +use crate::front::spv::{ + convert::{map_binary_operator, map_relational_fun}, + image, resolve_constant, BlockContext, Body, BodyFragment, Constant, Error, Frontend, + LookupExpression, LookupHelper as _, LookupLoadOverride, MergeBlockInformation, PhiExpression, + SignAnchor, +}; +use crate::Handle; + +impl<I: Iterator<Item = u32>> Frontend<I> { + /// Add the next SPIR-V block's contents to `block_ctx`. + /// + /// Except for the function's entry block, `block_id` should be the label of + /// a block we've seen mentioned before, with an entry in + /// `block_ctx.body_for_label` to tell us which `Body` it contributes to. + pub(in crate::front::spv) fn next_block( + &mut self, + block_id: spirv::Word, + ctx: &mut BlockContext, + ) -> Result<(), Error> { + // Extend `body` with the correct form for a branch to `target`. + fn merger(body: &mut Body, target: &MergeBlockInformation) { + body.data.push(match *target { + MergeBlockInformation::LoopContinue => BodyFragment::Continue, + MergeBlockInformation::LoopMerge | MergeBlockInformation::SwitchMerge => { + BodyFragment::Break + } + + // Finishing a selection merge means just falling off the end of + // the `accept` or `reject` block of the `If` statement. + MergeBlockInformation::SelectionMerge => return, + }) + } + + let mut emitter = crate::proc::Emitter::default(); + emitter.start(ctx.expressions); + + // Find the `Body` to which this block contributes. + // + // If this is some SPIR-V structured control flow construct's merge + // block, then `body_idx` will refer to the same `Body` as the header, + // so that we simply pick up accumulating the `Body` where the header + // left off. Each of the statements in a block dominates the next, so + // we're sure to encounter their SPIR-V blocks in order, ensuring that + // the `Body` will be assembled in the proper order. + // + // Note that, unlike every other kind of SPIR-V block, we don't know the + // function's first block's label in advance. Thus, we assume that if + // this block has no entry in `ctx.body_for_label`, it must be the + // function's first block. This always has body index zero. + let mut body_idx = *ctx.body_for_label.entry(block_id).or_default(); + + // The Naga IR block this call builds. This will end up as + // `ctx.blocks[&block_id]`, and `ctx.bodies[body_idx]` will refer to it + // via a `BodyFragment::BlockId`. + let mut block = crate::Block::new(); + + // Stores the merge block as defined by a `OpSelectionMerge` otherwise is `None` + // + // This is used in `OpSwitch` to promote the `MergeBlockInformation` from + // `SelectionMerge` to `SwitchMerge` to allow `Break`s this isn't desirable for + // `LoopMerge`s because otherwise `Continue`s wouldn't be allowed + let mut selection_merge_block = None; + + macro_rules! get_expr_handle { + ($id:expr, $lexp:expr) => { + self.get_expr_handle($id, $lexp, ctx, &mut emitter, &mut block, body_idx) + }; + } + macro_rules! parse_expr_op { + ($op:expr, BINARY) => { + self.parse_expr_binary_op(ctx, &mut emitter, &mut block, block_id, body_idx, $op) + }; + + ($op:expr, SHIFT) => { + self.parse_expr_shift_op(ctx, &mut emitter, &mut block, block_id, body_idx, $op) + }; + ($op:expr, UNARY) => { + self.parse_expr_unary_op(ctx, &mut emitter, &mut block, block_id, body_idx, $op) + }; + ($axis:expr, $ctrl:expr, DERIVATIVE) => { + self.parse_expr_derivative( + ctx, + &mut emitter, + &mut block, + block_id, + body_idx, + ($axis, $ctrl), + ) + }; + } + + let terminator = loop { + use spirv::Op; + let start = self.data_offset; + let inst = self.next_inst()?; + let span = crate::Span::from(start..(start + 4 * (inst.wc as usize))); + log::debug!("\t\t{:?} [{}]", inst.op, inst.wc); + + match inst.op { + Op::Line => { + inst.expect(4)?; + let _file_id = self.next()?; + let _row_id = self.next()?; + let _col_id = self.next()?; + } + Op::NoLine => inst.expect(1)?, + Op::Undef => { + inst.expect(3)?; + let type_id = self.next()?; + let id = self.next()?; + let type_lookup = self.lookup_type.lookup(type_id)?; + let ty = type_lookup.handle; + + self.lookup_expression.insert( + id, + LookupExpression { + handle: ctx + .expressions + .append(crate::Expression::ZeroValue(ty), span), + type_id, + block_id, + }, + ); + } + Op::Variable => { + inst.expect_at_least(4)?; + block.extend(emitter.finish(ctx.expressions)); + + let result_type_id = self.next()?; + let result_id = self.next()?; + let _storage_class = self.next()?; + let init = if inst.wc > 4 { + inst.expect(5)?; + let init_id = self.next()?; + let lconst = self.lookup_constant.lookup(init_id)?; + Some(ctx.expressions.append(lconst.inner.to_expr(), span)) + } else { + None + }; + + let name = self + .future_decor + .remove(&result_id) + .and_then(|decor| decor.name); + if let Some(ref name) = name { + log::debug!("\t\t\tid={result_id} name={name}"); + } + let lookup_ty = self.lookup_type.lookup(result_type_id)?; + let var_handle = ctx.local_arena.append( + crate::LocalVariable { + name, + ty: match ctx.module.types[lookup_ty.handle].inner { + crate::TypeInner::Pointer { base, .. } => base, + _ => lookup_ty.handle, + }, + init, + }, + span, + ); + + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx + .expressions + .append(crate::Expression::LocalVariable(var_handle), span), + type_id: result_type_id, + block_id, + }, + ); + emitter.start(ctx.expressions); + } + Op::Phi => { + inst.expect_at_least(3)?; + block.extend(emitter.finish(ctx.expressions)); + + let result_type_id = self.next()?; + let result_id = self.next()?; + + let name = format!("phi_{result_id}"); + let local = ctx.local_arena.append( + crate::LocalVariable { + name: Some(name), + ty: self.lookup_type.lookup(result_type_id)?.handle, + init: None, + }, + self.span_from(start), + ); + let pointer = ctx + .expressions + .append(crate::Expression::LocalVariable(local), span); + + let in_count = (inst.wc - 3) / 2; + let mut phi = PhiExpression { + local, + expressions: Vec::with_capacity(in_count as usize), + }; + for _ in 0..in_count { + let expr = self.next()?; + let block = self.next()?; + phi.expressions.push((expr, block)); + } + + ctx.phis.push(phi); + emitter.start(ctx.expressions); + + // Associate the lookup with an actual value, which is emitted + // into the current block. + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx + .expressions + .append(crate::Expression::Load { pointer }, span), + type_id: result_type_id, + block_id, + }, + ); + } + Op::AccessChain | Op::InBoundsAccessChain => { + struct AccessExpression { + base_handle: Handle<crate::Expression>, + type_id: spirv::Word, + load_override: Option<LookupLoadOverride>, + } + + inst.expect_at_least(4)?; + + let result_type_id = self.next()?; + let result_id = self.next()?; + let base_id = self.next()?; + log::trace!("\t\t\tlooking up expr {base_id:?}"); + + let mut acex = { + let lexp = self.lookup_expression.lookup(base_id)?; + let lty = self.lookup_type.lookup(lexp.type_id)?; + + // HACK `OpAccessChain` and `OpInBoundsAccessChain` + // require for the result type to be a pointer, but if + // we're given a pointer to an image / sampler, it will + // be *already* dereferenced, since we do that early + // during `parse_type_pointer()`. + // + // This can happen only through `BindingArray`, since + // that's the only case where one can obtain a pointer + // to an image / sampler, and so let's match on that: + let dereference = match ctx.module.types[lty.handle].inner { + crate::TypeInner::BindingArray { .. } => false, + _ => true, + }; + + let type_id = if dereference { + lty.base_id.ok_or(Error::InvalidAccessType(lexp.type_id))? + } else { + lexp.type_id + }; + + AccessExpression { + base_handle: get_expr_handle!(base_id, lexp), + type_id, + load_override: self.lookup_load_override.get(&base_id).cloned(), + } + }; + + for _ in 4..inst.wc { + let access_id = self.next()?; + log::trace!("\t\t\tlooking up index expr {access_id:?}"); + let index_expr = self.lookup_expression.lookup(access_id)?.clone(); + let index_expr_handle = get_expr_handle!(access_id, &index_expr); + let index_expr_data = &ctx.expressions[index_expr.handle]; + let index_maybe = match *index_expr_data { + crate::Expression::Constant(const_handle) => Some( + ctx.gctx() + .eval_expr_to_u32(ctx.module.constants[const_handle].init) + .map_err(|_| { + Error::InvalidAccess(crate::Expression::Constant( + const_handle, + )) + })?, + ), + _ => None, + }; + + log::trace!("\t\t\tlooking up type {:?}", acex.type_id); + let type_lookup = self.lookup_type.lookup(acex.type_id)?; + let ty = &ctx.module.types[type_lookup.handle]; + acex = match ty.inner { + // can only index a struct with a constant + crate::TypeInner::Struct { ref members, .. } => { + let index = index_maybe + .ok_or_else(|| Error::InvalidAccess(index_expr_data.clone()))?; + + let lookup_member = self + .lookup_member + .get(&(type_lookup.handle, index)) + .ok_or(Error::InvalidAccessType(acex.type_id))?; + let base_handle = ctx.expressions.append( + crate::Expression::AccessIndex { + base: acex.base_handle, + index, + }, + span, + ); + + if let Some(crate::Binding::BuiltIn(built_in)) = + members[index as usize].binding + { + self.gl_per_vertex_builtin_access.insert(built_in); + } + + AccessExpression { + base_handle, + type_id: lookup_member.type_id, + load_override: if lookup_member.row_major { + debug_assert!(acex.load_override.is_none()); + let sub_type_lookup = + self.lookup_type.lookup(lookup_member.type_id)?; + Some(match ctx.module.types[sub_type_lookup.handle].inner { + // load it transposed, to match column major expectations + crate::TypeInner::Matrix { .. } => { + let loaded = ctx.expressions.append( + crate::Expression::Load { + pointer: base_handle, + }, + span, + ); + let transposed = ctx.expressions.append( + crate::Expression::Math { + fun: crate::MathFunction::Transpose, + arg: loaded, + arg1: None, + arg2: None, + arg3: None, + }, + span, + ); + LookupLoadOverride::Loaded(transposed) + } + _ => LookupLoadOverride::Pending, + }) + } else { + None + }, + } + } + crate::TypeInner::Matrix { .. } => { + let load_override = match acex.load_override { + // We are indexing inside a row-major matrix + Some(LookupLoadOverride::Loaded(load_expr)) => { + let index = index_maybe.ok_or_else(|| { + Error::InvalidAccess(index_expr_data.clone()) + })?; + let sub_handle = ctx.expressions.append( + crate::Expression::AccessIndex { + base: load_expr, + index, + }, + span, + ); + Some(LookupLoadOverride::Loaded(sub_handle)) + } + _ => None, + }; + let sub_expr = match index_maybe { + Some(index) => crate::Expression::AccessIndex { + base: acex.base_handle, + index, + }, + None => crate::Expression::Access { + base: acex.base_handle, + index: index_expr_handle, + }, + }; + AccessExpression { + base_handle: ctx.expressions.append(sub_expr, span), + type_id: type_lookup + .base_id + .ok_or(Error::InvalidAccessType(acex.type_id))?, + load_override, + } + } + // This must be a vector or an array. + _ => { + let base_handle = ctx.expressions.append( + crate::Expression::Access { + base: acex.base_handle, + index: index_expr_handle, + }, + span, + ); + let load_override = match acex.load_override { + // If there is a load override in place, then we always end up + // with a side-loaded value here. + Some(lookup_load_override) => { + let sub_expr = match lookup_load_override { + // We must be indexing into the array of row-major matrices. + // Let's load the result of indexing and transpose it. + LookupLoadOverride::Pending => { + let loaded = ctx.expressions.append( + crate::Expression::Load { + pointer: base_handle, + }, + span, + ); + ctx.expressions.append( + crate::Expression::Math { + fun: crate::MathFunction::Transpose, + arg: loaded, + arg1: None, + arg2: None, + arg3: None, + }, + span, + ) + } + // We are indexing inside a row-major matrix. + LookupLoadOverride::Loaded(load_expr) => { + ctx.expressions.append( + crate::Expression::Access { + base: load_expr, + index: index_expr_handle, + }, + span, + ) + } + }; + Some(LookupLoadOverride::Loaded(sub_expr)) + } + None => None, + }; + AccessExpression { + base_handle, + type_id: type_lookup + .base_id + .ok_or(Error::InvalidAccessType(acex.type_id))?, + load_override, + } + } + }; + } + + if let Some(load_expr) = acex.load_override { + self.lookup_load_override.insert(result_id, load_expr); + } + let lookup_expression = LookupExpression { + handle: acex.base_handle, + type_id: result_type_id, + block_id, + }; + self.lookup_expression.insert(result_id, lookup_expression); + } + Op::VectorExtractDynamic => { + inst.expect(5)?; + + let result_type_id = self.next()?; + let id = self.next()?; + let composite_id = self.next()?; + let index_id = self.next()?; + + let root_lexp = self.lookup_expression.lookup(composite_id)?; + let root_handle = get_expr_handle!(composite_id, root_lexp); + let root_type_lookup = self.lookup_type.lookup(root_lexp.type_id)?; + let index_lexp = self.lookup_expression.lookup(index_id)?; + let index_handle = get_expr_handle!(index_id, index_lexp); + let index_type = self.lookup_type.lookup(index_lexp.type_id)?.handle; + + let num_components = match ctx.module.types[root_type_lookup.handle].inner { + crate::TypeInner::Vector { size, .. } => size as u32, + _ => return Err(Error::InvalidVectorType(root_type_lookup.handle)), + }; + + let mut make_index = |ctx: &mut BlockContext, index: u32| { + make_index_literal( + ctx, + index, + &mut block, + &mut emitter, + index_type, + index_lexp.type_id, + span, + ) + }; + + let index_expr = make_index(ctx, 0)?; + let mut handle = ctx.expressions.append( + crate::Expression::Access { + base: root_handle, + index: index_expr, + }, + span, + ); + for index in 1..num_components { + let index_expr = make_index(ctx, index)?; + let access_expr = ctx.expressions.append( + crate::Expression::Access { + base: root_handle, + index: index_expr, + }, + span, + ); + let cond = ctx.expressions.append( + crate::Expression::Binary { + op: crate::BinaryOperator::Equal, + left: index_expr, + right: index_handle, + }, + span, + ); + handle = ctx.expressions.append( + crate::Expression::Select { + condition: cond, + accept: access_expr, + reject: handle, + }, + span, + ); + } + + self.lookup_expression.insert( + id, + LookupExpression { + handle, + type_id: result_type_id, + block_id, + }, + ); + } + Op::VectorInsertDynamic => { + inst.expect(6)?; + + let result_type_id = self.next()?; + let id = self.next()?; + let composite_id = self.next()?; + let object_id = self.next()?; + let index_id = self.next()?; + + let object_lexp = self.lookup_expression.lookup(object_id)?; + let object_handle = get_expr_handle!(object_id, object_lexp); + let root_lexp = self.lookup_expression.lookup(composite_id)?; + let root_handle = get_expr_handle!(composite_id, root_lexp); + let root_type_lookup = self.lookup_type.lookup(root_lexp.type_id)?; + let index_lexp = self.lookup_expression.lookup(index_id)?; + let index_handle = get_expr_handle!(index_id, index_lexp); + let index_type = self.lookup_type.lookup(index_lexp.type_id)?.handle; + + let num_components = match ctx.module.types[root_type_lookup.handle].inner { + crate::TypeInner::Vector { size, .. } => size as u32, + _ => return Err(Error::InvalidVectorType(root_type_lookup.handle)), + }; + + let mut components = Vec::with_capacity(num_components as usize); + for index in 0..num_components { + let index_expr = make_index_literal( + ctx, + index, + &mut block, + &mut emitter, + index_type, + index_lexp.type_id, + span, + )?; + let access_expr = ctx.expressions.append( + crate::Expression::Access { + base: root_handle, + index: index_expr, + }, + span, + ); + let cond = ctx.expressions.append( + crate::Expression::Binary { + op: crate::BinaryOperator::Equal, + left: index_expr, + right: index_handle, + }, + span, + ); + let handle = ctx.expressions.append( + crate::Expression::Select { + condition: cond, + accept: object_handle, + reject: access_expr, + }, + span, + ); + components.push(handle); + } + let handle = ctx.expressions.append( + crate::Expression::Compose { + ty: root_type_lookup.handle, + components, + }, + span, + ); + + self.lookup_expression.insert( + id, + LookupExpression { + handle, + type_id: result_type_id, + block_id, + }, + ); + } + Op::CompositeExtract => { + inst.expect_at_least(4)?; + + let result_type_id = self.next()?; + let result_id = self.next()?; + let base_id = self.next()?; + log::trace!("\t\t\tlooking up expr {base_id:?}"); + let mut lexp = self.lookup_expression.lookup(base_id)?.clone(); + lexp.handle = get_expr_handle!(base_id, &lexp); + for _ in 4..inst.wc { + let index = self.next()?; + log::trace!("\t\t\tlooking up type {:?}", lexp.type_id); + let type_lookup = self.lookup_type.lookup(lexp.type_id)?; + let type_id = match ctx.module.types[type_lookup.handle].inner { + crate::TypeInner::Struct { .. } => { + self.lookup_member + .get(&(type_lookup.handle, index)) + .ok_or(Error::InvalidAccessType(lexp.type_id))? + .type_id + } + crate::TypeInner::Array { .. } + | crate::TypeInner::Vector { .. } + | crate::TypeInner::Matrix { .. } => type_lookup + .base_id + .ok_or(Error::InvalidAccessType(lexp.type_id))?, + ref other => { + log::warn!("composite type {other:?}"); + return Err(Error::UnsupportedType(type_lookup.handle)); + } + }; + lexp = LookupExpression { + handle: ctx.expressions.append( + crate::Expression::AccessIndex { + base: lexp.handle, + index, + }, + span, + ), + type_id, + block_id, + }; + } + + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: lexp.handle, + type_id: result_type_id, + block_id, + }, + ); + } + Op::CompositeInsert => { + inst.expect_at_least(5)?; + + let result_type_id = self.next()?; + let id = self.next()?; + let object_id = self.next()?; + let composite_id = self.next()?; + let mut selections = Vec::with_capacity(inst.wc as usize - 5); + for _ in 5..inst.wc { + selections.push(self.next()?); + } + + let object_lexp = self.lookup_expression.lookup(object_id)?.clone(); + let object_handle = get_expr_handle!(object_id, &object_lexp); + let root_lexp = self.lookup_expression.lookup(composite_id)?.clone(); + let root_handle = get_expr_handle!(composite_id, &root_lexp); + let handle = self.insert_composite( + root_handle, + result_type_id, + object_handle, + &selections, + &ctx.module.types, + ctx.expressions, + span, + )?; + + self.lookup_expression.insert( + id, + LookupExpression { + handle, + type_id: result_type_id, + block_id, + }, + ); + } + Op::CompositeConstruct => { + inst.expect_at_least(3)?; + + let result_type_id = self.next()?; + let id = self.next()?; + let mut components = Vec::with_capacity(inst.wc as usize - 2); + for _ in 3..inst.wc { + let comp_id = self.next()?; + log::trace!("\t\t\tlooking up expr {comp_id:?}"); + let lexp = self.lookup_expression.lookup(comp_id)?; + let handle = get_expr_handle!(comp_id, lexp); + components.push(handle); + } + let ty = self.lookup_type.lookup(result_type_id)?.handle; + let first = components[0]; + let expr = match ctx.module.types[ty].inner { + // this is an optimization to detect the splat + crate::TypeInner::Vector { size, .. } + if components.len() == size as usize + && components[1..].iter().all(|&c| c == first) => + { + crate::Expression::Splat { size, value: first } + } + _ => crate::Expression::Compose { ty, components }, + }; + self.lookup_expression.insert( + id, + LookupExpression { + handle: ctx.expressions.append(expr, span), + type_id: result_type_id, + block_id, + }, + ); + } + Op::Load => { + inst.expect_at_least(4)?; + + let result_type_id = self.next()?; + let result_id = self.next()?; + let pointer_id = self.next()?; + if inst.wc != 4 { + inst.expect(5)?; + let _memory_access = self.next()?; + } + + let base_lexp = self.lookup_expression.lookup(pointer_id)?; + let base_handle = get_expr_handle!(pointer_id, base_lexp); + let type_lookup = self.lookup_type.lookup(base_lexp.type_id)?; + let handle = match ctx.module.types[type_lookup.handle].inner { + crate::TypeInner::Image { .. } | crate::TypeInner::Sampler { .. } => { + base_handle + } + _ => match self.lookup_load_override.get(&pointer_id) { + Some(&LookupLoadOverride::Loaded(handle)) => handle, + //Note: we aren't handling `LookupLoadOverride::Pending` properly here + _ => ctx.expressions.append( + crate::Expression::Load { + pointer: base_handle, + }, + span, + ), + }, + }; + + self.lookup_expression.insert( + result_id, + LookupExpression { + handle, + type_id: result_type_id, + block_id, + }, + ); + } + Op::Store => { + inst.expect_at_least(3)?; + + let pointer_id = self.next()?; + let value_id = self.next()?; + if inst.wc != 3 { + inst.expect(4)?; + let _memory_access = self.next()?; + } + let base_expr = self.lookup_expression.lookup(pointer_id)?; + let base_handle = get_expr_handle!(pointer_id, base_expr); + let value_expr = self.lookup_expression.lookup(value_id)?; + let value_handle = get_expr_handle!(value_id, value_expr); + + block.extend(emitter.finish(ctx.expressions)); + block.push( + crate::Statement::Store { + pointer: base_handle, + value: value_handle, + }, + span, + ); + emitter.start(ctx.expressions); + } + // Arithmetic Instructions +, -, *, /, % + Op::SNegate | Op::FNegate => { + inst.expect(4)?; + self.parse_expr_unary_op_sign_adjusted( + ctx, + &mut emitter, + &mut block, + block_id, + body_idx, + crate::UnaryOperator::Negate, + )?; + } + Op::IAdd + | Op::ISub + | Op::IMul + | Op::BitwiseOr + | Op::BitwiseXor + | Op::BitwiseAnd + | Op::SDiv + | Op::SRem => { + inst.expect(5)?; + let operator = map_binary_operator(inst.op)?; + self.parse_expr_binary_op_sign_adjusted( + ctx, + &mut emitter, + &mut block, + block_id, + body_idx, + operator, + SignAnchor::Result, + )?; + } + Op::IEqual | Op::INotEqual => { + inst.expect(5)?; + let operator = map_binary_operator(inst.op)?; + self.parse_expr_binary_op_sign_adjusted( + ctx, + &mut emitter, + &mut block, + block_id, + body_idx, + operator, + SignAnchor::Operand, + )?; + } + Op::FAdd => { + inst.expect(5)?; + parse_expr_op!(crate::BinaryOperator::Add, BINARY)?; + } + Op::FSub => { + inst.expect(5)?; + parse_expr_op!(crate::BinaryOperator::Subtract, BINARY)?; + } + Op::FMul => { + inst.expect(5)?; + parse_expr_op!(crate::BinaryOperator::Multiply, BINARY)?; + } + Op::UDiv | Op::FDiv => { + inst.expect(5)?; + parse_expr_op!(crate::BinaryOperator::Divide, BINARY)?; + } + Op::UMod | Op::FRem => { + inst.expect(5)?; + parse_expr_op!(crate::BinaryOperator::Modulo, BINARY)?; + } + Op::SMod => { + inst.expect(5)?; + + // x - y * int(floor(float(x) / float(y))) + + let start = self.data_offset; + let result_type_id = self.next()?; + let result_id = self.next()?; + let p1_id = self.next()?; + let p2_id = self.next()?; + let span = self.span_from_with_op(start); + + let p1_lexp = self.lookup_expression.lookup(p1_id)?; + let left = self.get_expr_handle( + p1_id, + p1_lexp, + ctx, + &mut emitter, + &mut block, + body_idx, + ); + let p2_lexp = self.lookup_expression.lookup(p2_id)?; + let right = self.get_expr_handle( + p2_id, + p2_lexp, + ctx, + &mut emitter, + &mut block, + body_idx, + ); + + let result_ty = self.lookup_type.lookup(result_type_id)?; + let inner = &ctx.module.types[result_ty.handle].inner; + let kind = inner.scalar_kind().unwrap(); + let size = inner.size(ctx.gctx()) as u8; + + let left_cast = ctx.expressions.append( + crate::Expression::As { + expr: left, + kind: crate::ScalarKind::Float, + convert: Some(size), + }, + span, + ); + let right_cast = ctx.expressions.append( + crate::Expression::As { + expr: right, + kind: crate::ScalarKind::Float, + convert: Some(size), + }, + span, + ); + let div = ctx.expressions.append( + crate::Expression::Binary { + op: crate::BinaryOperator::Divide, + left: left_cast, + right: right_cast, + }, + span, + ); + let floor = ctx.expressions.append( + crate::Expression::Math { + fun: crate::MathFunction::Floor, + arg: div, + arg1: None, + arg2: None, + arg3: None, + }, + span, + ); + let cast = ctx.expressions.append( + crate::Expression::As { + expr: floor, + kind, + convert: Some(size), + }, + span, + ); + let mult = ctx.expressions.append( + crate::Expression::Binary { + op: crate::BinaryOperator::Multiply, + left: cast, + right, + }, + span, + ); + let sub = ctx.expressions.append( + crate::Expression::Binary { + op: crate::BinaryOperator::Subtract, + left, + right: mult, + }, + span, + ); + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: sub, + type_id: result_type_id, + block_id, + }, + ); + } + Op::FMod => { + inst.expect(5)?; + + // x - y * floor(x / y) + + let start = self.data_offset; + let span = self.span_from_with_op(start); + + let result_type_id = self.next()?; + let result_id = self.next()?; + let p1_id = self.next()?; + let p2_id = self.next()?; + + let p1_lexp = self.lookup_expression.lookup(p1_id)?; + let left = self.get_expr_handle( + p1_id, + p1_lexp, + ctx, + &mut emitter, + &mut block, + body_idx, + ); + let p2_lexp = self.lookup_expression.lookup(p2_id)?; + let right = self.get_expr_handle( + p2_id, + p2_lexp, + ctx, + &mut emitter, + &mut block, + body_idx, + ); + + let div = ctx.expressions.append( + crate::Expression::Binary { + op: crate::BinaryOperator::Divide, + left, + right, + }, + span, + ); + let floor = ctx.expressions.append( + crate::Expression::Math { + fun: crate::MathFunction::Floor, + arg: div, + arg1: None, + arg2: None, + arg3: None, + }, + span, + ); + let mult = ctx.expressions.append( + crate::Expression::Binary { + op: crate::BinaryOperator::Multiply, + left: floor, + right, + }, + span, + ); + let sub = ctx.expressions.append( + crate::Expression::Binary { + op: crate::BinaryOperator::Subtract, + left, + right: mult, + }, + span, + ); + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: sub, + type_id: result_type_id, + block_id, + }, + ); + } + Op::VectorTimesScalar + | Op::VectorTimesMatrix + | Op::MatrixTimesScalar + | Op::MatrixTimesVector + | Op::MatrixTimesMatrix => { + inst.expect(5)?; + parse_expr_op!(crate::BinaryOperator::Multiply, BINARY)?; + } + Op::Transpose => { + inst.expect(4)?; + + let result_type_id = self.next()?; + let result_id = self.next()?; + let matrix_id = self.next()?; + let matrix_lexp = self.lookup_expression.lookup(matrix_id)?; + let matrix_handle = get_expr_handle!(matrix_id, matrix_lexp); + let expr = crate::Expression::Math { + fun: crate::MathFunction::Transpose, + arg: matrix_handle, + arg1: None, + arg2: None, + arg3: None, + }; + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, span), + type_id: result_type_id, + block_id, + }, + ); + } + Op::Dot => { + inst.expect(5)?; + + let result_type_id = self.next()?; + let result_id = self.next()?; + let left_id = self.next()?; + let right_id = self.next()?; + let left_lexp = self.lookup_expression.lookup(left_id)?; + let left_handle = get_expr_handle!(left_id, left_lexp); + let right_lexp = self.lookup_expression.lookup(right_id)?; + let right_handle = get_expr_handle!(right_id, right_lexp); + let expr = crate::Expression::Math { + fun: crate::MathFunction::Dot, + arg: left_handle, + arg1: Some(right_handle), + arg2: None, + arg3: None, + }; + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, span), + type_id: result_type_id, + block_id, + }, + ); + } + Op::BitFieldInsert => { + inst.expect(7)?; + + let start = self.data_offset; + let span = self.span_from_with_op(start); + + let result_type_id = self.next()?; + let result_id = self.next()?; + let base_id = self.next()?; + let insert_id = self.next()?; + let offset_id = self.next()?; + let count_id = self.next()?; + let base_lexp = self.lookup_expression.lookup(base_id)?; + let base_handle = get_expr_handle!(base_id, base_lexp); + let insert_lexp = self.lookup_expression.lookup(insert_id)?; + let insert_handle = get_expr_handle!(insert_id, insert_lexp); + let offset_lexp = self.lookup_expression.lookup(offset_id)?; + let offset_handle = get_expr_handle!(offset_id, offset_lexp); + let offset_lookup_ty = self.lookup_type.lookup(offset_lexp.type_id)?; + let count_lexp = self.lookup_expression.lookup(count_id)?; + let count_handle = get_expr_handle!(count_id, count_lexp); + let count_lookup_ty = self.lookup_type.lookup(count_lexp.type_id)?; + + let offset_kind = ctx.module.types[offset_lookup_ty.handle] + .inner + .scalar_kind() + .unwrap(); + let count_kind = ctx.module.types[count_lookup_ty.handle] + .inner + .scalar_kind() + .unwrap(); + + let offset_cast_handle = if offset_kind != crate::ScalarKind::Uint { + ctx.expressions.append( + crate::Expression::As { + expr: offset_handle, + kind: crate::ScalarKind::Uint, + convert: None, + }, + span, + ) + } else { + offset_handle + }; + + let count_cast_handle = if count_kind != crate::ScalarKind::Uint { + ctx.expressions.append( + crate::Expression::As { + expr: count_handle, + kind: crate::ScalarKind::Uint, + convert: None, + }, + span, + ) + } else { + count_handle + }; + + let expr = crate::Expression::Math { + fun: crate::MathFunction::InsertBits, + arg: base_handle, + arg1: Some(insert_handle), + arg2: Some(offset_cast_handle), + arg3: Some(count_cast_handle), + }; + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, span), + type_id: result_type_id, + block_id, + }, + ); + } + Op::BitFieldSExtract | Op::BitFieldUExtract => { + inst.expect(6)?; + + let result_type_id = self.next()?; + let result_id = self.next()?; + let base_id = self.next()?; + let offset_id = self.next()?; + let count_id = self.next()?; + let base_lexp = self.lookup_expression.lookup(base_id)?; + let base_handle = get_expr_handle!(base_id, base_lexp); + let offset_lexp = self.lookup_expression.lookup(offset_id)?; + let offset_handle = get_expr_handle!(offset_id, offset_lexp); + let offset_lookup_ty = self.lookup_type.lookup(offset_lexp.type_id)?; + let count_lexp = self.lookup_expression.lookup(count_id)?; + let count_handle = get_expr_handle!(count_id, count_lexp); + let count_lookup_ty = self.lookup_type.lookup(count_lexp.type_id)?; + + let offset_kind = ctx.module.types[offset_lookup_ty.handle] + .inner + .scalar_kind() + .unwrap(); + let count_kind = ctx.module.types[count_lookup_ty.handle] + .inner + .scalar_kind() + .unwrap(); + + let offset_cast_handle = if offset_kind != crate::ScalarKind::Uint { + ctx.expressions.append( + crate::Expression::As { + expr: offset_handle, + kind: crate::ScalarKind::Uint, + convert: None, + }, + span, + ) + } else { + offset_handle + }; + + let count_cast_handle = if count_kind != crate::ScalarKind::Uint { + ctx.expressions.append( + crate::Expression::As { + expr: count_handle, + kind: crate::ScalarKind::Uint, + convert: None, + }, + span, + ) + } else { + count_handle + }; + + let expr = crate::Expression::Math { + fun: crate::MathFunction::ExtractBits, + arg: base_handle, + arg1: Some(offset_cast_handle), + arg2: Some(count_cast_handle), + arg3: None, + }; + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, span), + type_id: result_type_id, + block_id, + }, + ); + } + Op::BitReverse | Op::BitCount => { + inst.expect(4)?; + + let result_type_id = self.next()?; + let result_id = self.next()?; + let base_id = self.next()?; + let base_lexp = self.lookup_expression.lookup(base_id)?; + let base_handle = get_expr_handle!(base_id, base_lexp); + let expr = crate::Expression::Math { + fun: match inst.op { + Op::BitReverse => crate::MathFunction::ReverseBits, + Op::BitCount => crate::MathFunction::CountOneBits, + _ => unreachable!(), + }, + arg: base_handle, + arg1: None, + arg2: None, + arg3: None, + }; + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, span), + type_id: result_type_id, + block_id, + }, + ); + } + Op::OuterProduct => { + inst.expect(5)?; + + let result_type_id = self.next()?; + let result_id = self.next()?; + let left_id = self.next()?; + let right_id = self.next()?; + let left_lexp = self.lookup_expression.lookup(left_id)?; + let left_handle = get_expr_handle!(left_id, left_lexp); + let right_lexp = self.lookup_expression.lookup(right_id)?; + let right_handle = get_expr_handle!(right_id, right_lexp); + let expr = crate::Expression::Math { + fun: crate::MathFunction::Outer, + arg: left_handle, + arg1: Some(right_handle), + arg2: None, + arg3: None, + }; + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, span), + type_id: result_type_id, + block_id, + }, + ); + } + // Bitwise instructions + Op::Not => { + inst.expect(4)?; + self.parse_expr_unary_op_sign_adjusted( + ctx, + &mut emitter, + &mut block, + block_id, + body_idx, + crate::UnaryOperator::BitwiseNot, + )?; + } + Op::ShiftRightLogical => { + inst.expect(5)?; + //TODO: convert input and result to unsigned + parse_expr_op!(crate::BinaryOperator::ShiftRight, SHIFT)?; + } + Op::ShiftRightArithmetic => { + inst.expect(5)?; + //TODO: convert input and result to signed + parse_expr_op!(crate::BinaryOperator::ShiftRight, SHIFT)?; + } + Op::ShiftLeftLogical => { + inst.expect(5)?; + parse_expr_op!(crate::BinaryOperator::ShiftLeft, SHIFT)?; + } + // Sampling + Op::Image => { + inst.expect(4)?; + self.parse_image_uncouple(block_id)?; + } + Op::SampledImage => { + inst.expect(5)?; + self.parse_image_couple()?; + } + Op::ImageWrite => { + let extra = inst.expect_at_least(4)?; + let stmt = + self.parse_image_write(extra, ctx, &mut emitter, &mut block, body_idx)?; + block.extend(emitter.finish(ctx.expressions)); + block.push(stmt, span); + emitter.start(ctx.expressions); + } + Op::ImageFetch | Op::ImageRead => { + let extra = inst.expect_at_least(5)?; + self.parse_image_load( + extra, + ctx, + &mut emitter, + &mut block, + block_id, + body_idx, + )?; + } + Op::ImageSampleImplicitLod | Op::ImageSampleExplicitLod => { + let extra = inst.expect_at_least(5)?; + let options = image::SamplingOptions { + compare: false, + project: false, + gather: false, + }; + self.parse_image_sample( + extra, + options, + ctx, + &mut emitter, + &mut block, + block_id, + body_idx, + )?; + } + Op::ImageSampleProjImplicitLod | Op::ImageSampleProjExplicitLod => { + let extra = inst.expect_at_least(5)?; + let options = image::SamplingOptions { + compare: false, + project: true, + gather: false, + }; + self.parse_image_sample( + extra, + options, + ctx, + &mut emitter, + &mut block, + block_id, + body_idx, + )?; + } + Op::ImageSampleDrefImplicitLod | Op::ImageSampleDrefExplicitLod => { + let extra = inst.expect_at_least(6)?; + let options = image::SamplingOptions { + compare: true, + project: false, + gather: false, + }; + self.parse_image_sample( + extra, + options, + ctx, + &mut emitter, + &mut block, + block_id, + body_idx, + )?; + } + Op::ImageSampleProjDrefImplicitLod | Op::ImageSampleProjDrefExplicitLod => { + let extra = inst.expect_at_least(6)?; + let options = image::SamplingOptions { + compare: true, + project: true, + gather: false, + }; + self.parse_image_sample( + extra, + options, + ctx, + &mut emitter, + &mut block, + block_id, + body_idx, + )?; + } + Op::ImageGather => { + let extra = inst.expect_at_least(6)?; + let options = image::SamplingOptions { + compare: false, + project: false, + gather: true, + }; + self.parse_image_sample( + extra, + options, + ctx, + &mut emitter, + &mut block, + block_id, + body_idx, + )?; + } + Op::ImageDrefGather => { + let extra = inst.expect_at_least(6)?; + let options = image::SamplingOptions { + compare: true, + project: false, + gather: true, + }; + self.parse_image_sample( + extra, + options, + ctx, + &mut emitter, + &mut block, + block_id, + body_idx, + )?; + } + Op::ImageQuerySize => { + inst.expect(4)?; + self.parse_image_query_size( + false, + ctx, + &mut emitter, + &mut block, + block_id, + body_idx, + )?; + } + Op::ImageQuerySizeLod => { + inst.expect(5)?; + self.parse_image_query_size( + true, + ctx, + &mut emitter, + &mut block, + block_id, + body_idx, + )?; + } + Op::ImageQueryLevels => { + inst.expect(4)?; + self.parse_image_query_other(crate::ImageQuery::NumLevels, ctx, block_id)?; + } + Op::ImageQuerySamples => { + inst.expect(4)?; + self.parse_image_query_other(crate::ImageQuery::NumSamples, ctx, block_id)?; + } + // other ops + Op::Select => { + inst.expect(6)?; + let result_type_id = self.next()?; + let result_id = self.next()?; + let condition = self.next()?; + let o1_id = self.next()?; + let o2_id = self.next()?; + + let cond_lexp = self.lookup_expression.lookup(condition)?; + let cond_handle = get_expr_handle!(condition, cond_lexp); + let o1_lexp = self.lookup_expression.lookup(o1_id)?; + let o1_handle = get_expr_handle!(o1_id, o1_lexp); + let o2_lexp = self.lookup_expression.lookup(o2_id)?; + let o2_handle = get_expr_handle!(o2_id, o2_lexp); + + let expr = crate::Expression::Select { + condition: cond_handle, + accept: o1_handle, + reject: o2_handle, + }; + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, span), + type_id: result_type_id, + block_id, + }, + ); + } + Op::VectorShuffle => { + inst.expect_at_least(5)?; + let result_type_id = self.next()?; + let result_id = self.next()?; + let v1_id = self.next()?; + let v2_id = self.next()?; + + let v1_lexp = self.lookup_expression.lookup(v1_id)?; + let v1_lty = self.lookup_type.lookup(v1_lexp.type_id)?; + let v1_handle = get_expr_handle!(v1_id, v1_lexp); + let n1 = match ctx.module.types[v1_lty.handle].inner { + crate::TypeInner::Vector { size, .. } => size as u32, + _ => return Err(Error::InvalidInnerType(v1_lexp.type_id)), + }; + let v2_lexp = self.lookup_expression.lookup(v2_id)?; + let v2_lty = self.lookup_type.lookup(v2_lexp.type_id)?; + let v2_handle = get_expr_handle!(v2_id, v2_lexp); + let n2 = match ctx.module.types[v2_lty.handle].inner { + crate::TypeInner::Vector { size, .. } => size as u32, + _ => return Err(Error::InvalidInnerType(v2_lexp.type_id)), + }; + + self.temp_bytes.clear(); + let mut max_component = 0; + for _ in 5..inst.wc as usize { + let mut index = self.next()?; + if index == u32::MAX { + // treat Undefined as X + index = 0; + } + max_component = max_component.max(index); + self.temp_bytes.push(index as u8); + } + + // Check for swizzle first. + let expr = if max_component < n1 { + use crate::SwizzleComponent as Sc; + let size = match self.temp_bytes.len() { + 2 => crate::VectorSize::Bi, + 3 => crate::VectorSize::Tri, + _ => crate::VectorSize::Quad, + }; + let mut pattern = [Sc::X; 4]; + for (pat, index) in pattern.iter_mut().zip(self.temp_bytes.drain(..)) { + *pat = match index { + 0 => Sc::X, + 1 => Sc::Y, + 2 => Sc::Z, + _ => Sc::W, + }; + } + crate::Expression::Swizzle { + size, + vector: v1_handle, + pattern, + } + } else { + // Fall back to access + compose + let mut components = Vec::with_capacity(self.temp_bytes.len()); + for index in self.temp_bytes.drain(..).map(|i| i as u32) { + let expr = if index < n1 { + crate::Expression::AccessIndex { + base: v1_handle, + index, + } + } else if index < n1 + n2 { + crate::Expression::AccessIndex { + base: v2_handle, + index: index - n1, + } + } else { + return Err(Error::InvalidAccessIndex(index)); + }; + components.push(ctx.expressions.append(expr, span)); + } + crate::Expression::Compose { + ty: self.lookup_type.lookup(result_type_id)?.handle, + components, + } + }; + + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, span), + type_id: result_type_id, + block_id, + }, + ); + } + Op::Bitcast + | Op::ConvertSToF + | Op::ConvertUToF + | Op::ConvertFToU + | Op::ConvertFToS + | Op::FConvert + | Op::UConvert + | Op::SConvert => { + inst.expect(4)?; + let result_type_id = self.next()?; + let result_id = self.next()?; + let value_id = self.next()?; + + let value_lexp = self.lookup_expression.lookup(value_id)?; + let ty_lookup = self.lookup_type.lookup(result_type_id)?; + let scalar = match ctx.module.types[ty_lookup.handle].inner { + crate::TypeInner::Scalar(scalar) + | crate::TypeInner::Vector { scalar, .. } + | crate::TypeInner::Matrix { scalar, .. } => scalar, + _ => return Err(Error::InvalidAsType(ty_lookup.handle)), + }; + + let expr = crate::Expression::As { + expr: get_expr_handle!(value_id, value_lexp), + kind: scalar.kind, + convert: if scalar.kind == crate::ScalarKind::Bool { + Some(crate::BOOL_WIDTH) + } else if inst.op == Op::Bitcast { + None + } else { + Some(scalar.width) + }, + }; + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, span), + type_id: result_type_id, + block_id, + }, + ); + } + Op::FunctionCall => { + inst.expect_at_least(4)?; + + let result_type_id = self.next()?; + let result_id = self.next()?; + let func_id = self.next()?; + + let mut arguments = Vec::with_capacity(inst.wc as usize - 4); + for _ in 0..arguments.capacity() { + let arg_id = self.next()?; + let lexp = self.lookup_expression.lookup(arg_id)?; + arguments.push(get_expr_handle!(arg_id, lexp)); + } + + block.extend(emitter.finish(ctx.expressions)); + + // We just need an unique handle here, nothing more. + let function = self.add_call(ctx.function_id, func_id); + + let result = if self.lookup_void_type == Some(result_type_id) { + None + } else { + let expr_handle = ctx + .expressions + .append(crate::Expression::CallResult(function), span); + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: expr_handle, + type_id: result_type_id, + block_id, + }, + ); + Some(expr_handle) + }; + block.push( + crate::Statement::Call { + function, + arguments, + result, + }, + span, + ); + emitter.start(ctx.expressions); + } + Op::ExtInst => { + use crate::MathFunction as Mf; + use spirv::GLOp as Glo; + + let base_wc = 5; + inst.expect_at_least(base_wc)?; + + let result_type_id = self.next()?; + let result_id = self.next()?; + let set_id = self.next()?; + if Some(set_id) != self.ext_glsl_id { + return Err(Error::UnsupportedExtInstSet(set_id)); + } + let inst_id = self.next()?; + let gl_op = Glo::from_u32(inst_id).ok_or(Error::UnsupportedExtInst(inst_id))?; + + let fun = match gl_op { + Glo::Round => Mf::Round, + Glo::RoundEven => Mf::Round, + Glo::Trunc => Mf::Trunc, + Glo::FAbs | Glo::SAbs => Mf::Abs, + Glo::FSign | Glo::SSign => Mf::Sign, + Glo::Floor => Mf::Floor, + Glo::Ceil => Mf::Ceil, + Glo::Fract => Mf::Fract, + Glo::Sin => Mf::Sin, + Glo::Cos => Mf::Cos, + Glo::Tan => Mf::Tan, + Glo::Asin => Mf::Asin, + Glo::Acos => Mf::Acos, + Glo::Atan => Mf::Atan, + Glo::Sinh => Mf::Sinh, + Glo::Cosh => Mf::Cosh, + Glo::Tanh => Mf::Tanh, + Glo::Atan2 => Mf::Atan2, + Glo::Asinh => Mf::Asinh, + Glo::Acosh => Mf::Acosh, + Glo::Atanh => Mf::Atanh, + Glo::Radians => Mf::Radians, + Glo::Degrees => Mf::Degrees, + Glo::Pow => Mf::Pow, + Glo::Exp => Mf::Exp, + Glo::Log => Mf::Log, + Glo::Exp2 => Mf::Exp2, + Glo::Log2 => Mf::Log2, + Glo::Sqrt => Mf::Sqrt, + Glo::InverseSqrt => Mf::InverseSqrt, + Glo::MatrixInverse => Mf::Inverse, + Glo::Determinant => Mf::Determinant, + Glo::ModfStruct => Mf::Modf, + Glo::FMin | Glo::UMin | Glo::SMin | Glo::NMin => Mf::Min, + Glo::FMax | Glo::UMax | Glo::SMax | Glo::NMax => Mf::Max, + Glo::FClamp | Glo::UClamp | Glo::SClamp | Glo::NClamp => Mf::Clamp, + Glo::FMix => Mf::Mix, + Glo::Step => Mf::Step, + Glo::SmoothStep => Mf::SmoothStep, + Glo::Fma => Mf::Fma, + Glo::FrexpStruct => Mf::Frexp, + Glo::Ldexp => Mf::Ldexp, + Glo::Length => Mf::Length, + Glo::Distance => Mf::Distance, + Glo::Cross => Mf::Cross, + Glo::Normalize => Mf::Normalize, + Glo::FaceForward => Mf::FaceForward, + Glo::Reflect => Mf::Reflect, + Glo::Refract => Mf::Refract, + Glo::PackUnorm4x8 => Mf::Pack4x8unorm, + Glo::PackSnorm4x8 => Mf::Pack4x8snorm, + Glo::PackHalf2x16 => Mf::Pack2x16float, + Glo::PackUnorm2x16 => Mf::Pack2x16unorm, + Glo::PackSnorm2x16 => Mf::Pack2x16snorm, + Glo::UnpackUnorm4x8 => Mf::Unpack4x8unorm, + Glo::UnpackSnorm4x8 => Mf::Unpack4x8snorm, + Glo::UnpackHalf2x16 => Mf::Unpack2x16float, + Glo::UnpackUnorm2x16 => Mf::Unpack2x16unorm, + Glo::UnpackSnorm2x16 => Mf::Unpack2x16snorm, + Glo::FindILsb => Mf::FirstTrailingBit, + Glo::FindUMsb | Glo::FindSMsb => Mf::FirstLeadingBit, + // TODO: https://github.com/gfx-rs/naga/issues/2526 + Glo::Modf | Glo::Frexp => return Err(Error::UnsupportedExtInst(inst_id)), + Glo::IMix + | Glo::PackDouble2x32 + | Glo::UnpackDouble2x32 + | Glo::InterpolateAtCentroid + | Glo::InterpolateAtSample + | Glo::InterpolateAtOffset => { + return Err(Error::UnsupportedExtInst(inst_id)) + } + }; + + let arg_count = fun.argument_count(); + inst.expect(base_wc + arg_count as u16)?; + let arg = { + let arg_id = self.next()?; + let lexp = self.lookup_expression.lookup(arg_id)?; + get_expr_handle!(arg_id, lexp) + }; + let arg1 = if arg_count > 1 { + let arg_id = self.next()?; + let lexp = self.lookup_expression.lookup(arg_id)?; + Some(get_expr_handle!(arg_id, lexp)) + } else { + None + }; + let arg2 = if arg_count > 2 { + let arg_id = self.next()?; + let lexp = self.lookup_expression.lookup(arg_id)?; + Some(get_expr_handle!(arg_id, lexp)) + } else { + None + }; + let arg3 = if arg_count > 3 { + let arg_id = self.next()?; + let lexp = self.lookup_expression.lookup(arg_id)?; + Some(get_expr_handle!(arg_id, lexp)) + } else { + None + }; + + let expr = crate::Expression::Math { + fun, + arg, + arg1, + arg2, + arg3, + }; + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, span), + type_id: result_type_id, + block_id, + }, + ); + } + // Relational and Logical Instructions + Op::LogicalNot => { + inst.expect(4)?; + parse_expr_op!(crate::UnaryOperator::LogicalNot, UNARY)?; + } + Op::LogicalOr => { + inst.expect(5)?; + parse_expr_op!(crate::BinaryOperator::LogicalOr, BINARY)?; + } + Op::LogicalAnd => { + inst.expect(5)?; + parse_expr_op!(crate::BinaryOperator::LogicalAnd, BINARY)?; + } + Op::SGreaterThan | Op::SGreaterThanEqual | Op::SLessThan | Op::SLessThanEqual => { + inst.expect(5)?; + self.parse_expr_int_comparison( + ctx, + &mut emitter, + &mut block, + block_id, + body_idx, + map_binary_operator(inst.op)?, + crate::ScalarKind::Sint, + )?; + } + Op::UGreaterThan | Op::UGreaterThanEqual | Op::ULessThan | Op::ULessThanEqual => { + inst.expect(5)?; + self.parse_expr_int_comparison( + ctx, + &mut emitter, + &mut block, + block_id, + body_idx, + map_binary_operator(inst.op)?, + crate::ScalarKind::Uint, + )?; + } + Op::FOrdEqual + | Op::FUnordEqual + | Op::FOrdNotEqual + | Op::FUnordNotEqual + | Op::FOrdLessThan + | Op::FUnordLessThan + | Op::FOrdGreaterThan + | Op::FUnordGreaterThan + | Op::FOrdLessThanEqual + | Op::FUnordLessThanEqual + | Op::FOrdGreaterThanEqual + | Op::FUnordGreaterThanEqual + | Op::LogicalEqual + | Op::LogicalNotEqual => { + inst.expect(5)?; + let operator = map_binary_operator(inst.op)?; + parse_expr_op!(operator, BINARY)?; + } + Op::Any | Op::All | Op::IsNan | Op::IsInf | Op::IsFinite | Op::IsNormal => { + inst.expect(4)?; + let result_type_id = self.next()?; + let result_id = self.next()?; + let arg_id = self.next()?; + + let arg_lexp = self.lookup_expression.lookup(arg_id)?; + let arg_handle = get_expr_handle!(arg_id, arg_lexp); + + let expr = crate::Expression::Relational { + fun: map_relational_fun(inst.op)?, + argument: arg_handle, + }; + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, span), + type_id: result_type_id, + block_id, + }, + ); + } + Op::Kill => { + inst.expect(1)?; + break Some(crate::Statement::Kill); + } + Op::Unreachable => { + inst.expect(1)?; + break None; + } + Op::Return => { + inst.expect(1)?; + break Some(crate::Statement::Return { value: None }); + } + Op::ReturnValue => { + inst.expect(2)?; + let value_id = self.next()?; + let value_lexp = self.lookup_expression.lookup(value_id)?; + let value_handle = get_expr_handle!(value_id, value_lexp); + break Some(crate::Statement::Return { + value: Some(value_handle), + }); + } + Op::Branch => { + inst.expect(2)?; + let target_id = self.next()?; + + // If this is a branch to a merge or continue block, then + // that ends the current body. + // + // Why can we count on finding an entry here when it's + // needed? SPIR-V requires dominators to appear before + // blocks they dominate, so we will have visited a + // structured control construct's header block before + // anything that could exit it. + if let Some(info) = ctx.mergers.get(&target_id) { + block.extend(emitter.finish(ctx.expressions)); + ctx.blocks.insert(block_id, block); + let body = &mut ctx.bodies[body_idx]; + body.data.push(BodyFragment::BlockId(block_id)); + + merger(body, info); + + return Ok(()); + } + + // If `target_id` has no entry in `ctx.body_for_label`, then + // this must be the only branch to it: + // + // - We've already established that it's not anybody's merge + // block. + // + // - It can't be a switch case. Only switch header blocks + // and other switch cases can branch to a switch case. + // Switch header blocks must dominate all their cases, so + // they must appear in the file before them, and when we + // see `Op::Switch` we populate `ctx.body_for_label` for + // every switch case. + // + // Thus, `target_id` must be a simple extension of the + // current block, which we dominate, so we know we'll + // encounter it later in the file. + ctx.body_for_label.entry(target_id).or_insert(body_idx); + + break None; + } + Op::BranchConditional => { + inst.expect_at_least(4)?; + + let condition = { + let condition_id = self.next()?; + let lexp = self.lookup_expression.lookup(condition_id)?; + get_expr_handle!(condition_id, lexp) + }; + + // HACK(eddyb) Naga doesn't seem to have this helper, + // so it's declared on the fly here for convenience. + #[derive(Copy, Clone)] + struct BranchTarget { + label_id: spirv::Word, + merge_info: Option<MergeBlockInformation>, + } + let branch_target = |label_id| BranchTarget { + label_id, + merge_info: ctx.mergers.get(&label_id).copied(), + }; + + let true_target = branch_target(self.next()?); + let false_target = branch_target(self.next()?); + + // Consume branch weights + for _ in 4..inst.wc { + let _ = self.next()?; + } + + // Handle `OpBranchConditional`s used at the end of a loop + // body's "continuing" section as a "conditional backedge", + // i.e. a `do`-`while` condition, or `break if` in WGSL. + + // HACK(eddyb) this has to go to the parent *twice*, because + // `OpLoopMerge` left the "continuing" section nested in the + // loop body in terms of `parent`, but not `BodyFragment`. + let parent_body_idx = ctx.bodies[body_idx].parent; + let parent_parent_body_idx = ctx.bodies[parent_body_idx].parent; + match ctx.bodies[parent_parent_body_idx].data[..] { + // The `OpLoopMerge`'s `continuing` block and the loop's + // backedge block may not be the same, but they'll both + // belong to the same body. + [.., BodyFragment::Loop { + body: loop_body_idx, + continuing: loop_continuing_idx, + break_if: ref mut break_if_slot @ None, + }] if body_idx == loop_continuing_idx => { + // Try both orderings of break-vs-backedge, because + // SPIR-V is symmetrical here, unlike WGSL `break if`. + let break_if_cond = [true, false].into_iter().find_map(|true_breaks| { + let (break_candidate, backedge_candidate) = if true_breaks { + (true_target, false_target) + } else { + (false_target, true_target) + }; + + if break_candidate.merge_info + != Some(MergeBlockInformation::LoopMerge) + { + return None; + } + + // HACK(eddyb) since Naga doesn't explicitly track + // backedges, this is checking for the outcome of + // `OpLoopMerge` below (even if it looks weird). + let backedge_candidate_is_backedge = + backedge_candidate.merge_info.is_none() + && ctx.body_for_label.get(&backedge_candidate.label_id) + == Some(&loop_body_idx); + if !backedge_candidate_is_backedge { + return None; + } + + Some(if true_breaks { + condition + } else { + ctx.expressions.append( + crate::Expression::Unary { + op: crate::UnaryOperator::LogicalNot, + expr: condition, + }, + span, + ) + }) + }); + + if let Some(break_if_cond) = break_if_cond { + *break_if_slot = Some(break_if_cond); + + // This `OpBranchConditional` ends the "continuing" + // section of the loop body as normal, with the + // `break if` condition having been stashed above. + break None; + } + } + _ => {} + } + + block.extend(emitter.finish(ctx.expressions)); + ctx.blocks.insert(block_id, block); + let body = &mut ctx.bodies[body_idx]; + body.data.push(BodyFragment::BlockId(block_id)); + + let same_target = true_target.label_id == false_target.label_id; + + // Start a body block for the `accept` branch. + let accept = ctx.bodies.len(); + let mut accept_block = Body::with_parent(body_idx); + + // If the `OpBranchConditional` target is somebody else's + // merge or continue block, then put a `Break` or `Continue` + // statement in this new body block. + if let Some(info) = true_target.merge_info { + merger( + match same_target { + true => &mut ctx.bodies[body_idx], + false => &mut accept_block, + }, + &info, + ) + } else { + // Note the body index for the block we're branching to. + let prev = ctx.body_for_label.insert( + true_target.label_id, + match same_target { + true => body_idx, + false => accept, + }, + ); + debug_assert!(prev.is_none()); + } + + if same_target { + return Ok(()); + } + + ctx.bodies.push(accept_block); + + // Handle the `reject` branch just like the `accept` block. + let reject = ctx.bodies.len(); + let mut reject_block = Body::with_parent(body_idx); + + if let Some(info) = false_target.merge_info { + merger(&mut reject_block, &info) + } else { + let prev = ctx.body_for_label.insert(false_target.label_id, reject); + debug_assert!(prev.is_none()); + } + + ctx.bodies.push(reject_block); + + let body = &mut ctx.bodies[body_idx]; + body.data.push(BodyFragment::If { + condition, + accept, + reject, + }); + + return Ok(()); + } + Op::Switch => { + inst.expect_at_least(3)?; + let selector = self.next()?; + let default_id = self.next()?; + + // If the previous instruction was a `OpSelectionMerge` then we must + // promote the `MergeBlockInformation` to a `SwitchMerge` + if let Some(merge) = selection_merge_block { + ctx.mergers + .insert(merge, MergeBlockInformation::SwitchMerge); + } + + let default = ctx.bodies.len(); + ctx.bodies.push(Body::with_parent(body_idx)); + ctx.body_for_label.entry(default_id).or_insert(default); + + let selector_lexp = &self.lookup_expression[&selector]; + let selector_lty = self.lookup_type.lookup(selector_lexp.type_id)?; + let selector_handle = get_expr_handle!(selector, selector_lexp); + let selector = match ctx.module.types[selector_lty.handle].inner { + crate::TypeInner::Scalar(crate::Scalar { + kind: crate::ScalarKind::Uint, + width: _, + }) => { + // IR expects a signed integer, so do a bitcast + ctx.expressions.append( + crate::Expression::As { + kind: crate::ScalarKind::Sint, + expr: selector_handle, + convert: None, + }, + span, + ) + } + crate::TypeInner::Scalar(crate::Scalar { + kind: crate::ScalarKind::Sint, + width: _, + }) => selector_handle, + ref other => unimplemented!("Unexpected selector {:?}", other), + }; + + // Clear past switch cases to prevent them from entering this one + self.switch_cases.clear(); + + for _ in 0..(inst.wc - 3) / 2 { + let literal = self.next()?; + let target = self.next()?; + + let case_body_idx = ctx.bodies.len(); + + // Check if any previous case already used this target block id, if so + // group them together to reorder them later so that no weird + // fallthrough cases happen. + if let Some(&mut (_, ref mut literals)) = self.switch_cases.get_mut(&target) + { + literals.push(literal as i32); + continue; + } + + let mut body = Body::with_parent(body_idx); + + if let Some(info) = ctx.mergers.get(&target) { + merger(&mut body, info); + } + + ctx.bodies.push(body); + ctx.body_for_label.entry(target).or_insert(case_body_idx); + + // Register this target block id as already having been processed and + // the respective body index assigned and the first case value + self.switch_cases + .insert(target, (case_body_idx, vec![literal as i32])); + } + + // Loop through the collected target blocks creating a new case for each + // literal pointing to it, only one case will have the true body and all the + // others will be empty fallthrough so that they all execute the same body + // without duplicating code. + // + // Since `switch_cases` is an indexmap the order of insertion is preserved + // this is needed because spir-v defines fallthrough order in the switch + // instruction. + let mut cases = Vec::with_capacity((inst.wc as usize - 3) / 2); + for &(case_body_idx, ref literals) in self.switch_cases.values() { + let value = literals[0]; + + for &literal in literals.iter().skip(1) { + let empty_body_idx = ctx.bodies.len(); + let body = Body::with_parent(body_idx); + + ctx.bodies.push(body); + + cases.push((literal, empty_body_idx)); + } + + cases.push((value, case_body_idx)); + } + + block.extend(emitter.finish(ctx.expressions)); + + let body = &mut ctx.bodies[body_idx]; + ctx.blocks.insert(block_id, block); + // Make sure the vector has space for at least two more allocations + body.data.reserve(2); + body.data.push(BodyFragment::BlockId(block_id)); + body.data.push(BodyFragment::Switch { + selector, + cases, + default, + }); + + return Ok(()); + } + Op::SelectionMerge => { + inst.expect(3)?; + let merge_block_id = self.next()?; + // TODO: Selection Control Mask + let _selection_control = self.next()?; + + // Indicate that the merge block is a continuation of the + // current `Body`. + ctx.body_for_label.entry(merge_block_id).or_insert(body_idx); + + // Let subsequent branches to the merge block know that + // they've reached the end of the selection construct. + ctx.mergers + .insert(merge_block_id, MergeBlockInformation::SelectionMerge); + + selection_merge_block = Some(merge_block_id); + } + Op::LoopMerge => { + inst.expect_at_least(4)?; + let merge_block_id = self.next()?; + let continuing = self.next()?; + + // TODO: Loop Control Parameters + for _ in 0..inst.wc - 3 { + self.next()?; + } + + // Indicate that the merge block is a continuation of the + // current `Body`. + ctx.body_for_label.entry(merge_block_id).or_insert(body_idx); + // Let subsequent branches to the merge block know that + // they're `Break` statements. + ctx.mergers + .insert(merge_block_id, MergeBlockInformation::LoopMerge); + + let loop_body_idx = ctx.bodies.len(); + ctx.bodies.push(Body::with_parent(body_idx)); + + let continue_idx = ctx.bodies.len(); + // The continue block inherits the scope of the loop body + ctx.bodies.push(Body::with_parent(loop_body_idx)); + ctx.body_for_label.entry(continuing).or_insert(continue_idx); + // Let subsequent branches to the continue block know that + // they're `Continue` statements. + ctx.mergers + .insert(continuing, MergeBlockInformation::LoopContinue); + + // The loop header always belongs to the loop body + ctx.body_for_label.insert(block_id, loop_body_idx); + + let parent_body = &mut ctx.bodies[body_idx]; + parent_body.data.push(BodyFragment::Loop { + body: loop_body_idx, + continuing: continue_idx, + break_if: None, + }); + body_idx = loop_body_idx; + } + Op::DPdxCoarse => { + parse_expr_op!( + crate::DerivativeAxis::X, + crate::DerivativeControl::Coarse, + DERIVATIVE + )?; + } + Op::DPdyCoarse => { + parse_expr_op!( + crate::DerivativeAxis::Y, + crate::DerivativeControl::Coarse, + DERIVATIVE + )?; + } + Op::FwidthCoarse => { + parse_expr_op!( + crate::DerivativeAxis::Width, + crate::DerivativeControl::Coarse, + DERIVATIVE + )?; + } + Op::DPdxFine => { + parse_expr_op!( + crate::DerivativeAxis::X, + crate::DerivativeControl::Fine, + DERIVATIVE + )?; + } + Op::DPdyFine => { + parse_expr_op!( + crate::DerivativeAxis::Y, + crate::DerivativeControl::Fine, + DERIVATIVE + )?; + } + Op::FwidthFine => { + parse_expr_op!( + crate::DerivativeAxis::Width, + crate::DerivativeControl::Fine, + DERIVATIVE + )?; + } + Op::DPdx => { + parse_expr_op!( + crate::DerivativeAxis::X, + crate::DerivativeControl::None, + DERIVATIVE + )?; + } + Op::DPdy => { + parse_expr_op!( + crate::DerivativeAxis::Y, + crate::DerivativeControl::None, + DERIVATIVE + )?; + } + Op::Fwidth => { + parse_expr_op!( + crate::DerivativeAxis::Width, + crate::DerivativeControl::None, + DERIVATIVE + )?; + } + Op::ArrayLength => { + inst.expect(5)?; + let result_type_id = self.next()?; + let result_id = self.next()?; + let structure_id = self.next()?; + let member_index = self.next()?; + + // We're assuming that the validation pass, if it's run, will catch if the + // wrong types or parameters are supplied here. + + let structure_ptr = self.lookup_expression.lookup(structure_id)?; + let structure_handle = get_expr_handle!(structure_id, structure_ptr); + + let member_ptr = ctx.expressions.append( + crate::Expression::AccessIndex { + base: structure_handle, + index: member_index, + }, + span, + ); + + let length = ctx + .expressions + .append(crate::Expression::ArrayLength(member_ptr), span); + + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: length, + type_id: result_type_id, + block_id, + }, + ); + } + Op::CopyMemory => { + inst.expect_at_least(3)?; + let target_id = self.next()?; + let source_id = self.next()?; + let _memory_access = if inst.wc != 3 { + inst.expect(4)?; + spirv::MemoryAccess::from_bits(self.next()?) + .ok_or(Error::InvalidParameter(Op::CopyMemory))? + } else { + spirv::MemoryAccess::NONE + }; + + // TODO: check if the source and target types are the same? + let target = self.lookup_expression.lookup(target_id)?; + let target_handle = get_expr_handle!(target_id, target); + let source = self.lookup_expression.lookup(source_id)?; + let source_handle = get_expr_handle!(source_id, source); + + // This operation is practically the same as loading and then storing, I think. + let value_expr = ctx.expressions.append( + crate::Expression::Load { + pointer: source_handle, + }, + span, + ); + + block.extend(emitter.finish(ctx.expressions)); + block.push( + crate::Statement::Store { + pointer: target_handle, + value: value_expr, + }, + span, + ); + + emitter.start(ctx.expressions); + } + Op::ControlBarrier => { + inst.expect(4)?; + let exec_scope_id = self.next()?; + let _mem_scope_raw = self.next()?; + let semantics_id = self.next()?; + let exec_scope_const = self.lookup_constant.lookup(exec_scope_id)?; + let semantics_const = self.lookup_constant.lookup(semantics_id)?; + + let exec_scope = resolve_constant(ctx.gctx(), &exec_scope_const.inner) + .ok_or(Error::InvalidBarrierScope(exec_scope_id))?; + let semantics = resolve_constant(ctx.gctx(), &semantics_const.inner) + .ok_or(Error::InvalidBarrierMemorySemantics(semantics_id))?; + + if exec_scope == spirv::Scope::Workgroup as u32 + || exec_scope == spirv::Scope::Subgroup as u32 + { + let mut flags = crate::Barrier::empty(); + flags.set( + crate::Barrier::STORAGE, + semantics & spirv::MemorySemantics::UNIFORM_MEMORY.bits() != 0, + ); + flags.set( + crate::Barrier::WORK_GROUP, + semantics & (spirv::MemorySemantics::WORKGROUP_MEMORY).bits() != 0, + ); + flags.set( + crate::Barrier::SUB_GROUP, + semantics & spirv::MemorySemantics::SUBGROUP_MEMORY.bits() != 0, + ); + flags.set( + crate::Barrier::TEXTURE, + semantics & spirv::MemorySemantics::IMAGE_MEMORY.bits() != 0, + ); + + block.extend(emitter.finish(ctx.expressions)); + block.push(crate::Statement::ControlBarrier(flags), span); + emitter.start(ctx.expressions); + } else { + log::warn!("Unsupported barrier execution scope: {exec_scope}"); + } + } + Op::MemoryBarrier => { + inst.expect(3)?; + let mem_scope_id = self.next()?; + let semantics_id = self.next()?; + let mem_scope_const = self.lookup_constant.lookup(mem_scope_id)?; + let semantics_const = self.lookup_constant.lookup(semantics_id)?; + + let mem_scope = resolve_constant(ctx.gctx(), &mem_scope_const.inner) + .ok_or(Error::InvalidBarrierScope(mem_scope_id))?; + let semantics = resolve_constant(ctx.gctx(), &semantics_const.inner) + .ok_or(Error::InvalidBarrierMemorySemantics(semantics_id))?; + + let mut flags = if mem_scope == spirv::Scope::Device as u32 { + crate::Barrier::STORAGE + } else if mem_scope == spirv::Scope::Workgroup as u32 { + crate::Barrier::WORK_GROUP + } else if mem_scope == spirv::Scope::Subgroup as u32 { + crate::Barrier::SUB_GROUP + } else { + crate::Barrier::empty() + }; + flags.set( + crate::Barrier::STORAGE, + semantics & spirv::MemorySemantics::UNIFORM_MEMORY.bits() != 0, + ); + flags.set( + crate::Barrier::WORK_GROUP, + semantics & (spirv::MemorySemantics::WORKGROUP_MEMORY).bits() != 0, + ); + flags.set( + crate::Barrier::SUB_GROUP, + semantics & spirv::MemorySemantics::SUBGROUP_MEMORY.bits() != 0, + ); + flags.set( + crate::Barrier::TEXTURE, + semantics & spirv::MemorySemantics::IMAGE_MEMORY.bits() != 0, + ); + + block.extend(emitter.finish(ctx.expressions)); + block.push(crate::Statement::MemoryBarrier(flags), span); + emitter.start(ctx.expressions); + } + Op::CopyObject => { + inst.expect(4)?; + let result_type_id = self.next()?; + let result_id = self.next()?; + let operand_id = self.next()?; + + let lookup = self.lookup_expression.lookup(operand_id)?; + let handle = get_expr_handle!(operand_id, lookup); + + self.lookup_expression.insert( + result_id, + LookupExpression { + handle, + type_id: result_type_id, + block_id, + }, + ); + } + Op::GroupNonUniformBallot => { + inst.expect(5)?; + block.extend(emitter.finish(ctx.expressions)); + let result_type_id = self.next()?; + let result_id = self.next()?; + let exec_scope_id = self.next()?; + let predicate_id = self.next()?; + + let exec_scope_const = self.lookup_constant.lookup(exec_scope_id)?; + let _exec_scope = resolve_constant(ctx.gctx(), &exec_scope_const.inner) + .filter(|exec_scope| *exec_scope == spirv::Scope::Subgroup as u32) + .ok_or(Error::InvalidBarrierScope(exec_scope_id))?; + + let predicate = if self + .lookup_constant + .lookup(predicate_id) + .ok() + .filter(|predicate_const| match predicate_const.inner { + Constant::Constant(constant) => matches!( + ctx.gctx().global_expressions[ctx.gctx().constants[constant].init], + crate::Expression::Literal(crate::Literal::Bool(true)), + ), + Constant::Override(_) => false, + }) + .is_some() + { + None + } else { + let predicate_lookup = self.lookup_expression.lookup(predicate_id)?; + let predicate_handle = get_expr_handle!(predicate_id, predicate_lookup); + Some(predicate_handle) + }; + + let result_handle = ctx + .expressions + .append(crate::Expression::SubgroupBallotResult, span); + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: result_handle, + type_id: result_type_id, + block_id, + }, + ); + + block.push( + crate::Statement::SubgroupBallot { + result: result_handle, + predicate, + }, + span, + ); + emitter.start(ctx.expressions); + } + Op::GroupNonUniformAll + | Op::GroupNonUniformAny + | Op::GroupNonUniformIAdd + | Op::GroupNonUniformFAdd + | Op::GroupNonUniformIMul + | Op::GroupNonUniformFMul + | Op::GroupNonUniformSMax + | Op::GroupNonUniformUMax + | Op::GroupNonUniformFMax + | Op::GroupNonUniformSMin + | Op::GroupNonUniformUMin + | Op::GroupNonUniformFMin + | Op::GroupNonUniformBitwiseAnd + | Op::GroupNonUniformBitwiseOr + | Op::GroupNonUniformBitwiseXor + | Op::GroupNonUniformLogicalAnd + | Op::GroupNonUniformLogicalOr + | Op::GroupNonUniformLogicalXor => { + block.extend(emitter.finish(ctx.expressions)); + inst.expect( + if matches!(inst.op, Op::GroupNonUniformAll | Op::GroupNonUniformAny) { + 5 + } else { + 6 + }, + )?; + let result_type_id = self.next()?; + let result_id = self.next()?; + let exec_scope_id = self.next()?; + let collective_op_id = match inst.op { + Op::GroupNonUniformAll | Op::GroupNonUniformAny => { + crate::CollectiveOperation::Reduce + } + _ => { + let group_op_id = self.next()?; + match spirv::GroupOperation::from_u32(group_op_id) { + Some(spirv::GroupOperation::Reduce) => { + crate::CollectiveOperation::Reduce + } + Some(spirv::GroupOperation::InclusiveScan) => { + crate::CollectiveOperation::InclusiveScan + } + Some(spirv::GroupOperation::ExclusiveScan) => { + crate::CollectiveOperation::ExclusiveScan + } + _ => return Err(Error::UnsupportedGroupOperation(group_op_id)), + } + } + }; + let argument_id = self.next()?; + + let argument_lookup = self.lookup_expression.lookup(argument_id)?; + let argument_handle = get_expr_handle!(argument_id, argument_lookup); + + let exec_scope_const = self.lookup_constant.lookup(exec_scope_id)?; + let _exec_scope = resolve_constant(ctx.gctx(), &exec_scope_const.inner) + .filter(|exec_scope| *exec_scope == spirv::Scope::Subgroup as u32) + .ok_or(Error::InvalidBarrierScope(exec_scope_id))?; + + let op_id = match inst.op { + Op::GroupNonUniformAll => crate::SubgroupOperation::All, + Op::GroupNonUniformAny => crate::SubgroupOperation::Any, + Op::GroupNonUniformIAdd | Op::GroupNonUniformFAdd => { + crate::SubgroupOperation::Add + } + Op::GroupNonUniformIMul | Op::GroupNonUniformFMul => { + crate::SubgroupOperation::Mul + } + Op::GroupNonUniformSMax + | Op::GroupNonUniformUMax + | Op::GroupNonUniformFMax => crate::SubgroupOperation::Max, + Op::GroupNonUniformSMin + | Op::GroupNonUniformUMin + | Op::GroupNonUniformFMin => crate::SubgroupOperation::Min, + Op::GroupNonUniformBitwiseAnd | Op::GroupNonUniformLogicalAnd => { + crate::SubgroupOperation::And + } + Op::GroupNonUniformBitwiseOr | Op::GroupNonUniformLogicalOr => { + crate::SubgroupOperation::Or + } + Op::GroupNonUniformBitwiseXor | Op::GroupNonUniformLogicalXor => { + crate::SubgroupOperation::Xor + } + _ => unreachable!(), + }; + + let result_type = self.lookup_type.lookup(result_type_id)?; + + let result_handle = ctx.expressions.append( + crate::Expression::SubgroupOperationResult { + ty: result_type.handle, + }, + span, + ); + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: result_handle, + type_id: result_type_id, + block_id, + }, + ); + + block.push( + crate::Statement::SubgroupCollectiveOperation { + result: result_handle, + op: op_id, + collective_op: collective_op_id, + argument: argument_handle, + }, + span, + ); + emitter.start(ctx.expressions); + } + Op::GroupNonUniformBroadcastFirst + | Op::GroupNonUniformBroadcast + | Op::GroupNonUniformShuffle + | Op::GroupNonUniformShuffleDown + | Op::GroupNonUniformShuffleUp + | Op::GroupNonUniformShuffleXor + | Op::GroupNonUniformQuadBroadcast => { + inst.expect(if matches!(inst.op, Op::GroupNonUniformBroadcastFirst) { + 5 + } else { + 6 + })?; + block.extend(emitter.finish(ctx.expressions)); + let result_type_id = self.next()?; + let result_id = self.next()?; + let exec_scope_id = self.next()?; + let argument_id = self.next()?; + + let argument_lookup = self.lookup_expression.lookup(argument_id)?; + let argument_handle = get_expr_handle!(argument_id, argument_lookup); + + let exec_scope_const = self.lookup_constant.lookup(exec_scope_id)?; + let _exec_scope = resolve_constant(ctx.gctx(), &exec_scope_const.inner) + .filter(|exec_scope| *exec_scope == spirv::Scope::Subgroup as u32) + .ok_or(Error::InvalidBarrierScope(exec_scope_id))?; + + let mode = if matches!(inst.op, Op::GroupNonUniformBroadcastFirst) { + crate::GatherMode::BroadcastFirst + } else { + let index_id = self.next()?; + let index_lookup = self.lookup_expression.lookup(index_id)?; + let index_handle = get_expr_handle!(index_id, index_lookup); + match inst.op { + Op::GroupNonUniformBroadcast => { + crate::GatherMode::Broadcast(index_handle) + } + Op::GroupNonUniformShuffle => crate::GatherMode::Shuffle(index_handle), + Op::GroupNonUniformShuffleDown => { + crate::GatherMode::ShuffleDown(index_handle) + } + Op::GroupNonUniformShuffleUp => { + crate::GatherMode::ShuffleUp(index_handle) + } + Op::GroupNonUniformShuffleXor => { + crate::GatherMode::ShuffleXor(index_handle) + } + Op::GroupNonUniformQuadBroadcast => { + crate::GatherMode::QuadBroadcast(index_handle) + } + _ => unreachable!(), + } + }; + + let result_type = self.lookup_type.lookup(result_type_id)?; + + let result_handle = ctx.expressions.append( + crate::Expression::SubgroupOperationResult { + ty: result_type.handle, + }, + span, + ); + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: result_handle, + type_id: result_type_id, + block_id, + }, + ); + + block.push( + crate::Statement::SubgroupGather { + result: result_handle, + mode, + argument: argument_handle, + }, + span, + ); + emitter.start(ctx.expressions); + } + Op::GroupNonUniformQuadSwap => { + inst.expect(6)?; + block.extend(emitter.finish(ctx.expressions)); + let result_type_id = self.next()?; + let result_id = self.next()?; + let exec_scope_id = self.next()?; + let argument_id = self.next()?; + let direction_id = self.next()?; + + let argument_lookup = self.lookup_expression.lookup(argument_id)?; + let argument_handle = get_expr_handle!(argument_id, argument_lookup); + + let exec_scope_const = self.lookup_constant.lookup(exec_scope_id)?; + let _exec_scope = resolve_constant(ctx.gctx(), &exec_scope_const.inner) + .filter(|exec_scope| *exec_scope == spirv::Scope::Subgroup as u32) + .ok_or(Error::InvalidBarrierScope(exec_scope_id))?; + + let direction_const = self.lookup_constant.lookup(direction_id)?; + let direction_const = resolve_constant(ctx.gctx(), &direction_const.inner) + .ok_or(Error::InvalidOperand)?; + let direction = match direction_const { + 0 => crate::Direction::X, + 1 => crate::Direction::Y, + 2 => crate::Direction::Diagonal, + _ => unreachable!(), + }; + + let result_type = self.lookup_type.lookup(result_type_id)?; + + let result_handle = ctx.expressions.append( + crate::Expression::SubgroupOperationResult { + ty: result_type.handle, + }, + span, + ); + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: result_handle, + type_id: result_type_id, + block_id, + }, + ); + + block.push( + crate::Statement::SubgroupGather { + mode: crate::GatherMode::QuadSwap(direction), + result: result_handle, + argument: argument_handle, + }, + span, + ); + emitter.start(ctx.expressions); + } + Op::AtomicLoad => { + inst.expect(6)?; + let start = self.data_offset; + let result_type_id = self.next()?; + let result_id = self.next()?; + let pointer_id = self.next()?; + let _scope_id = self.next()?; + let _memory_semantics_id = self.next()?; + let span = self.span_from_with_op(start); + + log::trace!("\t\t\tlooking up expr {pointer_id:?}"); + let p_lexp_handle = + get_expr_handle!(pointer_id, self.lookup_expression.lookup(pointer_id)?); + + // Create an expression for our result + let expr = crate::Expression::Load { + pointer: p_lexp_handle, + }; + let handle = ctx.expressions.append(expr, span); + self.lookup_expression.insert( + result_id, + LookupExpression { + handle, + type_id: result_type_id, + block_id, + }, + ); + + // Store any associated global variables so we can upgrade their types later + self.record_atomic_access(ctx, p_lexp_handle)?; + } + Op::AtomicStore => { + inst.expect(5)?; + let start = self.data_offset; + let pointer_id = self.next()?; + let _scope_id = self.next()?; + let _memory_semantics_id = self.next()?; + let value_id = self.next()?; + let span = self.span_from_with_op(start); + + log::trace!("\t\t\tlooking up pointer expr {pointer_id:?}"); + let p_lexp_handle = + get_expr_handle!(pointer_id, self.lookup_expression.lookup(pointer_id)?); + + log::trace!("\t\t\tlooking up value expr {pointer_id:?}"); + let v_lexp_handle = + get_expr_handle!(value_id, self.lookup_expression.lookup(value_id)?); + + block.extend(emitter.finish(ctx.expressions)); + // Create a statement for the op itself + let stmt = crate::Statement::Store { + pointer: p_lexp_handle, + value: v_lexp_handle, + }; + block.push(stmt, span); + emitter.start(ctx.expressions); + + // Store any associated global variables so we can upgrade their types later + self.record_atomic_access(ctx, p_lexp_handle)?; + } + Op::AtomicIIncrement | Op::AtomicIDecrement => { + inst.expect(6)?; + let start = self.data_offset; + let result_type_id = self.next()?; + let result_id = self.next()?; + let pointer_id = self.next()?; + let _scope_id = self.next()?; + let _memory_semantics_id = self.next()?; + let span = self.span_from_with_op(start); + + let (p_exp_h, p_base_ty_h) = self.get_exp_and_base_ty_handles( + pointer_id, + ctx, + &mut emitter, + &mut block, + body_idx, + )?; + + block.extend(emitter.finish(ctx.expressions)); + // Create an expression for our result + let r_lexp_handle = { + let expr = crate::Expression::AtomicResult { + ty: p_base_ty_h, + comparison: false, + }; + let handle = ctx.expressions.append(expr, span); + self.lookup_expression.insert( + result_id, + LookupExpression { + handle, + type_id: result_type_id, + block_id, + }, + ); + handle + }; + emitter.start(ctx.expressions); + + // Create a literal "1" to use as our value + let one_lexp_handle = make_index_literal( + ctx, + 1, + &mut block, + &mut emitter, + p_base_ty_h, + result_type_id, + span, + )?; + + // Create a statement for the op itself + let stmt = crate::Statement::Atomic { + pointer: p_exp_h, + fun: match inst.op { + Op::AtomicIIncrement => crate::AtomicFunction::Add, + _ => crate::AtomicFunction::Subtract, + }, + value: one_lexp_handle, + result: Some(r_lexp_handle), + }; + block.push(stmt, span); + + // Store any associated global variables so we can upgrade their types later + self.record_atomic_access(ctx, p_exp_h)?; + } + Op::AtomicCompareExchange => { + inst.expect(9)?; + + let start = self.data_offset; + let span = self.span_from_with_op(start); + let result_type_id = self.next()?; + let result_id = self.next()?; + let pointer_id = self.next()?; + let _memory_scope_id = self.next()?; + let _equal_memory_semantics_id = self.next()?; + let _unequal_memory_semantics_id = self.next()?; + let value_id = self.next()?; + let comparator_id = self.next()?; + + let (p_exp_h, p_base_ty_h) = self.get_exp_and_base_ty_handles( + pointer_id, + ctx, + &mut emitter, + &mut block, + body_idx, + )?; + + log::trace!("\t\t\tlooking up value expr {value_id:?}"); + let v_lexp_handle = + get_expr_handle!(value_id, self.lookup_expression.lookup(value_id)?); + + log::trace!("\t\t\tlooking up comparator expr {value_id:?}"); + let c_lexp_handle = get_expr_handle!( + comparator_id, + self.lookup_expression.lookup(comparator_id)? + ); + + // We know from the SPIR-V spec that the result type must be an integer + // scalar, and we'll need the type itself to get a handle to the atomic + // result struct. + let crate::TypeInner::Scalar(scalar) = ctx.module.types[p_base_ty_h].inner + else { + return Err( + crate::front::atomic_upgrade::Error::CompareExchangeNonScalarBaseType + .into(), + ); + }; + + // Get a handle to the atomic result struct type. + let atomic_result_struct_ty_h = ctx.module.generate_predeclared_type( + crate::PredeclaredType::AtomicCompareExchangeWeakResult(scalar), + ); + + block.extend(emitter.finish(ctx.expressions)); + + // Create an expression for our atomic result + let atomic_lexp_handle = { + let expr = crate::Expression::AtomicResult { + ty: atomic_result_struct_ty_h, + comparison: true, + }; + ctx.expressions.append(expr, span) + }; + + // Create an dot accessor to extract the value from the + // result struct __atomic_compare_exchange_result<T> and use that + // as the expression for the result_id + { + let expr = crate::Expression::AccessIndex { + base: atomic_lexp_handle, + index: 0, + }; + let handle = ctx.expressions.append(expr, span); + // Use this dot accessor as the result id's expression + let _ = self.lookup_expression.insert( + result_id, + LookupExpression { + handle, + type_id: result_type_id, + block_id, + }, + ); + } + + emitter.start(ctx.expressions); + + // Create a statement for the op itself + let stmt = crate::Statement::Atomic { + pointer: p_exp_h, + fun: crate::AtomicFunction::Exchange { + compare: Some(c_lexp_handle), + }, + value: v_lexp_handle, + result: Some(atomic_lexp_handle), + }; + block.push(stmt, span); + + // Store any associated global variables so we can upgrade their types later + self.record_atomic_access(ctx, p_exp_h)?; + } + Op::AtomicExchange + | Op::AtomicIAdd + | Op::AtomicISub + | Op::AtomicSMin + | Op::AtomicUMin + | Op::AtomicSMax + | Op::AtomicUMax + | Op::AtomicAnd + | Op::AtomicOr + | Op::AtomicXor + | Op::AtomicFAddEXT => self.parse_atomic_expr_with_value( + inst, + &mut emitter, + ctx, + &mut block, + block_id, + body_idx, + match inst.op { + Op::AtomicExchange => crate::AtomicFunction::Exchange { compare: None }, + Op::AtomicIAdd | Op::AtomicFAddEXT => crate::AtomicFunction::Add, + Op::AtomicISub => crate::AtomicFunction::Subtract, + Op::AtomicSMin => crate::AtomicFunction::Min, + Op::AtomicUMin => crate::AtomicFunction::Min, + Op::AtomicSMax => crate::AtomicFunction::Max, + Op::AtomicUMax => crate::AtomicFunction::Max, + Op::AtomicAnd => crate::AtomicFunction::And, + Op::AtomicOr => crate::AtomicFunction::InclusiveOr, + Op::AtomicXor => crate::AtomicFunction::ExclusiveOr, + _ => unreachable!(), + }, + )?, + + _ => { + return Err(Error::UnsupportedInstruction(self.state, inst.op)); + } + } + }; + + block.extend(emitter.finish(ctx.expressions)); + if let Some(stmt) = terminator { + block.push(stmt, crate::Span::default()); + } + + // Save this block fragment in `block_ctx.blocks`, and mark it to be + // incorporated into the current body at `Statement` assembly time. + ctx.blocks.insert(block_id, block); + let body = &mut ctx.bodies[body_idx]; + body.data.push(BodyFragment::BlockId(block_id)); + Ok(()) + } +} + +fn make_index_literal( + ctx: &mut BlockContext, + index: u32, + block: &mut crate::Block, + emitter: &mut crate::proc::Emitter, + index_type: Handle<crate::Type>, + index_type_id: spirv::Word, + span: crate::Span, +) -> Result<Handle<crate::Expression>, Error> { + block.extend(emitter.finish(ctx.expressions)); + + let literal = match ctx.module.types[index_type].inner.scalar_kind() { + Some(crate::ScalarKind::Uint) => crate::Literal::U32(index), + Some(crate::ScalarKind::Sint) => crate::Literal::I32(index as i32), + _ => return Err(Error::InvalidIndexType(index_type_id)), + }; + let expr = ctx + .expressions + .append(crate::Expression::Literal(literal), span); + + emitter.start(ctx.expressions); + Ok(expr) +} diff --git a/third_party/rust/naga/src/front/wgsl/error.rs b/third_party/rust/naga/src/front/wgsl/error.rs @@ -199,6 +199,7 @@ pub(crate) enum Error<'a> { InvalidIdentifierUnderscore(Span), ReservedIdentifierPrefix(Span), UnknownAddressSpace(Span), + InvalidLocalVariableAddressSpace(Span), RepeatedAttribute(Span), UnknownAttribute(Span), UnknownBuiltin(Span), @@ -660,6 +661,11 @@ impl<'a> Error<'a> { labels: vec![(bad_span, "unknown address space".into())], notes: vec![], }, + Error::InvalidLocalVariableAddressSpace(bad_span) => ParseError { + message: format!("invalid address space for local variable: `{}`", &source[bad_span]), + labels: vec![(bad_span, "local variables can only use 'function' address space".into())], + notes: vec![], + }, Error::RepeatedAttribute(bad_span) => ParseError { message: format!("repeated attribute: `{}`", &source[bad_span]), labels: vec![(bad_span, "repeated attribute".into())], diff --git a/third_party/rust/naga/src/front/wgsl/lower/mod.rs b/third_party/rust/naga/src/front/wgsl/lower/mod.rs @@ -426,6 +426,13 @@ impl TypeContext for ExpressionContext<'_, '_, '_> { } impl<'source, 'temp, 'out> ExpressionContext<'source, 'temp, 'out> { + const fn is_runtime(&self) -> bool { + match self.expr_type { + ExpressionContextType::Runtime(_) => true, + ExpressionContextType::Constant(_) | ExpressionContextType::Override => false, + } + } + #[allow(dead_code)] fn as_const(&mut self) -> ExpressionContext<'source, '_, '_> { ExpressionContext { @@ -553,6 +560,28 @@ impl<'source, 'temp, 'out> ExpressionContext<'source, 'temp, 'out> { } } + fn const_eval_expr_to_bool(&self, handle: Handle<ir::Expression>) -> Option<bool> { + match self.expr_type { + ExpressionContextType::Runtime(ref ctx) => { + if !ctx.local_expression_kind_tracker.is_const(handle) { + return None; + } + + self.module + .to_ctx() + .eval_expr_to_bool_from(handle, &ctx.function.expressions) + } + ExpressionContextType::Constant(Some(ref ctx)) => { + assert!(ctx.local_expression_kind_tracker.is_const(handle)); + self.module + .to_ctx() + .eval_expr_to_bool_from(handle, &ctx.function.expressions) + } + ExpressionContextType::Constant(None) => self.module.to_ctx().eval_expr_to_bool(handle), + ExpressionContextType::Override => None, + } + } + /// Return `true` if `handle` is a constant expression. fn is_const(&self, handle: Handle<ir::Expression>) -> bool { use ExpressionContextType as Ect; @@ -588,6 +617,16 @@ impl<'source, 'temp, 'out> ExpressionContext<'source, 'temp, 'out> { } } + fn get(&self, handle: Handle<crate::Expression>) -> &crate::Expression { + match self.expr_type { + ExpressionContextType::Runtime(ref ctx) + | ExpressionContextType::Constant(Some(ref ctx)) => &ctx.function.expressions[handle], + ExpressionContextType::Constant(None) | ExpressionContextType::Override => { + &self.module.global_expressions[handle] + } + } + } + fn local( &mut self, local: &Handle<ast::Local>, @@ -614,6 +653,52 @@ impl<'source, 'temp, 'out> ExpressionContext<'source, 'temp, 'out> { } } + fn with_nested_runtime_expression_ctx<'a, F, T>( + &mut self, + span: Span, + f: F, + ) -> Result<'source, (T, crate::Block)> + where + for<'t> F: FnOnce(&mut ExpressionContext<'source, 't, 't>) -> Result<'source, T>, + { + let mut block = crate::Block::new(); + let rctx = match self.expr_type { + ExpressionContextType::Runtime(ref mut rctx) => Ok(rctx), + ExpressionContextType::Constant(_) | ExpressionContextType::Override => { + Err(Error::UnexpectedOperationInConstContext(span)) + } + }?; + + rctx.block + .extend(rctx.emitter.finish(&rctx.function.expressions)); + rctx.emitter.start(&rctx.function.expressions); + + let nested_rctx = LocalExpressionContext { + local_table: rctx.local_table, + function: rctx.function, + block: &mut block, + emitter: rctx.emitter, + typifier: rctx.typifier, + local_expression_kind_tracker: rctx.local_expression_kind_tracker, + }; + let mut nested_ctx = ExpressionContext { + expr_type: ExpressionContextType::Runtime(nested_rctx), + ast_expressions: self.ast_expressions, + types: self.types, + globals: self.globals, + module: self.module, + const_typifier: self.const_typifier, + layouter: self.layouter, + global_expression_kind_tracker: self.global_expression_kind_tracker, + }; + let ret = f(&mut nested_ctx)?; + + block.extend(rctx.emitter.finish(&rctx.function.expressions)); + rctx.emitter.start(&rctx.function.expressions); + + Ok((ret, block)) + } + fn gather_component( &mut self, expr: Handle<ir::Expression>, @@ -682,7 +767,7 @@ impl<'source, 'temp, 'out> ExpressionContext<'source, 'temp, 'out> { /// Resolve the types of all expressions up through `handle`. /// /// Ensure that [`self.typifier`] has a [`TypeResolution`] for - /// every expression in [`self.function.expressions`]. + /// every expression in `self.function.expressions`. /// /// This does not add types to any arena. The [`Typifier`] /// documentation explains the steps we take to avoid filling @@ -2375,6 +2460,135 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { expr.try_map(|handle| ctx.append_expression(handle, span)) } + /// Generate IR for the short-circuiting operators `&&` and `||`. + /// + /// `binary` has already lowered the LHS expression and resolved its type. + fn logical( + &mut self, + op: crate::BinaryOperator, + left: Handle<crate::Expression>, + right: Handle<ast::Expression<'source>>, + span: Span, + ctx: &mut ExpressionContext<'source, '_, '_>, + ) -> Result<'source, Typed<crate::Expression>> { + debug_assert!( + op == crate::BinaryOperator::LogicalAnd || op == crate::BinaryOperator::LogicalOr + ); + + if ctx.is_runtime() { + // To simulate short-circuiting behavior, we want to generate IR + // like the following for `&&`. For `||`, the condition is `!_lhs` + // and the else value is `true`. + // + // var _e0: bool; + // if _lhs { + // _e0 = _rhs; + // } else { + // _e0 = false; + // } + + let (condition, else_val) = if op == crate::BinaryOperator::LogicalAnd { + let condition = left; + let else_val = ctx.append_expression( + crate::Expression::Literal(crate::Literal::Bool(false)), + span, + )?; + (condition, else_val) + } else { + let condition = ctx.append_expression( + crate::Expression::Unary { + op: crate::UnaryOperator::LogicalNot, + expr: left, + }, + span, + )?; + let else_val = ctx.append_expression( + crate::Expression::Literal(crate::Literal::Bool(true)), + span, + )?; + (condition, else_val) + }; + + let bool_ty = ctx.ensure_type_exists(crate::TypeInner::Scalar(crate::Scalar::BOOL)); + + let rctx = ctx.runtime_expression_ctx(span)?; + let result_var = rctx.function.local_variables.append( + crate::LocalVariable { + name: None, + ty: bool_ty, + init: None, + }, + span, + ); + let pointer = + ctx.append_expression(crate::Expression::LocalVariable(result_var), span)?; + + let (right, mut accept) = ctx.with_nested_runtime_expression_ctx(span, |ctx| { + let right = self.expression_for_abstract(right, ctx)?; + ctx.grow_types(right)?; + Ok(right) + })?; + + accept.push( + crate::Statement::Store { + pointer, + value: right, + }, + span, + ); + + let mut reject = crate::Block::with_capacity(1); + reject.push( + crate::Statement::Store { + pointer, + value: else_val, + }, + span, + ); + + let rctx = ctx.runtime_expression_ctx(span)?; + rctx.block.push( + crate::Statement::If { + condition, + accept, + reject, + }, + span, + ); + + Ok(Typed::Reference(crate::Expression::LocalVariable( + result_var, + ))) + } else { + let left_val = ctx.const_eval_expr_to_bool(left); + + if left_val.is_some_and(|left_val| { + op == crate::BinaryOperator::LogicalAnd && !left_val + || op == crate::BinaryOperator::LogicalOr && left_val + }) { + // Short-circuit behavior: don't evaluate the RHS. + + // TODO(https://github.com/gfx-rs/wgpu/issues/8440): We shouldn't ignore the + // RHS completely, it should still be type-checked. Preserving it for type + // checking is a bit tricky, because we're trying to produce an expression + // for a const context, but the RHS is allowed to have things that aren't + // const. + + Ok(Typed::Plain(ctx.get(left).clone())) + } else { + // Evaluate the RHS and construct the entire binary expression as we + // normally would. This case applies to well-formed constant logical + // expressions that don't short-circuit (handled by the constant evaluator + // shortly), to override expressions (handled when overrides are processed) + // and to non-well-formed expressions (rejected by type checking). + let right = self.expression_for_abstract(right, ctx)?; + ctx.grow_types(right)?; + + Ok(Typed::Plain(crate::Expression::Binary { op, left, right })) + } + } + } + fn binary( &mut self, op: ir::BinaryOperator, @@ -2383,57 +2597,74 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { span: Span, ctx: &mut ExpressionContext<'source, '_, '_>, ) -> Result<'source, Typed<ir::Expression>> { - // Load both operands. - let mut left = self.expression_for_abstract(left, ctx)?; - let mut right = self.expression_for_abstract(right, ctx)?; - - // Convert `scalar op vector` to `vector op vector` by introducing - // `Splat` expressions. - ctx.binary_op_splat(op, &mut left, &mut right)?; - - // Apply automatic conversions. - match op { - ir::BinaryOperator::ShiftLeft | ir::BinaryOperator::ShiftRight => { - // Shift operators require the right operand to be `u32` or - // `vecN<u32>`. We can let the validator sort out vector length - // issues, but the right operand must be, or convert to, a u32 leaf - // scalar. - right = - ctx.try_automatic_conversion_for_leaf_scalar(right, ir::Scalar::U32, span)?; - - // Additionally, we must concretize the left operand if the right operand - // is not a const-expression. - // See https://www.w3.org/TR/WGSL/#overload-resolution-section. - // - // 2. Eliminate any candidate where one of its subexpressions resolves to - // an abstract type after feasible automatic conversions, but another of - // the candidate’s subexpressions is not a const-expression. - // - // We only have to explicitly do so for shifts as their operands may be - // of different types - for other binary ops this is achieved by finding - // the conversion consensus for both operands. - if !ctx.is_const(right) { - left = ctx.concretize(left)?; - } + if op == ir::BinaryOperator::LogicalAnd || op == ir::BinaryOperator::LogicalOr { + let left = self.expression_for_abstract(left, ctx)?; + ctx.grow_types(left)?; + + if !matches!( + resolve_inner!(ctx, left), + &ir::TypeInner::Scalar(ir::Scalar::BOOL) + ) { + // Pass it through as-is, will fail validation + let right = self.expression_for_abstract(right, ctx)?; + ctx.grow_types(right)?; + Ok(Typed::Plain(crate::Expression::Binary { op, left, right })) + } else { + self.logical(op, left, right, span, ctx) } + } else { + // Load both operands. + let mut left = self.expression_for_abstract(left, ctx)?; + let mut right = self.expression_for_abstract(right, ctx)?; + + // Convert `scalar op vector` to `vector op vector` by introducing + // `Splat` expressions. + ctx.binary_op_splat(op, &mut left, &mut right)?; + + // Apply automatic conversions. + match op { + ir::BinaryOperator::ShiftLeft | ir::BinaryOperator::ShiftRight => { + // Shift operators require the right operand to be `u32` or + // `vecN<u32>`. We can let the validator sort out vector length + // issues, but the right operand must be, or convert to, a u32 leaf + // scalar. + right = + ctx.try_automatic_conversion_for_leaf_scalar(right, ir::Scalar::U32, span)?; + + // Additionally, we must concretize the left operand if the right operand + // is not a const-expression. + // See https://www.w3.org/TR/WGSL/#overload-resolution-section. + // + // 2. Eliminate any candidate where one of its subexpressions resolves to + // an abstract type after feasible automatic conversions, but another of + // the candidate’s subexpressions is not a const-expression. + // + // We only have to explicitly do so for shifts as their operands may be + // of different types - for other binary ops this is achieved by finding + // the conversion consensus for both operands. + if !ctx.is_const(right) { + left = ctx.concretize(left)?; + } + } - // All other operators follow the same pattern: reconcile the - // scalar leaf types. If there's no reconciliation possible, - // leave the expressions as they are: validation will report the - // problem. - _ => { - ctx.grow_types(left)?; - ctx.grow_types(right)?; - if let Ok(consensus_scalar) = - ctx.automatic_conversion_consensus([left, right].iter()) - { - ctx.convert_to_leaf_scalar(&mut left, consensus_scalar)?; - ctx.convert_to_leaf_scalar(&mut right, consensus_scalar)?; + // All other operators follow the same pattern: reconcile the + // scalar leaf types. If there's no reconciliation possible, + // leave the expressions as they are: validation will report the + // problem. + _ => { + ctx.grow_types(left)?; + ctx.grow_types(right)?; + if let Ok(consensus_scalar) = + ctx.automatic_conversion_consensus([left, right].iter()) + { + ctx.convert_to_leaf_scalar(&mut left, consensus_scalar)?; + ctx.convert_to_leaf_scalar(&mut right, consensus_scalar)?; + } } } - } - Ok(Typed::Plain(ir::Expression::Binary { op, left, right })) + Ok(Typed::Plain(ir::Expression::Binary { op, left, right })) + } } /// Generate Naga IR for call expressions and statements, and type diff --git a/third_party/rust/naga/src/front/wgsl/parse/conv.rs b/third_party/rust/naga/src/front/wgsl/parse/conv.rs @@ -18,7 +18,7 @@ pub fn map_address_space<'a>( "storage" => Ok(crate::AddressSpace::Storage { access: crate::StorageAccess::default(), }), - "push_constant" => Ok(crate::AddressSpace::PushConstant), + "immediate" => Ok(crate::AddressSpace::Immediate), "function" => Ok(crate::AddressSpace::Function), "task_payload" => { if enable_extensions.contains(ImplementedEnableExtension::WgpuMeshShader) { diff --git a/third_party/rust/naga/src/front/wgsl/parse/directive/enable_extension.rs b/third_party/rust/naga/src/front/wgsl/parse/directive/enable_extension.rs @@ -11,6 +11,8 @@ use alloc::boxed::Box; #[derive(Clone, Debug, Eq, PartialEq)] pub struct EnableExtensions { wgpu_mesh_shader: bool, + wgpu_ray_query: bool, + wgpu_ray_query_vertex_return: bool, dual_source_blending: bool, /// Whether `enable f16;` was written earlier in the shader module. f16: bool, @@ -21,6 +23,8 @@ impl EnableExtensions { pub(crate) const fn empty() -> Self { Self { wgpu_mesh_shader: false, + wgpu_ray_query: false, + wgpu_ray_query_vertex_return: false, f16: false, dual_source_blending: false, clip_distances: false, @@ -31,6 +35,10 @@ impl EnableExtensions { pub(crate) fn add(&mut self, ext: ImplementedEnableExtension) { let field = match ext { ImplementedEnableExtension::WgpuMeshShader => &mut self.wgpu_mesh_shader, + ImplementedEnableExtension::WgpuRayQuery => &mut self.wgpu_ray_query, + ImplementedEnableExtension::WgpuRayQueryVertexReturn => { + &mut self.wgpu_ray_query_vertex_return + } ImplementedEnableExtension::DualSourceBlending => &mut self.dual_source_blending, ImplementedEnableExtension::F16 => &mut self.f16, ImplementedEnableExtension::ClipDistances => &mut self.clip_distances, @@ -42,6 +50,10 @@ impl EnableExtensions { pub(crate) const fn contains(&self, ext: ImplementedEnableExtension) -> bool { match ext { ImplementedEnableExtension::WgpuMeshShader => self.wgpu_mesh_shader, + ImplementedEnableExtension::WgpuRayQuery => self.wgpu_ray_query, + ImplementedEnableExtension::WgpuRayQueryVertexReturn => { + self.wgpu_ray_query_vertex_return + } ImplementedEnableExtension::DualSourceBlending => self.dual_source_blending, ImplementedEnableExtension::F16 => self.f16, ImplementedEnableExtension::ClipDistances => self.clip_distances, @@ -75,6 +87,8 @@ impl EnableExtension { const CLIP_DISTANCES: &'static str = "clip_distances"; const DUAL_SOURCE_BLENDING: &'static str = "dual_source_blending"; const MESH_SHADER: &'static str = "wgpu_mesh_shader"; + const RAY_QUERY: &'static str = "wgpu_ray_query"; + const RAY_QUERY_VERTEX_RETURN: &'static str = "wgpu_ray_query_vertex_return"; const SUBGROUPS: &'static str = "subgroups"; const PRIMITIVE_INDEX: &'static str = "primitive_index"; @@ -87,6 +101,10 @@ impl EnableExtension { Self::Implemented(ImplementedEnableExtension::DualSourceBlending) } Self::MESH_SHADER => Self::Implemented(ImplementedEnableExtension::WgpuMeshShader), + Self::RAY_QUERY => Self::Implemented(ImplementedEnableExtension::WgpuRayQuery), + Self::RAY_QUERY_VERTEX_RETURN => { + Self::Implemented(ImplementedEnableExtension::WgpuRayQueryVertexReturn) + } Self::SUBGROUPS => Self::Unimplemented(UnimplementedEnableExtension::Subgroups), Self::PRIMITIVE_INDEX => { Self::Unimplemented(UnimplementedEnableExtension::PrimitiveIndex) @@ -100,6 +118,10 @@ impl EnableExtension { match self { Self::Implemented(kind) => match kind { ImplementedEnableExtension::WgpuMeshShader => Self::MESH_SHADER, + ImplementedEnableExtension::WgpuRayQuery => Self::RAY_QUERY, + ImplementedEnableExtension::WgpuRayQueryVertexReturn => { + Self::RAY_QUERY_VERTEX_RETURN + } ImplementedEnableExtension::DualSourceBlending => Self::DUAL_SOURCE_BLENDING, ImplementedEnableExtension::F16 => Self::F16, ImplementedEnableExtension::ClipDistances => Self::CLIP_DISTANCES, @@ -135,6 +157,10 @@ pub enum ImplementedEnableExtension { ClipDistances, /// Enables the `wgpu_mesh_shader` extension, native only WgpuMeshShader, + /// Enables the `wgpu_ray_query` extension, native only. + WgpuRayQuery, + /// Enables the `wgpu_ray_query_vertex_return` extension, native only. + WgpuRayQueryVertexReturn, } /// A variant of [`EnableExtension::Unimplemented`]. diff --git a/third_party/rust/naga/src/front/wgsl/parse/mod.rs b/third_party/rust/naga/src/front/wgsl/parse/mod.rs @@ -1930,15 +1930,87 @@ impl Parser { } } "acceleration_structure" => { + if !lexer + .enable_extensions + .contains(ImplementedEnableExtension::WgpuRayQuery) + { + return Err(Box::new(Error::EnableExtensionNotEnabled { + kind: EnableExtension::Implemented( + ImplementedEnableExtension::WgpuRayQuery, + ), + span, + })); + } let vertex_return = lexer.next_acceleration_structure_flags()?; + if !lexer + .enable_extensions + .contains(ImplementedEnableExtension::WgpuRayQueryVertexReturn) + && vertex_return + { + return Err(Box::new(Error::EnableExtensionNotEnabled { + kind: EnableExtension::Implemented( + ImplementedEnableExtension::WgpuRayQueryVertexReturn, + ), + span, + })); + } ast::Type::AccelerationStructure { vertex_return } } "ray_query" => { + if !lexer + .enable_extensions + .contains(ImplementedEnableExtension::WgpuRayQuery) + { + return Err(Box::new(Error::EnableExtensionNotEnabled { + kind: EnableExtension::Implemented( + ImplementedEnableExtension::WgpuRayQuery, + ), + span, + })); + } let vertex_return = lexer.next_acceleration_structure_flags()?; + if !lexer + .enable_extensions + .contains(ImplementedEnableExtension::WgpuRayQueryVertexReturn) + && vertex_return + { + return Err(Box::new(Error::EnableExtensionNotEnabled { + kind: EnableExtension::Implemented( + ImplementedEnableExtension::WgpuRayQueryVertexReturn, + ), + span, + })); + } ast::Type::RayQuery { vertex_return } } - "RayDesc" => ast::Type::RayDesc, - "RayIntersection" => ast::Type::RayIntersection, + "RayDesc" => { + if !lexer + .enable_extensions + .contains(ImplementedEnableExtension::WgpuRayQuery) + { + return Err(Box::new(Error::EnableExtensionNotEnabled { + kind: EnableExtension::Implemented( + ImplementedEnableExtension::WgpuRayQuery, + ), + span, + })); + } + ast::Type::RayDesc + } + "RayIntersection" => { + if !lexer + .enable_extensions + .contains(ImplementedEnableExtension::WgpuRayQuery) + { + return Err(Box::new(Error::EnableExtensionNotEnabled { + kind: EnableExtension::Implemented( + ImplementedEnableExtension::WgpuRayQuery, + ), + span, + })); + } + ast::Type::RayIntersection + } _ => return Ok(None), })) } @@ -2201,6 +2273,16 @@ impl Parser { "var" => { let _ = lexer.next(); + if lexer.skip(Token::Paren('<')) { + let (class_str, span) = lexer.next_ident_with_span()?; + if class_str != "function" { + return Err(Box::new(Error::InvalidLocalVariableAddressSpace( + span, + ))); + } + lexer.expect(Token::Paren('>'))?; + } + let name = lexer.next_ident()?; let ty = if lexer.skip(Token::Separator(':')) { let ty = this.type_decl(lexer, ctx)?; diff --git a/third_party/rust/naga/src/front/wgsl/tests.rs b/third_party/rust/naga/src/front/wgsl/tests.rs @@ -457,6 +457,41 @@ fn parse_assignment_statements() { } #[test] +fn parse_local_var_address_space() { + parse_str( + " + fn foo() { + var<function> a = true; + var<function> b: i32 = 5; + var c = 10; + }", + ) + .unwrap(); + + let error = parse_str( + "fn foo() { + var<private> x: i32 = 5; + }", + ) + .unwrap_err(); + assert_eq!( + error.message(), + "invalid address space for local variable: `private`", + ); + + let error = parse_str( + "fn foo() { + var<storage> x: i32 = 5; + }", + ) + .unwrap_err(); + assert_eq!( + error.message(), + "invalid address space for local variable: `storage`", + ); +} + +#[test] fn binary_expression_mixed_scalar_and_vector_operands() { for (operand, expect_splat) in [ ('<', false), diff --git a/third_party/rust/naga/src/ir/mod.rs b/third_party/rust/naga/src/ir/mod.rs @@ -356,21 +356,21 @@ pub enum AddressSpace { /// Opaque handles, such as samplers and images. Handle, - /// Push constants. + /// Immediate data. /// /// A [`Module`] may contain at most one [`GlobalVariable`] in /// this address space. Its contents are provided not by a buffer - /// but by `SetPushConstant` pass commands, allowing the CPU to + /// but by `SetImmediates` pass commands, allowing the CPU to /// establish different values for each draw/dispatch. /// - /// `PushConstant` variables may not contain `f16` values, even if + /// `Immediate` variables may not contain `f16` values, even if /// the [`SHADER_FLOAT16`] capability is enabled. /// /// Backends generally place tight limits on the size of - /// `PushConstant` variables. + /// `Immediate` variables. /// /// [`SHADER_FLOAT16`]: crate::valid::Capabilities::SHADER_FLOAT16 - PushConstant, + Immediate, /// Task shader to mesh shader payload TaskPayload, } diff --git a/third_party/rust/naga/src/proc/constant_evaluator.rs b/third_party/rust/naga/src/proc/constant_evaluator.rs @@ -1189,8 +1189,13 @@ impl<'a> ConstantEvaluator<'a> { Behavior::Wgsl(WgslRestrictions::Const(_)) => { Err(ConstantEvaluatorError::OverrideExpr) } - Behavior::Glsl(_) => { - unreachable!() + + // GLSL specialization constants (constant_id) become Override expressions + Behavior::Glsl(GlslRestrictions::Runtime(_)) => { + Ok(self.append_expr(expr, span, ExpressionKind::Override)) + } + Behavior::Glsl(GlslRestrictions::Const) => { + Err(ConstantEvaluatorError::OverrideExpr) } }, ExpressionKind::Runtime => { diff --git a/third_party/rust/naga/src/proc/layouter.rs b/third_party/rust/naga/src/proc/layouter.rs @@ -161,10 +161,11 @@ impl Layouter { self.layouts.clear(); } + #[expect(rustdoc::private_intra_doc_links)] /// Extend this `Layouter` with layouts for any new entries in `gctx.types`. /// /// Ensure that every type in `gctx.types` has a corresponding [TypeLayout] - /// in [`self.layouts`]. + /// in [`Self::layouts`]. /// /// Some front ends need to be able to compute layouts for existing types /// while module construction is still in progress and new types are still diff --git a/third_party/rust/naga/src/proc/mod.rs b/third_party/rust/naga/src/proc/mod.rs @@ -24,7 +24,9 @@ pub use namer::{EntryPointIndex, ExternalTextureNameKey, NameKey, Namer}; pub use overloads::{Conclusion, MissingSpecialType, OverloadSet, Rule}; pub use terminator::ensure_block_returns; use thiserror::Error; -pub use type_methods::min_max_float_representable_by; +pub use type_methods::{ + concrete_int_scalars, min_max_float_representable_by, vector_size_str, vector_sizes, +}; pub use typifier::{compare_types, ResolveContext, ResolveError, TypeResolution}; use crate::non_max_u32::NonMaxU32; @@ -180,7 +182,7 @@ impl super::AddressSpace { crate::AddressSpace::Uniform => Sa::LOAD, crate::AddressSpace::Storage { access } => access, crate::AddressSpace::Handle => Sa::LOAD, - crate::AddressSpace::PushConstant => Sa::LOAD, + crate::AddressSpace::Immediate => Sa::LOAD, // TaskPayload isn't always writable, but this is checked for elsewhere, // when not using multiple payloads and matching the entry payload is checked. crate::AddressSpace::TaskPayload => Sa::LOAD | Sa::STORE, @@ -438,7 +440,18 @@ pub struct GlobalCtx<'a> { impl GlobalCtx<'_> { /// Try to evaluate the expression in `self.global_expressions` using its `handle` and return it as a `u32`. - #[allow(dead_code)] + #[cfg_attr( + not(any( + feature = "glsl-in", + feature = "spv-in", + feature = "wgsl-in", + glsl_out, + hlsl_out, + msl_out, + wgsl_out + )), + allow(dead_code) + )] pub(super) fn eval_expr_to_u32( &self, handle: crate::Handle<crate::Expression>, @@ -461,8 +474,17 @@ impl GlobalCtx<'_> { } } + /// Try to evaluate the expression in `self.global_expressions` using its `handle` and return it as a `bool`. + #[cfg_attr(not(feature = "wgsl-in"), allow(dead_code))] + pub(super) fn eval_expr_to_bool( + &self, + handle: crate::Handle<crate::Expression>, + ) -> Option<bool> { + self.eval_expr_to_bool_from(handle, self.global_expressions) + } + /// Try to evaluate the expression in the `arena` using its `handle` and return it as a `bool`. - #[allow(dead_code)] + #[cfg_attr(not(feature = "wgsl-in"), allow(dead_code))] pub(super) fn eval_expr_to_bool_from( &self, handle: crate::Handle<crate::Expression>, @@ -474,7 +496,7 @@ impl GlobalCtx<'_> { } } - #[allow(dead_code)] + #[expect(dead_code)] pub(crate) fn eval_expr_to_literal( &self, handle: crate::Handle<crate::Expression>, diff --git a/third_party/rust/naga/src/proc/namer.rs b/third_party/rust/naga/src/proc/namer.rs @@ -51,6 +51,7 @@ impl ExternalTextureNameKey { #[derive(Debug, Eq, Hash, PartialEq)] pub enum NameKey { Constant(Handle<crate::Constant>), + Override(Handle<crate::Override>), GlobalVariable(Handle<crate::GlobalVariable>), Type(Handle<crate::Type>), StructMember(Handle<crate::Type>, u32), @@ -374,6 +375,21 @@ impl Namer { let name = self.call(label); output.insert(NameKey::Constant(handle), name); } + + for (handle, override_) in module.overrides.iter() { + let label = match override_.name { + Some(ref name) => name, + None => { + use core::fmt::Write; + // Try to be more descriptive about the override values + temp.clear(); + write!(temp, "override_{}", output[&NameKey::Type(override_.ty)]).unwrap(); + &temp + } + }; + let name = self.call(label); + output.insert(NameKey::Override(handle), name); + } } } diff --git a/third_party/rust/naga/src/proc/overloads/list.rs b/third_party/rust/naga/src/proc/overloads/list.rs @@ -162,6 +162,10 @@ impl crate::proc::overloads::OverloadSet for List { } const fn len_to_full_mask(n: usize) -> u64 { + // This is a const function, which _sometimes_ gets called, + // so this lint is _sometimes_ triggered, depending on feature set. + #[expect(clippy::allow_attributes)] + #[allow(clippy::panic)] if n >= 64 { panic!("List::rules can only hold up to 63 rules"); } diff --git a/third_party/rust/naga/src/proc/overloads/mathfunction.rs b/third_party/rust/naga/src/proc/overloads/mathfunction.rs @@ -4,10 +4,10 @@ use crate::proc::overloads::any_overload_set::AnyOverloadSet; use crate::proc::overloads::list::List; use crate::proc::overloads::regular::regular; use crate::proc::overloads::utils::{ - concrete_int_scalars, float_scalars, float_scalars_unimplemented_abstract, list, pairs, rule, - scalar_or_vecn, triples, vector_sizes, + float_scalars, float_scalars_unimplemented_abstract, list, pairs, rule, scalar_or_vecn, triples, }; use crate::proc::overloads::OverloadSet; +use crate::proc::type_methods::{concrete_int_scalars, vector_sizes}; use crate::ir; diff --git a/third_party/rust/naga/src/proc/overloads/utils.rs b/third_party/rust/naga/src/proc/overloads/utils.rs @@ -9,17 +9,6 @@ use crate::proc::TypeResolution; use alloc::vec::Vec; -/// Produce all vector sizes. -pub fn vector_sizes() -> impl Iterator<Item = ir::VectorSize> + Clone { - static SIZES: [ir::VectorSize; 3] = [ - ir::VectorSize::Bi, - ir::VectorSize::Tri, - ir::VectorSize::Quad, - ]; - - SIZES.iter().cloned() -} - /// Produce all the floating-point [`ir::Scalar`]s. /// /// Note that `F32` must appear before other sizes; this is how we @@ -40,20 +29,6 @@ pub fn float_scalars_unimplemented_abstract() -> impl Iterator<Item = ir::Scalar [ir::Scalar::F32, ir::Scalar::F16, ir::Scalar::F64].into_iter() } -/// Produce all concrete integer [`ir::Scalar`]s. -/// -/// Note that `I32` and `U32` must come first; this is how we -/// represent conversion rank. -pub fn concrete_int_scalars() -> impl Iterator<Item = ir::Scalar> { - [ - ir::Scalar::I32, - ir::Scalar::U32, - ir::Scalar::I64, - ir::Scalar::U64, - ] - .into_iter() -} - /// Produce the scalar and vector [`ir::TypeInner`]s that have `s` as /// their scalar. pub fn scalar_or_vecn(scalar: ir::Scalar) -> impl Iterator<Item = ir::TypeInner> { diff --git a/third_party/rust/naga/src/proc/type_methods.rs b/third_party/rust/naga/src/proc/type_methods.rs @@ -1,8 +1,9 @@ -//! Methods on [`TypeInner`], [`Scalar`], and [`ScalarKind`]. +//! Methods on or related to [`TypeInner`], [`Scalar`], [`ScalarKind`], and [`VectorSize`]. //! //! [`TypeInner`]: crate::TypeInner //! [`Scalar`]: crate::Scalar //! [`ScalarKind`]: crate::ScalarKind +//! [`VectorSize`]: crate::VectorSize use crate::{ir, valid::MAX_TYPE_SIZE}; @@ -97,6 +98,31 @@ impl crate::Scalar { } } +/// Produce all concrete integer [`ir::Scalar`]s. +/// +/// Note that `I32` and `U32` must come first; this represents conversion rank +/// in overload resolution. +pub fn concrete_int_scalars() -> impl Iterator<Item = ir::Scalar> { + [ + ir::Scalar::I32, + ir::Scalar::U32, + ir::Scalar::I64, + ir::Scalar::U64, + ] + .into_iter() +} + +/// Produce all vector sizes. +pub fn vector_sizes() -> impl Iterator<Item = ir::VectorSize> + Clone { + static SIZES: [ir::VectorSize; 3] = [ + ir::VectorSize::Bi, + ir::VectorSize::Tri, + ir::VectorSize::Quad, + ]; + + SIZES.iter().cloned() +} + const POINTER_SPAN: u32 = 4; impl crate::TypeInner { @@ -612,3 +638,12 @@ pub fn min_max_float_representable_by( _ => unreachable!(), } } + +/// Helper function that returns the string corresponding to the [`VectorSize`](crate::VectorSize) +pub const fn vector_size_str(size: crate::VectorSize) -> &'static str { + match size { + crate::VectorSize::Bi => "2", + crate::VectorSize::Tri => "3", + crate::VectorSize::Quad => "4", + } +} diff --git a/third_party/rust/naga/src/valid/analyzer.rs b/third_party/rust/naga/src/valid/analyzer.rs @@ -551,30 +551,34 @@ impl FunctionInfo { base: array_element_ty_handle, .. } => { - // these are nasty aliases, but these idents are too long and break rustfmt - let sto = super::Capabilities::STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING; - let uni = super::Capabilities::UNIFORM_BUFFER_ARRAY_NON_UNIFORM_INDEXING; - let st_sb = super::Capabilities::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING; - let sampler = super::Capabilities::SAMPLER_NON_UNIFORM_INDEXING; - // We're a binding array, so lets use the type of _what_ we are array of to determine if we can non-uniformly index it. let array_element_ty = &resolve_context.types[array_element_ty_handle].inner; needed_caps |= match *array_element_ty { - // If we're an image, use the appropriate limit. + // If we're an image, use the appropriate capability. crate::TypeInner::Image { class, .. } => match class { - crate::ImageClass::Storage { .. } => sto, - _ => st_sb, + crate::ImageClass::Storage { .. } => { + super::Capabilities::STORAGE_TEXTURE_BINDING_ARRAY_NON_UNIFORM_INDEXING + } + _ => { + super::Capabilities::TEXTURE_AND_SAMPLER_BINDING_ARRAY_NON_UNIFORM_INDEXING + } }, - crate::TypeInner::Sampler { .. } => sampler, - // If we're anything but an image, assume we're a buffer and use the address space. + crate::TypeInner::Sampler { .. } => { + super::Capabilities::TEXTURE_AND_SAMPLER_BINDING_ARRAY_NON_UNIFORM_INDEXING + } + // If we're anything but an image or sampler, assume we're a buffer and use the address space. _ => { if let E::GlobalVariable(global_handle) = expression_arena[base] { let global = &resolve_context.global_vars[global_handle]; match global.space { - crate::AddressSpace::Uniform => uni, - crate::AddressSpace::Storage { .. } => st_sb, + crate::AddressSpace::Uniform => { + super::Capabilities::BUFFER_BINDING_ARRAY_NON_UNIFORM_INDEXING + } + crate::AddressSpace::Storage { .. } => { + super::Capabilities::STORAGE_BUFFER_BINDING_ARRAY_NON_UNIFORM_INDEXING + } _ => unreachable!(), } } else { @@ -654,7 +658,7 @@ impl FunctionInfo { // task payload memory is very similar to workgroup memory As::WorkGroup | As::TaskPayload => true, // uniform data - As::Uniform | As::PushConstant => true, + As::Uniform | As::Immediate => true, // storage data is only uniform when read-only As::Storage { access } => !access.contains(crate::StorageAccess::STORE), As::Handle => false, diff --git a/third_party/rust/naga/src/valid/expression.rs b/third_party/rust/naga/src/valid/expression.rs @@ -1165,7 +1165,7 @@ impl super::Validator { // WorkGroupUniformLoad .contains(TypeFlags::SIZED | TypeFlags::CONSTRUCTIBLE) { - ShaderStages::COMPUTE + ShaderStages::COMPUTE_LIKE } else { return Err(ExpressionError::InvalidWorkGroupUniformLoadResultType(ty)); } diff --git a/third_party/rust/naga/src/valid/function.rs b/third_party/rust/naga/src/valid/function.rs @@ -1014,7 +1014,7 @@ impl super::Validator { stages &= super::ShaderStages::FRAGMENT; } S::ControlBarrier(barrier) | S::MemoryBarrier(barrier) => { - stages &= super::ShaderStages::COMPUTE; + stages &= super::ShaderStages::COMPUTE_LIKE; if barrier.contains(crate::Barrier::SUB_GROUP) { if !self.capabilities.contains( super::Capabilities::SUBGROUP | super::Capabilities::SUBGROUP_BARRIER, @@ -1443,7 +1443,7 @@ impl super::Validator { } } S::WorkGroupUniformLoad { pointer, result } => { - stages &= super::ShaderStages::COMPUTE; + stages &= super::ShaderStages::COMPUTE_LIKE; let pointer_inner = context.resolve_type_inner(pointer, &self.valid_expression_set)?; match *pointer_inner { diff --git a/third_party/rust/naga/src/valid/interface.rs b/third_party/rust/naga/src/valid/interface.rs @@ -4,7 +4,7 @@ use bit_set::BitSet; use super::{ analyzer::{FunctionInfo, GlobalUse}, - Capabilities, Disalignment, FunctionError, ModuleInfo, PushConstantError, + Capabilities, Disalignment, FunctionError, ImmediateError, ModuleInfo, }; use crate::arena::{Handle, UniqueArena}; use crate::span::{AddSpan as _, MapErrWithSpan as _, SpanProvider as _, WithSpan}; @@ -18,6 +18,8 @@ pub enum GlobalVariableError { InvalidUsage(crate::AddressSpace), #[error("Type isn't compatible with address space {0:?}")] InvalidType(crate::AddressSpace), + #[error("Type {0:?} isn't compatible with binding arrays")] + InvalidBindingArray(Handle<crate::Type>), #[error("Type flags {seen:?} do not meet the required {required:?}")] MissingTypeFlags { required: super::TypeFlags, @@ -41,8 +43,8 @@ pub enum GlobalVariableError { InitializerNotAllowed(crate::AddressSpace), #[error("Storage address space doesn't support write-only access")] StorageAddressSpaceWriteOnlyNotSupported, - #[error("Type is not valid for use as a push constant")] - InvalidPushConstantType(#[source] PushConstantError), + #[error("Type is not valid for use as a immediate data")] + InvalidImmediateType(#[source] ImmediateError), #[error("Task payload must not be zero-sized")] ZeroSizedTaskPayload, } @@ -65,8 +67,8 @@ pub enum VaryingError { MissingInterpolation, #[error("Built-in {0:?} is not available at this stage")] InvalidBuiltInStage(crate::BuiltIn), - #[error("Built-in type for {0:?} is invalid")] - InvalidBuiltInType(crate::BuiltIn), + #[error("Built-in type for {0:?} is invalid. Found {1:?}")] + InvalidBuiltInType(crate::BuiltIn, crate::TypeInner), #[error("Entry point arguments and return values must all have bindings")] MissingBinding, #[error("Struct member {0} is missing a binding")] @@ -117,8 +119,8 @@ pub enum EntryPointError { ForbiddenStageOperations, #[error("Global variable {0:?} is used incorrectly as {1:?}")] InvalidGlobalUsage(Handle<crate::GlobalVariable>, GlobalUse), - #[error("More than 1 push constant variable is used")] - MoreThanOnePushConstantUsed, + #[error("More than 1 immediate data variable is used")] + MoreThanOneImmediateUsed, #[error("Bindings for {0:?} conflict with other resource")] BindingCollision(Handle<crate::GlobalVariable>), #[error("Argument {0} varying error")] @@ -426,8 +428,7 @@ impl VaryingContext<'_> { return Err(VaryingError::InvalidBuiltInStage(built_in)); } if !type_good { - log::warn!("Wrong builtin type: {ty_inner:?}"); - return Err(VaryingError::InvalidBuiltInType(built_in)); + return Err(VaryingError::InvalidBuiltInType(built_in, ty_inner.clone())); } } crate::Binding::Location { @@ -644,9 +645,80 @@ impl super::Validator { // series of individually bound resources, so we can (mostly) // validate a `binding_array<T>` as if it were just a plain `T`. crate::TypeInner::BindingArray { base, .. } => match var.space { - crate::AddressSpace::Storage { .. } - | crate::AddressSpace::Uniform - | crate::AddressSpace::Handle => base, + crate::AddressSpace::Storage { .. } => { + if !self + .capabilities + .contains(Capabilities::STORAGE_BUFFER_BINDING_ARRAY) + { + return Err(GlobalVariableError::UnsupportedCapability( + Capabilities::STORAGE_BUFFER_BINDING_ARRAY, + )); + } + base + } + crate::AddressSpace::Uniform => { + if !self + .capabilities + .contains(Capabilities::BUFFER_BINDING_ARRAY) + { + return Err(GlobalVariableError::UnsupportedCapability( + Capabilities::BUFFER_BINDING_ARRAY, + )); + } + base + } + crate::AddressSpace::Handle => { + match gctx.types[base].inner { + crate::TypeInner::Image { class, .. } => match class { + crate::ImageClass::Storage { .. } => { + if !self + .capabilities + .contains(Capabilities::STORAGE_TEXTURE_BINDING_ARRAY) + { + return Err(GlobalVariableError::UnsupportedCapability( + Capabilities::STORAGE_TEXTURE_BINDING_ARRAY, + )); + } + } + crate::ImageClass::Sampled { .. } | crate::ImageClass::Depth { .. } => { + if !self + .capabilities + .contains(Capabilities::TEXTURE_AND_SAMPLER_BINDING_ARRAY) + { + return Err(GlobalVariableError::UnsupportedCapability( + Capabilities::TEXTURE_AND_SAMPLER_BINDING_ARRAY, + )); + } + } + crate::ImageClass::External => { + // This should have been rejected in `validate_type`. + unreachable!("binding arrays of external images are not supported"); + } + }, + crate::TypeInner::Sampler { .. } => { + if !self + .capabilities + .contains(Capabilities::TEXTURE_AND_SAMPLER_BINDING_ARRAY) + { + return Err(GlobalVariableError::UnsupportedCapability( + Capabilities::TEXTURE_AND_SAMPLER_BINDING_ARRAY, + )); + } + } + crate::TypeInner::AccelerationStructure { .. } => { + return Err(GlobalVariableError::InvalidBindingArray(base)); + } + crate::TypeInner::RayQuery { .. } => { + // This should have been rejected in `validate_type`. + unreachable!("binding arrays of ray queries are not supported"); + } + _ => { + // Fall through to the regular validation, which will reject `base` + // as invalid in `AddressSpace::Handle`. + } + } + base + } _ => return Err(GlobalVariableError::InvalidUsage(var.space)), }, _ => var.ty, @@ -741,14 +813,14 @@ impl super::Validator { } (TypeFlags::DATA | TypeFlags::SIZED, false) } - crate::AddressSpace::PushConstant => { - if !self.capabilities.contains(Capabilities::PUSH_CONSTANT) { + crate::AddressSpace::Immediate => { + if !self.capabilities.contains(Capabilities::IMMEDIATES) { return Err(GlobalVariableError::UnsupportedCapability( - Capabilities::PUSH_CONSTANT, + Capabilities::IMMEDIATES, )); } - if let Err(ref err) = type_info.push_constant_compatibility { - return Err(GlobalVariableError::InvalidPushConstantType(err.clone())); + if let Err(ref err) = type_info.immediates_compatibility { + return Err(GlobalVariableError::InvalidImmediateType(err.clone())); } ( TypeFlags::DATA @@ -1032,16 +1104,16 @@ impl super::Validator { } { - let mut used_push_constants = module + let mut used_immediates = module .global_variables .iter() - .filter(|&(_, var)| var.space == crate::AddressSpace::PushConstant) + .filter(|&(_, var)| var.space == crate::AddressSpace::Immediate) .map(|(handle, _)| handle) .filter(|&handle| !info[handle].is_empty()); - // Check if there is more than one push constant, and error if so. + // Check if there is more than one immediate data, and error if so. // Use a loop for when returning multiple errors is supported. - if let Some(handle) = used_push_constants.nth(1) { - return Err(EntryPointError::MoreThanOnePushConstantUsed + if let Some(handle) = used_immediates.nth(1) { + return Err(EntryPointError::MoreThanOneImmediateUsed .with_span_handle(handle, &module.global_variables)); } } @@ -1095,7 +1167,7 @@ impl super::Validator { GlobalUse::empty() } } - crate::AddressSpace::PushConstant => GlobalUse::READ, + crate::AddressSpace::Immediate => GlobalUse::READ, }; if !allowed_usage.contains(usage) { log::warn!("\tUsage error for: {var:?}"); diff --git a/third_party/rust/naga/src/valid/mod.rs b/third_party/rust/naga/src/valid/mod.rs @@ -31,7 +31,7 @@ pub use expression::{check_literal_value, LiteralError}; pub use expression::{ConstExpressionError, ExpressionError}; pub use function::{CallError, FunctionError, LocalVariableError, SubgroupError}; pub use interface::{EntryPointError, GlobalVariableError, VaryingError}; -pub use r#type::{Disalignment, PushConstantError, TypeError, TypeFlags, WidthError}; +pub use r#type::{Disalignment, ImmediateError, TypeError, TypeFlags, WidthError}; use self::handles::InvalidHandleError; @@ -83,25 +83,25 @@ bitflags::bitflags! { #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub struct Capabilities: u32 { - /// Support for [`AddressSpace::PushConstant`][1]. + pub struct Capabilities: u64 { + /// Support for [`AddressSpace::Immediate`][1]. /// - /// [1]: crate::AddressSpace::PushConstant - const PUSH_CONSTANT = 1 << 0; + /// [1]: crate::AddressSpace::Immediate + const IMMEDIATES = 1 << 0; /// Float values with width = 8. const FLOAT64 = 1 << 1; /// Support for [`BuiltIn::PrimitiveIndex`][1]. /// /// [1]: crate::BuiltIn::PrimitiveIndex const PRIMITIVE_INDEX = 1 << 2; - /// Support for non-uniform indexing of sampled textures and storage buffer arrays. - const SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING = 1 << 3; - /// Support for non-uniform indexing of storage texture arrays. - const STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING = 1 << 4; - /// Support for non-uniform indexing of uniform buffer arrays. - const UNIFORM_BUFFER_ARRAY_NON_UNIFORM_INDEXING = 1 << 5; - /// Support for non-uniform indexing of samplers. - const SAMPLER_NON_UNIFORM_INDEXING = 1 << 6; + /// Support for binding arrays of sampled textures and samplers. + const TEXTURE_AND_SAMPLER_BINDING_ARRAY = 1 << 3; + /// Support for binding arrays of uniform buffers. + const BUFFER_BINDING_ARRAY = 1 << 4; + /// Support for binding arrays of storage textures. + const STORAGE_TEXTURE_BINDING_ARRAY = 1 << 5; + /// Support for binding arrays of storage buffers. + const STORAGE_BUFFER_BINDING_ARRAY = 1 << 6; /// Support for [`BuiltIn::ClipDistance`]. /// /// [`BuiltIn::ClipDistance`]: crate::BuiltIn::ClipDistance @@ -191,7 +191,15 @@ bitflags::bitflags! { /// Support for task shaders, mesh shaders, and per-primitive fragment inputs const MESH_SHADER = 1 << 30; /// Support for mesh shaders which output points. - const MESH_SHADER_POINT_TOPOLOGY = 1 << 30; + const MESH_SHADER_POINT_TOPOLOGY = 1 << 31; + /// Support for non-uniform indexing of binding arrays of sampled textures and samplers. + const TEXTURE_AND_SAMPLER_BINDING_ARRAY_NON_UNIFORM_INDEXING = 1 << 32; + /// Support for non-uniform indexing of binding arrays of uniform buffers. + const BUFFER_BINDING_ARRAY_NON_UNIFORM_INDEXING = 1 << 33; + /// Support for non-uniform indexing of binding arrays of storage textures. + const STORAGE_TEXTURE_BINDING_ARRAY_NON_UNIFORM_INDEXING = 1 << 34; + /// Support for non-uniform indexing of binding arrays of storage buffers. + const STORAGE_BUFFER_BINDING_ARRAY_NON_UNIFORM_INDEXING = 1 << 35; } } @@ -208,6 +216,9 @@ impl Capabilities { // NOTE: `SHADER_FLOAT16_IN_FLOAT32` _does not_ require the `f16` extension Self::SHADER_FLOAT16 => Some(Ext::F16), Self::CLIP_DISTANCE => Some(Ext::ClipDistances), + Self::MESH_SHADER => Some(Ext::WgpuMeshShader), + Self::RAY_QUERY => Some(Ext::WgpuRayQuery), + Self::RAY_HIT_VERTEX_POSITION => Some(Ext::WgpuRayQueryVertexReturn), _ => None, } } @@ -286,6 +297,7 @@ bitflags::bitflags! { const COMPUTE = 0x4; const MESH = 0x8; const TASK = 0x10; + const COMPUTE_LIKE = Self::COMPUTE.bits() | Self::TASK.bits() | Self::MESH.bits(); } } @@ -537,7 +549,7 @@ impl Validator { stages |= ShaderStages::VERTEX; } if capabilities.contains(Capabilities::SUBGROUP) { - stages |= ShaderStages::FRAGMENT | ShaderStages::COMPUTE; + stages |= ShaderStages::FRAGMENT | ShaderStages::COMPUTE_LIKE; } stages }; @@ -561,11 +573,13 @@ impl Validator { } } + // TODO(https://github.com/gfx-rs/wgpu/issues/8207): Consider removing this pub fn subgroup_stages(&mut self, stages: ShaderStages) -> &mut Self { self.subgroup_stages = stages; self } + // TODO(https://github.com/gfx-rs/wgpu/issues/8207): Consider removing this pub fn subgroup_operations(&mut self, operations: SubgroupOperationSet) -> &mut Self { self.subgroup_operations = operations; self diff --git a/third_party/rust/naga/src/valid/type.rs b/third_party/rust/naga/src/valid/type.rs @@ -173,14 +173,14 @@ pub enum WidthError { #[derive(Clone, Debug, thiserror::Error)] #[cfg_attr(test, derive(PartialEq))] -pub enum PushConstantError { - #[error("The scalar type {0:?} is not supported in push constants")] +pub enum ImmediateError { + #[error("The scalar type {0:?} is not supported in immediates")] InvalidScalar(crate::Scalar), } // Only makes sense if `flags.contains(HOST_SHAREABLE)` type LayoutCompatibility = Result<Alignment, (Handle<crate::Type>, Disalignment)>; -type PushConstantCompatibility = Result<(), PushConstantError>; +type ImmediateCompatibility = Result<(), ImmediateError>; fn check_member_layout( accum: &mut LayoutCompatibility, @@ -223,7 +223,7 @@ const fn ptr_space_argument_flag(space: crate::AddressSpace) -> TypeFlags { As::Uniform | As::Storage { .. } | As::Handle - | As::PushConstant + | As::Immediate | As::WorkGroup | As::TaskPayload => TypeFlags::empty(), } @@ -234,7 +234,7 @@ pub(super) struct TypeInfo { pub flags: TypeFlags, pub uniform_layout: LayoutCompatibility, pub storage_layout: LayoutCompatibility, - pub push_constant_compatibility: PushConstantCompatibility, + pub immediates_compatibility: ImmediateCompatibility, } impl TypeInfo { @@ -243,7 +243,7 @@ impl TypeInfo { flags: TypeFlags::empty(), uniform_layout: Ok(Alignment::ONE), storage_layout: Ok(Alignment::ONE), - push_constant_compatibility: Ok(()), + immediates_compatibility: Ok(()), } } @@ -252,7 +252,7 @@ impl TypeInfo { flags, uniform_layout: Ok(alignment), storage_layout: Ok(alignment), - push_constant_compatibility: Ok(()), + immediates_compatibility: Ok(()), } } } @@ -271,15 +271,15 @@ impl super::Validator { /// If `scalar` is not a width allowed by the selected [`Capabilities`], /// return an error explaining why. /// - /// If `scalar` is allowed, return a [`PushConstantCompatibility`] result - /// that says whether `scalar` is allowed specifically in push constants. + /// If `scalar` is allowed, return a [`ImmediateCompatibility`] result + /// that says whether `scalar` is allowed specifically in immediates. /// /// [`Capabilities`]: crate::valid::Capabilities pub(super) const fn check_width( &self, scalar: crate::Scalar, - ) -> Result<PushConstantCompatibility, WidthError> { - let mut push_constant_compatibility = Ok(()); + ) -> Result<ImmediateCompatibility, WidthError> { + let mut immediates_compatibility = Ok(()); let good = match scalar.kind { crate::ScalarKind::Bool => scalar.width == crate::BOOL_WIDTH, crate::ScalarKind::Float => match scalar.width { @@ -300,7 +300,7 @@ impl super::Validator { }); } - push_constant_compatibility = Err(PushConstantError::InvalidScalar(scalar)); + immediates_compatibility = Err(ImmediateError::InvalidScalar(scalar)); true } @@ -337,7 +337,7 @@ impl super::Validator { } }; if good { - Ok(push_constant_compatibility) + Ok(immediates_compatibility) } else { Err(WidthError::Invalid(scalar.kind, scalar.width)) } @@ -357,7 +357,7 @@ impl super::Validator { use crate::TypeInner as Ti; Ok(match gctx.types[handle].inner { Ti::Scalar(scalar) => { - let push_constant_compatibility = self.check_width(scalar)?; + let immediates_compatibility = self.check_width(scalar)?; let shareable = if scalar.kind.is_numeric() { TypeFlags::IO_SHAREABLE | TypeFlags::HOST_SHAREABLE } else { @@ -373,11 +373,11 @@ impl super::Validator { | shareable, Alignment::from_width(scalar.width), ); - type_info.push_constant_compatibility = push_constant_compatibility; + type_info.immediates_compatibility = immediates_compatibility; type_info } Ti::Vector { size, scalar } => { - let push_constant_compatibility = self.check_width(scalar)?; + let immediates_compatibility = self.check_width(scalar)?; let shareable = if scalar.kind.is_numeric() { TypeFlags::IO_SHAREABLE | TypeFlags::HOST_SHAREABLE } else { @@ -393,7 +393,7 @@ impl super::Validator { | shareable, Alignment::from(size) * Alignment::from_width(scalar.width), ); - type_info.push_constant_compatibility = push_constant_compatibility; + type_info.immediates_compatibility = immediates_compatibility; type_info } Ti::Matrix { @@ -404,7 +404,7 @@ impl super::Validator { if scalar.kind != crate::ScalarKind::Float { return Err(TypeError::MatrixElementNotFloat); } - let push_constant_compatibility = self.check_width(scalar)?; + let immediates_compatibility = self.check_width(scalar)?; let mut type_info = TypeInfo::new( TypeFlags::DATA | TypeFlags::SIZED @@ -415,7 +415,7 @@ impl super::Validator { | TypeFlags::CREATION_RESOLVED, Alignment::from(rows) * Alignment::from_width(scalar.width), ); - type_info.push_constant_compatibility = push_constant_compatibility; + type_info.immediates_compatibility = immediates_compatibility; type_info } Ti::Atomic(scalar) => { @@ -597,7 +597,7 @@ impl super::Validator { flags: base_info.flags & type_info_mask, uniform_layout, storage_layout, - push_constant_compatibility: base_info.push_constant_compatibility.clone(), + immediates_compatibility: base_info.immediates_compatibility.clone(), } } Ti::Struct { ref members, span } => { @@ -678,9 +678,8 @@ impl super::Validator { base_info.storage_layout, handle, ); - if base_info.push_constant_compatibility.is_err() { - ti.push_constant_compatibility = - base_info.push_constant_compatibility.clone(); + if base_info.immediates_compatibility.is_err() { + ti.immediates_compatibility = base_info.immediates_compatibility.clone(); } // Validate rule: If a structure member itself has a structure type S, @@ -803,6 +802,7 @@ impl super::Validator { if base_info.flags.contains(TypeFlags::DATA) { // Currently Naga only supports binding arrays of structs for non-handle types. + // `validate_global_var` relies on ray queries (which are `DATA`) being rejected here match gctx.types[base].inner { crate::TypeInner::Struct { .. } => {} _ => return Err(TypeError::BindingArrayBaseTypeNotStruct(base)), @@ -816,7 +816,8 @@ impl super::Validator { } ) { // Binding arrays of external textures are not yet supported. - // https://github.com/gfx-rs/wgpu/issues/8027 + // See <https://github.com/gfx-rs/wgpu/issues/8027>. Note that + // `validate_global_var` relies on this error being raised here. return Err(TypeError::BindingArrayBaseExternalTextures); } diff --git a/third_party/rust/ron/.cargo-checksum.json b/third_party/rust/ron/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"CHANGELOG.md":"b55480cd4e5e655b1b2c84471b223cc79ee7a9f203e8d015cfb32833e261c395","Cargo.lock":"25764fddd9c2a62e43eaeb22243c1f4ece9eb841f6e2a0744baae618be58aba9","Cargo.toml":"3b0c3781178e687681626725f18768fa1d9c2ad951340c6799d363102210e44d","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"fa0598d520497731445a5bc89a2b3d47896d95568bd2be255b7bc43d771b9994","README.md":"70649b85004fc155375a09cfa425646eb861891b36c09729b0aefdb7b1180b06","clippy.toml":"873fc9b7842395d9c61c2265ea4ad391c1de35bb25fd94347f72b4c7fc528329","docs/extensions.md":"b01d8c32d6adf1ece115957bc63400873aad6106a0f5b80e107e57bada605e5b","docs/grammar.md":"3c1f8423e197d234782724f5bf2cde5d6ed03e77ce63ddda7241717846739f81","examples/base64.rs":"c03740931bba10240db25edd928b1e7167b16da3664cb8314f3dc3cd9bc663cf","examples/decode.rs":"b8e2db901f0a25070daa0294794da7c95f9a7d61934e9b5b1cbcb131ca70a7d7","examples/decode_file.rs":"92920fc94e22c2aa83f6864e68e16932425655139d21ed3bdca4e5b4e86428e1","examples/encode.rs":"cfded2357414e2ea68580bf8f2333a62c1ae9d9fa70775c56a23dfe158e297be","examples/encode_file.rs":"b055eed27e7f9d8903bbd43e9d559c97578534cf3888635768aac587743c8508","examples/example.ron":"aebd2baff31a62c3aed1ba568b7a7c1165b197d526a1e2c9eb7adb211de8292e","examples/file_read_write_vec.rs":"d1afa1a6ae4d3bcdb8d9ad3cde235979122ff0a3eaafd92db2b36bebb441d410","examples/transcode.rs":"7a1e281684243b263d28f90f2836d6098239ae8ab2a0cb9d97ee0b7521ba960e","rustfmt.toml":"10f292ebd4c21ef67c36b6edb79eed7fada8f021c527948dfbeb3a54a5591e59","src/de/id.rs":"ed5be2ef39ad3490dcdebf99bd692299a20dbc66f2a2b67b0db1eb6dab5fd68b","src/de/mod.rs":"a5d8f44ed99b1070548227c343dba6bce850a9584bcc42d5f46480380be3d26b","src/de/tag.rs":"0513808804d16ea74f49d937089dd8ae6f74aa0c30f445dd03c7e6db41abce9a","src/de/tests.rs":"fc8a8c168c3b7d510bb9687c88d74f742ce5354b9fb562e5c963efd76b998c5c","src/de/value.rs":"0188558422aa1ec7bb6a36317535073ecb2ce5f23c64e704284d94f3ddea8c4d","src/error.rs":"e9f2d532d158a1293546193602e173dc36715f1086841895bc3dbbd5fd370301","src/extensions.rs":"e37b7658ab1fb7adbb09e5c6be4a44f28ee2dae6bc90f3adb66ee68283b768ef","src/lib.rs":"e536f55f1a73d92fdaa6614b32e1093b0246d7044c176542b110cc8f83307c57","src/options.rs":"99e9d8bba41c5cc74fb741d8db69acd677b649a708661b34ba3467719dd7d419","src/parse.rs":"2e1efbad94a47a18214274863f657b13e144da2a6c138709ddb25ed406db15ad","src/ser/mod.rs":"6dbc0a2423850e5b13322e6345439ae9a1bb03eaf0afd09285bd4cff68f58959","src/ser/path_meta.rs":"d101300d127bb76289b5a9dfc6c674c5af08f27a4ad5088f42f71bf20410a2c0","src/ser/raw.rs":"00a29fc03d3025f0bafed90fdecb216b63ef39ac8ca81b2080a70cc49f6d3d9f","src/ser/tests.rs":"2d28f31b837d370c46af4f2f41230fed73a52ee36021005b9469844cf1b4616f","src/ser/value.rs":"476f186322ba027219b0d01b5a1bcdaa33ccbeeb24ab062a477e6128816a6177","src/util/mod.rs":"f00f9b2861a57f70e9e8ada0abb21577aeb113c65ad27471ce8ebc5e91568003","src/util/span_substring.rs":"66f39c69d2af3c8624fb4f09ef027d353d8ce906ace409334cbd8929fecde1f5","src/value/map.rs":"f89f07492ab3971198bf93b82b0f9acd65a1fdd636aa5467f50242018cd8dfed","src/value/mod.rs":"a1b6607b4f1a9f6d0ae23db2e9849e0517f855d6bc119e6d773ea787c0655f0e","src/value/number.rs":"7b269a26bd66be1552d3b2d656379acb02861adf8b78154b08af93ea5b9ceb70","src/value/raw.rs":"959a6d74a802474b9c87f40557c737280d8e1a50e5dacdf725d4dbba0e501839","tests/115_minimal_flattening.rs":"724a4d981d55dd19e462166b562b5e8659dc637f4f51bcb56ca53049fb4b2c98","tests/117_untagged_tuple_variant.rs":"dc69fb057595cc72b379994e76217315761484a455c7ba45aceb445af9c36403","tests/123_enum_representation.rs":"84346679c262c5f1092d195e2e4dff73fc4779cda7b58bd4242f80168c08a0e7","tests/129_indexmap.rs":"a55ed888407dce347035893afa6a5e60d3e2f92672287c93cf5e21bb19218a40","tests/147_empty_sets_serialisation.rs":"102daa5648f4aaa461d3dc79a9124ff57c92d6c361c2aa20a3f966a0c0908823","tests/152_bitflags.rs":"e93d135e4da6c591f697be7d3b4c116d9151ab52111fe39fda94926f384ad563","tests/203_error_positions.rs":"15c90a298c9766dd8f4239a8aeefe7e0c246f66f2ebfee36c292fff938405214","tests/207_adjacently_tagged_enum.rs":"f2ebdf4268eeb6be95fd249c10930e4ea8f4fd4e3452d1ae8799334db2a3b96e","tests/217_nested_untagged_enums.rs":"91f7f0d344b256c707091a8985a1a15e799796ed757901077e3fe5a230c4ecae","tests/238_array.rs":"d84f1664c09658a322b2bc13eb3ad9cd0a789ad42d1fa66d88147760d4f9dfa9","tests/240_array_pretty.rs":"7cedc29d8bdd9bf40941f3e28a8f1200e72c5211d5e52376e5d5be9076286e36","tests/250_variant_newtypes.rs":"1733d19faba349318c8b5f5179b96252b770b1c1da77d2b08798266d4eeae8a0","tests/254_typetag.rs":"fecad2bd7c8681d900bf2b48159edebe88941a523a8ad3b6bc2ba831cfede771","tests/256_comma_error.rs":"2ca38100def63fe33c0b6e099640c90ddfc334a2df9902bcc4482b803976d691","tests/289_enumerate_arrays.rs":"0e1e9bd624b756714c6d79b79c388972c4b443c331d08098d585a85cba519bb5","tests/301_struct_name_mismatch.rs":"e0b4592367cdd3cd39936364b7cd3dacb526af3b1bfa63fb8e0275d23e38b371","tests/307_stack_overflow.rs":"b8affab09acdb6594242e7142e2fc9f6cd0f3bf36337129dc42b11f8d72060f5","tests/321_unicode_ident.rs":"b32560400d21a633734b366c743a13d972afc12c1d73552485f93a397d8b349a","tests/322_escape_idents.rs":"4f8912aeb24655a378049919ffc8b270519849abcb34ef9d7c34af5be1ec72f3","tests/337_value_float_roundtrip.rs":"e1d2b0ab6ded31fae940b1a937166c385711e68c1f9c716aead7abbeaf17ea01","tests/357_untagged_enum_roundtrip.rs":"193e1804f79bb20b93a9a80203fa64952d811bda6676d808947ac33ec27c2a90","tests/359_deserialize_seed.rs":"f69be820dbee676e6ba64f3e0878ca6e4401e3f6258d0ab525545e376931b1d0","tests/367_implicit_some.rs":"0c4ebdb13872079954b83ea1a89e0d1fb4714b406fd9dcac976754534e9e36d5","tests/370_float_parsing.rs":"3a376c0ea391367618a4dbab987e86d6db43b8f679d1e5d8878e4529bb5c7ab0","tests/393_serde_errors.rs":"5dbedc00337bc14d4b1d0fd3fbf576cdc5038aacb5cc81346e6ecfcefdeef101","tests/401_raw_identifier.rs":"fd08b742afc3106aa412d78b3c010895b6b57d1c95b55f7cf74afcc05c4d32f2","tests/407_raw_value.rs":"00739f879946de24fa5be03c9ff29569bcef5e1415e362ccfce2f8c9f3bd877d","tests/410_trailing_comma.rs":"f40b4195266a7e79f9580f4a848a5fb8d9291aaa50ebdf4820bd12b86d5a960e","tests/423_de_borrowed_identifier.rs":"1dfeba0ec2930289cd85b7ac138ba24d2206f0d5e41422ad2617b5e57590db7f","tests/425_escape_strings.rs":"d30a63591d5f517b33ac3aaac927a10e1f22d673083497847d0d08b243d2e6b5","tests/436_untagged_bytes.rs":"88fe49032243fd62b5b004c62b369cef25f0d38c4fc2679899f6f0a70b5ff303","tests/438_rusty_byte_strings.rs":"77d3f23d04bd7fef75690bee7af66379bc40b3c2ba1d1575e5287d741f7dbb1c","tests/447_compact_maps_structs.rs":"cff3aa89e123e8c8098e49e4b1c4f2725f94c8eb1d2ed2abd7cff46695e1df29","tests/449_tagged_enum.rs":"f6fa229948912123947dad39c8f22a62c06aff949eb4eb7913d1e5c3c4043a6c","tests/462_bytes.rs":"60696488e6c8163ef9eae2b4e439bbfa53b7eb978aff0f282cf0e0eb6dbd4450","tests/465_implicit_some_stack.rs":"b5956d5cca3e96959c899d6eb9811591291f36da3af47920de87248c12e834e0","tests/465_no_comment_char_value.rs":"bf1b7aae4b4c5e982511e13cddc1fd2eca9c52173d747ce8f829c3e9d99ba55e","tests/465_r_name_value.rs":"17e87a47b5a22d0f068698d557bd46e5d64c3083b1906c8a0fede56529a06d19","tests/465_ser_backslash_string.rs":"481c0f07dc9826d7b11711e49da4430847f105918a5925a22aa6396b81ecf9be","tests/465_unwrap_some_newtype_variant_value.rs":"7cf408c90ae816108bfd8eb09464de9e071c64f1d6eb88e87767789e77764ea3","tests/465_validate_ser_identifiers.rs":"84d407d71666312538a6ccd3b350ad025e6199a65c48364705dfd5a2ad16930a","tests/481_number_underscores_suffixes.rs":"2cd8e30381afcad494e93d38e9ae7d330d41b84c143ffb38cf270909116eaead","tests/492_enum_in_untagged_enum.rs":"af6d009025ffb5a7e5818617deb86306fa1fe371737f7dca592d9951f2c55a5e","tests/502_known_bugs.rs":"7788b9c6ce0f3ed5379746800ef6232b3c2502e734e4be1b844aac7b186f51ca","tests/508_value_adjacently_tagged_bug.rs":"d32b2f280d73d2939d5b1f836cab30a076cdddb71343ce8df6d2847f2e86080c","tests/511_deserialize_any_map_string_key.rs":"5beff2bbae16796d0c37d6d16ee3d654d8ebd3b0a2f911b4cb2872f72f21ed33","tests/522_explicit_struct_names.rs":"c1405f6e31157d042e7cb4ea61c15127bc15b5c785b3ca7121b0ec1f8ef6e24c","tests/526_flatten.rs":"96ad9a3934a83266b662c06cb6b71425999d8ff9751bed9250dd8a10def2f3b7","tests/530_untagged_union.rs":"13623c4dc2deb98bc05537daa0a557568314e98bba86c4059d60b0fc2db10354","tests/544_path_meta.rs":"3ad38d27ac802e2edc2268093503940a0ecb40d54d96cc91d49376b0e1807832","tests/564_exhaustive_number.rs":"e953b0c88121c06319c993d89dddb4dd116e547448208d4b8e36126609cc6c12","tests/big_struct.rs":"9c7b41233ae7d0c4fec0727aabd4cea1cd95844c85c0beb5e688b3c79eb43c14","tests/borrowed_str.rs":"24390ee6076ce3896b7cf1b2e202cb3a705c61129a4f62f6c39a2412ca68405f","tests/comments.rs":"243193276f60b0f74d3629156ecb812c0bdd995c0e1ee32666eedfebd8e53f4e","tests/depth_limit.rs":"817c0a61576c3133a66bb8c705675a125290f0ccfb92dba8a8aa46a141ad30ce","tests/escape.rs":"f153a99127d26584ce330a0a0148ee7cb59feff7968011d3705409e1c1a7d8bd","tests/extensions.rs":"4058b5da64c3f9591026790e044d19cf30c744a30cd3e9af79acd70f76ec0d40","tests/floats.rs":"367a22cca7d3a3ce6bdffc30df8712aae348ad29a1adbe9a47bc98e0a51b614d","tests/large_number.rs":"1f823b826e086f35329849d5bd9aac87266933a07988ef380348b406dea32835","tests/min_max.rs":"4529513a4cf1e5c25f7697ba258fdbae258617cf8f3374080843aef048c2bde3","tests/non_identifier_identifier.rs":"75f19fe1225855e8edb87b93d2b97ac21b1e24ab9efd133248b15cb3a76fbd9d","tests/non_string_tag.rs":"6b8d4a8adde053533d3a90afdcc0a00ee27cf6023c60916d411d9c97a1dbb0c8","tests/numbers.rs":"1856a7094235145d06673f817af0dc27645543458bb4d543efa9c546379fdd4a","tests/options.rs":"071faa0f23fdf51d8ae13a84d9d93750574a35570a2d1372243d01a3f29f45ca","tests/preserve_sequence.rs":"cda4aa098e579cf68837101b8e6b12f5b26e11272cc17d260fa232379c5008bf","tests/preserve_sequence_ex1.ron":"47bdf8da637b6e99701b5a4b8c100b4c9d45440c357aef8d368a406a05394ea9","tests/preserve_sequence_ex2.ron":"9ba759300324f8978469ce616f20eb0cfc134f0a8a1afff884fff5315b32e0d0","tests/roundtrip.rs":"4af0accc1129850537f2e04fcf9c9c1d4ecf25fdd05e79179de8fb7f8596a910","tests/struct_integers.rs":"005ff6238fdd953b822fa0ad87c013370da646bd63f880a476a2d9bbe3da7b57","tests/to_string_pretty.rs":"96b3aaba638f90b66a16693714139ef57ecd8daefd8f87b771ae2bf37b15e985","tests/unicode.rs":"98ca3dc19187908989cccca33c07d76e1fd316039453d65ff9bd19249dbb7a25","tests/value.rs":"1fdc1dec740a9ea9d55b44fe904c1fb060170e0fadd05ecac5ffae167a8b3d48"},"package":"db09040cc89e461f1a265139777a2bde7f8d8c67c4936f700c63ce3e2904d468"} -\ No newline at end of file +{"files":{"CHANGELOG.md":"eea87178d0585168c8ead7b4c587c752eb341086bcec8d55625260ade1a31809","Cargo.lock":"2663c4b21f8627416ff9f5fba0ecdc2a96551cc875e5acd2333cc9cb126f5836","Cargo.toml":"a4e5623ca7fa784644b4084bfdeb775cd3c2d241b033fc5cd4124ef4608a3226","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"fa0598d520497731445a5bc89a2b3d47896d95568bd2be255b7bc43d771b9994","README.md":"0363ef800fd27c222feb278439f9efb068e76d322a76ea52dc48343938e87616","clippy.toml":"873fc9b7842395d9c61c2265ea4ad391c1de35bb25fd94347f72b4c7fc528329","docs/extensions.md":"b01d8c32d6adf1ece115957bc63400873aad6106a0f5b80e107e57bada605e5b","docs/grammar.md":"3c1f8423e197d234782724f5bf2cde5d6ed03e77ce63ddda7241717846739f81","examples/base64.rs":"c03740931bba10240db25edd928b1e7167b16da3664cb8314f3dc3cd9bc663cf","examples/decode.rs":"b8e2db901f0a25070daa0294794da7c95f9a7d61934e9b5b1cbcb131ca70a7d7","examples/decode_file.rs":"92920fc94e22c2aa83f6864e68e16932425655139d21ed3bdca4e5b4e86428e1","examples/encode.rs":"cfded2357414e2ea68580bf8f2333a62c1ae9d9fa70775c56a23dfe158e297be","examples/encode_file.rs":"b055eed27e7f9d8903bbd43e9d559c97578534cf3888635768aac587743c8508","examples/example.ron":"aebd2baff31a62c3aed1ba568b7a7c1165b197d526a1e2c9eb7adb211de8292e","examples/file_read_write_vec.rs":"d1afa1a6ae4d3bcdb8d9ad3cde235979122ff0a3eaafd92db2b36bebb441d410","examples/transcode.rs":"7a1e281684243b263d28f90f2836d6098239ae8ab2a0cb9d97ee0b7521ba960e","rustfmt.toml":"10f292ebd4c21ef67c36b6edb79eed7fada8f021c527948dfbeb3a54a5591e59","src/de/id.rs":"ed5be2ef39ad3490dcdebf99bd692299a20dbc66f2a2b67b0db1eb6dab5fd68b","src/de/mod.rs":"367a39b318703928a760bda6ac587d6abca5a320aa9a60783256c9dfa7c1aaf4","src/de/tag.rs":"0513808804d16ea74f49d937089dd8ae6f74aa0c30f445dd03c7e6db41abce9a","src/de/tests.rs":"fc8a8c168c3b7d510bb9687c88d74f742ce5354b9fb562e5c963efd76b998c5c","src/de/value.rs":"0188558422aa1ec7bb6a36317535073ecb2ce5f23c64e704284d94f3ddea8c4d","src/error.rs":"1eab853fad54ce9bb065971a1c0afb1935386e2f285b6784210722e605d3cc40","src/extensions.rs":"e37b7658ab1fb7adbb09e5c6be4a44f28ee2dae6bc90f3adb66ee68283b768ef","src/lib.rs":"e95953d9c1a3db029bf7bb78e694c807afc8e67c479bad2e90c4d0eeaaefb734","src/options.rs":"99e9d8bba41c5cc74fb741d8db69acd677b649a708661b34ba3467719dd7d419","src/parse.rs":"abb9ba17459b1f1424d54b4831a92de95d05cdc30eeb67030f1ac9b96966a1c5","src/ser/mod.rs":"b115b59a7e847631b7a9f0ef9076abd0ceff43d6fa650217d70c087cf56cb097","src/ser/path_meta.rs":"d101300d127bb76289b5a9dfc6c674c5af08f27a4ad5088f42f71bf20410a2c0","src/ser/raw.rs":"00a29fc03d3025f0bafed90fdecb216b63ef39ac8ca81b2080a70cc49f6d3d9f","src/ser/tests.rs":"2d28f31b837d370c46af4f2f41230fed73a52ee36021005b9469844cf1b4616f","src/ser/value.rs":"476f186322ba027219b0d01b5a1bcdaa33ccbeeb24ab062a477e6128816a6177","src/util/mod.rs":"f00f9b2861a57f70e9e8ada0abb21577aeb113c65ad27471ce8ebc5e91568003","src/util/span_substring.rs":"66f39c69d2af3c8624fb4f09ef027d353d8ce906ace409334cbd8929fecde1f5","src/value/map.rs":"f89f07492ab3971198bf93b82b0f9acd65a1fdd636aa5467f50242018cd8dfed","src/value/mod.rs":"a1b6607b4f1a9f6d0ae23db2e9849e0517f855d6bc119e6d773ea787c0655f0e","src/value/number.rs":"7b269a26bd66be1552d3b2d656379acb02861adf8b78154b08af93ea5b9ceb70","src/value/raw.rs":"959a6d74a802474b9c87f40557c737280d8e1a50e5dacdf725d4dbba0e501839","tests/115_minimal_flattening.rs":"724a4d981d55dd19e462166b562b5e8659dc637f4f51bcb56ca53049fb4b2c98","tests/117_untagged_tuple_variant.rs":"dc69fb057595cc72b379994e76217315761484a455c7ba45aceb445af9c36403","tests/123_enum_representation.rs":"84346679c262c5f1092d195e2e4dff73fc4779cda7b58bd4242f80168c08a0e7","tests/129_indexmap.rs":"a55ed888407dce347035893afa6a5e60d3e2f92672287c93cf5e21bb19218a40","tests/147_empty_sets_serialisation.rs":"102daa5648f4aaa461d3dc79a9124ff57c92d6c361c2aa20a3f966a0c0908823","tests/152_bitflags.rs":"e93d135e4da6c591f697be7d3b4c116d9151ab52111fe39fda94926f384ad563","tests/203_error_positions.rs":"15c90a298c9766dd8f4239a8aeefe7e0c246f66f2ebfee36c292fff938405214","tests/207_adjacently_tagged_enum.rs":"f2ebdf4268eeb6be95fd249c10930e4ea8f4fd4e3452d1ae8799334db2a3b96e","tests/217_nested_untagged_enums.rs":"91f7f0d344b256c707091a8985a1a15e799796ed757901077e3fe5a230c4ecae","tests/238_array.rs":"d84f1664c09658a322b2bc13eb3ad9cd0a789ad42d1fa66d88147760d4f9dfa9","tests/240_array_pretty.rs":"7cedc29d8bdd9bf40941f3e28a8f1200e72c5211d5e52376e5d5be9076286e36","tests/250_variant_newtypes.rs":"1733d19faba349318c8b5f5179b96252b770b1c1da77d2b08798266d4eeae8a0","tests/254_typetag.rs":"fecad2bd7c8681d900bf2b48159edebe88941a523a8ad3b6bc2ba831cfede771","tests/256_comma_error.rs":"2ca38100def63fe33c0b6e099640c90ddfc334a2df9902bcc4482b803976d691","tests/289_enumerate_arrays.rs":"0e1e9bd624b756714c6d79b79c388972c4b443c331d08098d585a85cba519bb5","tests/301_struct_name_mismatch.rs":"e0b4592367cdd3cd39936364b7cd3dacb526af3b1bfa63fb8e0275d23e38b371","tests/307_stack_overflow.rs":"b8affab09acdb6594242e7142e2fc9f6cd0f3bf36337129dc42b11f8d72060f5","tests/321_unicode_ident.rs":"b32560400d21a633734b366c743a13d972afc12c1d73552485f93a397d8b349a","tests/322_escape_idents.rs":"4f8912aeb24655a378049919ffc8b270519849abcb34ef9d7c34af5be1ec72f3","tests/337_value_float_roundtrip.rs":"e1d2b0ab6ded31fae940b1a937166c385711e68c1f9c716aead7abbeaf17ea01","tests/357_untagged_enum_roundtrip.rs":"193e1804f79bb20b93a9a80203fa64952d811bda6676d808947ac33ec27c2a90","tests/359_deserialize_seed.rs":"f69be820dbee676e6ba64f3e0878ca6e4401e3f6258d0ab525545e376931b1d0","tests/367_implicit_some.rs":"0c4ebdb13872079954b83ea1a89e0d1fb4714b406fd9dcac976754534e9e36d5","tests/370_float_parsing.rs":"3a376c0ea391367618a4dbab987e86d6db43b8f679d1e5d8878e4529bb5c7ab0","tests/393_serde_errors.rs":"5dbedc00337bc14d4b1d0fd3fbf576cdc5038aacb5cc81346e6ecfcefdeef101","tests/401_raw_identifier.rs":"fd08b742afc3106aa412d78b3c010895b6b57d1c95b55f7cf74afcc05c4d32f2","tests/407_raw_value.rs":"00739f879946de24fa5be03c9ff29569bcef5e1415e362ccfce2f8c9f3bd877d","tests/410_trailing_comma.rs":"f40b4195266a7e79f9580f4a848a5fb8d9291aaa50ebdf4820bd12b86d5a960e","tests/423_de_borrowed_identifier.rs":"1dfeba0ec2930289cd85b7ac138ba24d2206f0d5e41422ad2617b5e57590db7f","tests/425_escape_strings.rs":"d30a63591d5f517b33ac3aaac927a10e1f22d673083497847d0d08b243d2e6b5","tests/436_untagged_bytes.rs":"88fe49032243fd62b5b004c62b369cef25f0d38c4fc2679899f6f0a70b5ff303","tests/438_rusty_byte_strings.rs":"c1efda612e9a53c64478abc7f08e0427431643be898985733027ec27c399e71b","tests/447_compact_maps_structs.rs":"cff3aa89e123e8c8098e49e4b1c4f2725f94c8eb1d2ed2abd7cff46695e1df29","tests/449_tagged_enum.rs":"4757c375cfded9228ae1d670962bf07b46c7f8ab79b927e6675729aa22073d19","tests/462_bytes.rs":"60696488e6c8163ef9eae2b4e439bbfa53b7eb978aff0f282cf0e0eb6dbd4450","tests/465_implicit_some_stack.rs":"b5956d5cca3e96959c899d6eb9811591291f36da3af47920de87248c12e834e0","tests/465_no_comment_char_value.rs":"bf1b7aae4b4c5e982511e13cddc1fd2eca9c52173d747ce8f829c3e9d99ba55e","tests/465_r_name_value.rs":"17e87a47b5a22d0f068698d557bd46e5d64c3083b1906c8a0fede56529a06d19","tests/465_ser_backslash_string.rs":"481c0f07dc9826d7b11711e49da4430847f105918a5925a22aa6396b81ecf9be","tests/465_unwrap_some_newtype_variant_value.rs":"7cf408c90ae816108bfd8eb09464de9e071c64f1d6eb88e87767789e77764ea3","tests/465_validate_ser_identifiers.rs":"84d407d71666312538a6ccd3b350ad025e6199a65c48364705dfd5a2ad16930a","tests/481_number_underscores_suffixes.rs":"2cd8e30381afcad494e93d38e9ae7d330d41b84c143ffb38cf270909116eaead","tests/492_enum_in_untagged_enum.rs":"af6d009025ffb5a7e5818617deb86306fa1fe371737f7dca592d9951f2c55a5e","tests/502_known_bugs.rs":"7788b9c6ce0f3ed5379746800ef6232b3c2502e734e4be1b844aac7b186f51ca","tests/508_value_adjacently_tagged_bug.rs":"d32b2f280d73d2939d5b1f836cab30a076cdddb71343ce8df6d2847f2e86080c","tests/511_deserialize_any_map_string_key.rs":"5beff2bbae16796d0c37d6d16ee3d654d8ebd3b0a2f911b4cb2872f72f21ed33","tests/522_explicit_struct_names.rs":"c1405f6e31157d042e7cb4ea61c15127bc15b5c785b3ca7121b0ec1f8ef6e24c","tests/526_flatten.rs":"96ad9a3934a83266b662c06cb6b71425999d8ff9751bed9250dd8a10def2f3b7","tests/530_untagged_union.rs":"13623c4dc2deb98bc05537daa0a557568314e98bba86c4059d60b0fc2db10354","tests/544_path_meta.rs":"3ad38d27ac802e2edc2268093503940a0ecb40d54d96cc91d49376b0e1807832","tests/564_exhaustive_number.rs":"e953b0c88121c06319c993d89dddb4dd116e547448208d4b8e36126609cc6c12","tests/581_serde_core_content.rs":"773d15331de6dce9d94311f20b70af500202757e69a9c2f1207572e34abf66ab","tests/big_struct.rs":"9c7b41233ae7d0c4fec0727aabd4cea1cd95844c85c0beb5e688b3c79eb43c14","tests/borrowed_str.rs":"24390ee6076ce3896b7cf1b2e202cb3a705c61129a4f62f6c39a2412ca68405f","tests/comments.rs":"243193276f60b0f74d3629156ecb812c0bdd995c0e1ee32666eedfebd8e53f4e","tests/depth_limit.rs":"817c0a61576c3133a66bb8c705675a125290f0ccfb92dba8a8aa46a141ad30ce","tests/escape.rs":"f153a99127d26584ce330a0a0148ee7cb59feff7968011d3705409e1c1a7d8bd","tests/extensions.rs":"4058b5da64c3f9591026790e044d19cf30c744a30cd3e9af79acd70f76ec0d40","tests/floats.rs":"367a22cca7d3a3ce6bdffc30df8712aae348ad29a1adbe9a47bc98e0a51b614d","tests/large_number.rs":"1f823b826e086f35329849d5bd9aac87266933a07988ef380348b406dea32835","tests/min_max.rs":"4529513a4cf1e5c25f7697ba258fdbae258617cf8f3374080843aef048c2bde3","tests/non_identifier_identifier.rs":"75f19fe1225855e8edb87b93d2b97ac21b1e24ab9efd133248b15cb3a76fbd9d","tests/numbers.rs":"1856a7094235145d06673f817af0dc27645543458bb4d543efa9c546379fdd4a","tests/options.rs":"071faa0f23fdf51d8ae13a84d9d93750574a35570a2d1372243d01a3f29f45ca","tests/preserve_sequence.rs":"cda4aa098e579cf68837101b8e6b12f5b26e11272cc17d260fa232379c5008bf","tests/preserve_sequence_ex1.ron":"47bdf8da637b6e99701b5a4b8c100b4c9d45440c357aef8d368a406a05394ea9","tests/preserve_sequence_ex2.ron":"9ba759300324f8978469ce616f20eb0cfc134f0a8a1afff884fff5315b32e0d0","tests/roundtrip.rs":"4af0accc1129850537f2e04fcf9c9c1d4ecf25fdd05e79179de8fb7f8596a910","tests/struct_integers.rs":"005ff6238fdd953b822fa0ad87c013370da646bd63f880a476a2d9bbe3da7b57","tests/to_string_pretty.rs":"96b3aaba638f90b66a16693714139ef57ecd8daefd8f87b771ae2bf37b15e985","tests/unicode.rs":"98ca3dc19187908989cccca33c07d76e1fd316039453d65ff9bd19249dbb7a25","tests/value.rs":"1fdc1dec740a9ea9d55b44fe904c1fb060170e0fadd05ecac5ffae167a8b3d48"},"package":"fd490c5b18261893f14449cbd28cb9c0b637aebf161cd77900bfdedaff21ec32"} +\ No newline at end of file diff --git a/third_party/rust/ron/CHANGELOG.md b/third_party/rust/ron/CHANGELOG.md @@ -6,14 +6,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## [0.12.0] - 2025-11-12 + +### API Changes + +- Breaking: Removed the `ron::error::Error::Base64Error` variant. ([#566](https://github.com/ron-rs/ron/pull/566)) +- Added `into_inner()` method to `ron::ser::Serializer` to retrieve the inner writer. ([#588](https://github.com/ron-rs/ron/pull/588)) +- Removed the `base64` dependency. ([#566](https://github.com/ron-rs/ron/pull/566)) + +### Format Changes + +- **Format-Breaking:** Remote base64-encoded byte strings deserialisation, replaced by Rusty byte strings in v0.9.0 ([#566](https://github.com/ron-rs/ron/pull/566)) + +### Bug Fixes + +- Fixed untagged enum deserialisation for serde >= 1.0.220 with better serde content detection ([#582](https://github.com/ron-rs/ron/pull/582)) + ## [0.11.0] - 2025-08-27 ### API Changes - Breaking: `SpannedError` now stores the full error span in span: Span { start: Position, end: Position }`, to facilitate, e.g., language server highlighting of syntax errors. - - Breaking: Added `no_std` support via a new `std` feature (enabled by default). With default features disabled, you must enable the `std` feature to access `de::from_reader`, and the `std::io` operations on `Options`, such as `from_reader`, `from_reader_seed`, `to_io_writer`, and `to_io_writer_pretty` ([#567](https://github.com/ron-rs/ron/pull/567)) - - Breaking: Fixed (again) `ron::value::Number` to ensure it is non-exhaustive, to avoid breaking `match`es when feature unification enables more of its variants than expected ([#568](https://github.com/ron-rs/ron/pull/568)) ### Examples diff --git a/third_party/rust/ron/Cargo.lock b/third_party/rust/ron/Cargo.lock @@ -10,9 +10,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "2.9.3" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" +checksum = "c70beb79cbb5ce9c4f8e20849978f34225931f665bb49efa6982875a4d5facb3" dependencies = [ "serde", ] @@ -34,19 +34,20 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "erased-serde" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" +checksum = "259d404d09818dec19332e31d94558aeb442fea04c817006456c24b5460bbd4b" dependencies = [ "serde", + "serde_core", "typeid", ] [[package]] name = "hashbrown" -version = "0.15.5" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "heck" @@ -56,9 +57,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "indexmap" -version = "2.11.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown", @@ -76,21 +77,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - -[[package]] -name = "memchr" -version = "2.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "once_cell" -version = "1.21.3" +version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "option_set" @@ -104,35 +99,37 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] [[package]] name = "ron" -version = "0.11.0" +version = "0.12.0" dependencies = [ "base64", "bitflags", "bytes", "indexmap", + "once_cell", "option_set", "serde", "serde_bytes", "serde_derive", "serde_json", + "typeid", "typetag", "unicode-ident", "unicode-segmentation", @@ -152,27 +149,37 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ + "serde_core", "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.17" +version = "0.11.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96" +checksum = "364fec0df39c49a083c9a8a18a23a6bcfd9af130fe9fe321d18520a0d113e09e" dependencies = [ "serde", ] [[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -181,12 +188,11 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.143" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" +checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779" dependencies = [ "itoa", - "memchr", "ryu", "serde", ] @@ -234,9 +240,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" [[package]] name = "unicode-segmentation" diff --git a/third_party/rust/ron/Cargo.toml b/third_party/rust/ron/Cargo.toml @@ -13,19 +13,13 @@ edition = "2021" rust-version = "1.64.0" name = "ron" -version = "0.11.0" +version = "0.12.0" authors = [ "Christopher Durham <cad97@cad97.com>", "Dzmitry Malyshau <kvarkus@gmail.com>", "Thomas Schaller <torkleyy@gmail.com>", "Juniper Tyree <juniper.tyree@helsinki.fi>", ] -build = false -autolib = false -autobins = false -autoexamples = false -autotests = false -autobenches = false description = "Rusty Object Notation" homepage = "https://github.com/ron-rs/ron" documentation = "https://docs.rs/ron/" @@ -46,339 +40,30 @@ features = [ ] rustdoc-args = ["--generate-link-to-definition"] -[features] -default = ["std"] -indexmap = [ - "std", - "dep:indexmap", -] -integer128 = [] -internal-span-substring-test = ["unicode-segmentation"] -std = ["serde/std"] - -[lib] -name = "ron" -path = "src/lib.rs" - [[example]] name = "base64" -path = "examples/base64.rs" -required-features = [] - -[[example]] -name = "decode" -path = "examples/decode.rs" required-features = [] [[example]] name = "decode_file" -path = "examples/decode_file.rs" required-features = ["std"] [[example]] -name = "encode" -path = "examples/encode.rs" +name = "decode" required-features = [] [[example]] name = "encode_file" -path = "examples/encode_file.rs" required-features = ["std"] [[example]] -name = "file_read_write_vec" -path = "examples/file_read_write_vec.rs" +name = "encode" +required-features = [] [[example]] name = "transcode" -path = "examples/transcode.rs" required-features = [] -[[test]] -name = "115_minimal_flattening" -path = "tests/115_minimal_flattening.rs" - -[[test]] -name = "117_untagged_tuple_variant" -path = "tests/117_untagged_tuple_variant.rs" - -[[test]] -name = "123_enum_representation" -path = "tests/123_enum_representation.rs" - -[[test]] -name = "129_indexmap" -path = "tests/129_indexmap.rs" - -[[test]] -name = "147_empty_sets_serialisation" -path = "tests/147_empty_sets_serialisation.rs" - -[[test]] -name = "152_bitflags" -path = "tests/152_bitflags.rs" - -[[test]] -name = "203_error_positions" -path = "tests/203_error_positions.rs" - -[[test]] -name = "207_adjacently_tagged_enum" -path = "tests/207_adjacently_tagged_enum.rs" - -[[test]] -name = "217_nested_untagged_enums" -path = "tests/217_nested_untagged_enums.rs" - -[[test]] -name = "238_array" -path = "tests/238_array.rs" - -[[test]] -name = "240_array_pretty" -path = "tests/240_array_pretty.rs" - -[[test]] -name = "250_variant_newtypes" -path = "tests/250_variant_newtypes.rs" - -[[test]] -name = "254_typetag" -path = "tests/254_typetag.rs" - -[[test]] -name = "256_comma_error" -path = "tests/256_comma_error.rs" - -[[test]] -name = "289_enumerate_arrays" -path = "tests/289_enumerate_arrays.rs" - -[[test]] -name = "301_struct_name_mismatch" -path = "tests/301_struct_name_mismatch.rs" - -[[test]] -name = "307_stack_overflow" -path = "tests/307_stack_overflow.rs" - -[[test]] -name = "321_unicode_ident" -path = "tests/321_unicode_ident.rs" - -[[test]] -name = "322_escape_idents" -path = "tests/322_escape_idents.rs" - -[[test]] -name = "337_value_float_roundtrip" -path = "tests/337_value_float_roundtrip.rs" - -[[test]] -name = "357_untagged_enum_roundtrip" -path = "tests/357_untagged_enum_roundtrip.rs" - -[[test]] -name = "359_deserialize_seed" -path = "tests/359_deserialize_seed.rs" - -[[test]] -name = "367_implicit_some" -path = "tests/367_implicit_some.rs" - -[[test]] -name = "370_float_parsing" -path = "tests/370_float_parsing.rs" - -[[test]] -name = "393_serde_errors" -path = "tests/393_serde_errors.rs" - -[[test]] -name = "401_raw_identifier" -path = "tests/401_raw_identifier.rs" - -[[test]] -name = "407_raw_value" -path = "tests/407_raw_value.rs" - -[[test]] -name = "410_trailing_comma" -path = "tests/410_trailing_comma.rs" - -[[test]] -name = "423_de_borrowed_identifier" -path = "tests/423_de_borrowed_identifier.rs" - -[[test]] -name = "425_escape_strings" -path = "tests/425_escape_strings.rs" - -[[test]] -name = "436_untagged_bytes" -path = "tests/436_untagged_bytes.rs" - -[[test]] -name = "438_rusty_byte_strings" -path = "tests/438_rusty_byte_strings.rs" - -[[test]] -name = "447_compact_maps_structs" -path = "tests/447_compact_maps_structs.rs" - -[[test]] -name = "449_tagged_enum" -path = "tests/449_tagged_enum.rs" - -[[test]] -name = "462_bytes" -path = "tests/462_bytes.rs" - -[[test]] -name = "465_implicit_some_stack" -path = "tests/465_implicit_some_stack.rs" - -[[test]] -name = "465_no_comment_char_value" -path = "tests/465_no_comment_char_value.rs" - -[[test]] -name = "465_r_name_value" -path = "tests/465_r_name_value.rs" - -[[test]] -name = "465_ser_backslash_string" -path = "tests/465_ser_backslash_string.rs" - -[[test]] -name = "465_unwrap_some_newtype_variant_value" -path = "tests/465_unwrap_some_newtype_variant_value.rs" - -[[test]] -name = "465_validate_ser_identifiers" -path = "tests/465_validate_ser_identifiers.rs" - -[[test]] -name = "481_number_underscores_suffixes" -path = "tests/481_number_underscores_suffixes.rs" - -[[test]] -name = "492_enum_in_untagged_enum" -path = "tests/492_enum_in_untagged_enum.rs" - -[[test]] -name = "502_known_bugs" -path = "tests/502_known_bugs.rs" - -[[test]] -name = "508_value_adjacently_tagged_bug" -path = "tests/508_value_adjacently_tagged_bug.rs" - -[[test]] -name = "511_deserialize_any_map_string_key" -path = "tests/511_deserialize_any_map_string_key.rs" - -[[test]] -name = "522_explicit_struct_names" -path = "tests/522_explicit_struct_names.rs" - -[[test]] -name = "526_flatten" -path = "tests/526_flatten.rs" - -[[test]] -name = "530_untagged_union" -path = "tests/530_untagged_union.rs" - -[[test]] -name = "544_path_meta" -path = "tests/544_path_meta.rs" - -[[test]] -name = "564_exhaustive_number" -path = "tests/564_exhaustive_number.rs" - -[[test]] -name = "big_struct" -path = "tests/big_struct.rs" - -[[test]] -name = "borrowed_str" -path = "tests/borrowed_str.rs" - -[[test]] -name = "comments" -path = "tests/comments.rs" - -[[test]] -name = "depth_limit" -path = "tests/depth_limit.rs" - -[[test]] -name = "escape" -path = "tests/escape.rs" - -[[test]] -name = "extensions" -path = "tests/extensions.rs" - -[[test]] -name = "floats" -path = "tests/floats.rs" - -[[test]] -name = "large_number" -path = "tests/large_number.rs" - -[[test]] -name = "min_max" -path = "tests/min_max.rs" - -[[test]] -name = "non_identifier_identifier" -path = "tests/non_identifier_identifier.rs" - -[[test]] -name = "non_string_tag" -path = "tests/non_string_tag.rs" - -[[test]] -name = "numbers" -path = "tests/numbers.rs" - -[[test]] -name = "options" -path = "tests/options.rs" - -[[test]] -name = "preserve_sequence" -path = "tests/preserve_sequence.rs" - -[[test]] -name = "roundtrip" -path = "tests/roundtrip.rs" - -[[test]] -name = "struct_integers" -path = "tests/struct_integers.rs" - -[[test]] -name = "to_string_pretty" -path = "tests/to_string_pretty.rs" - -[[test]] -name = "unicode" -path = "tests/unicode.rs" - -[[test]] -name = "value" -path = "tests/value.rs" - -[dependencies.base64] -version = "0.22" -features = ["alloc"] -default-features = false - [dependencies.bitflags] version = "2.1" features = ["serde"] @@ -390,6 +75,14 @@ features = ["serde"] optional = true default-features = false +[dependencies.once_cell] +version = "1.20" +features = [ + "alloc", + "race", +] +default-features = false + [dependencies.serde] version = "1.0.181" features = ["alloc"] @@ -399,6 +92,10 @@ default-features = false version = "1.0.181" default-features = false +[dependencies.typeid] +version = "1.0.1" +default-features = false + [dependencies.unicode-ident] version = "1.0" default-features = false @@ -408,6 +105,11 @@ version = "1.12.0" optional = true default-features = false +[dev-dependencies.base64] +version = "0.22" +features = ["std"] +default-features = false + [dev-dependencies.bytes] version = "1.3" features = ["serde"] @@ -438,3 +140,13 @@ default-features = false [dev-dependencies.typetag] version = "0.2" default-features = false + +[features] +default = ["std"] +indexmap = [ + "std", + "dep:indexmap", +] +integer128 = [] +internal-span-substring-test = ["unicode-segmentation"] +std = ["serde/std"] diff --git a/third_party/rust/ron/README.md b/third_party/rust/ron/README.md @@ -163,16 +163,15 @@ fn main() { ## Tooling -| Editor | Plugin | -| ------------ | ----------------------------------------------------------- | -| IntelliJ | [intellij-ron](https://github.com/ron-rs/intellij-ron) | -| VS Code | [a5huynh/vscode-ron](https://github.com/a5huynh/vscode-ron) | -| Sublime Text | [RON](https://packagecontrol.io/packages/RON) | -| Atom | [language-ron](https://atom.io/packages/language-ron) | -| Vim | [ron-rs/ron.vim](https://github.com/ron-rs/ron.vim) | -| EMACS | [emacs-ron] | - -[emacs-ron]: https://chiselapp.com/user/Hutzdog/repository/ron-mode/home +| Editor | Plugin | +| -------------- | ------------------------------------------------------------------------ | +| IntelliJ | [intellij-ron](https://github.com/ron-rs/intellij-ron) | +| VS Code | [a5huynh/vscode-ron](https://github.com/a5huynh/vscode-ron) | +| Sublime Text | [RON](https://packagecontrol.io/packages/RON) | +| Atom | [language-ron](https://atom.io/packages/language-ron) | +| Vim | [ron-rs/ron.vim](https://github.com/ron-rs/ron.vim) | +| EMACS | [emacs-ron](https://chiselapp.com/user/Hutzdog/repository/ron-mode/home) | +| Multiple / LSP | [ron-lsp](https://github.com/jasonjmcghee/ron-lsp) | ## Limitations @@ -225,7 +224,7 @@ Please file a [new issue](https://github.com/ron-rs/ron/issues/new) if you come While RON guarantees roundtrips like Rust -> RON -> Rust for Rust types using non-`deserialize_any`-based implementations, RON does not yet make any guarantees about roundtrips through `ron::Value`. For instance, even when RON -> Rust works, RON -> `ron::Value` -> Rust, or RON -> `ron::Value` -> RON -> Rust may not work. We plan on improving `ron::Value` in an upcoming version of RON, though this work is partially blocked on [serde#1183](https://github.com/serde-rs/serde/issues/1183). -[^serde-enum-hack]: Deserialising an internally, adjacently, or un-tagged enum requires detecting `serde`'s internal `serde::__private::de::content::Content` content type so that RON can describe the deserialised data structure in serde's internal JSON-like format. This detection only works for the automatically-derived [`Deserialize`](https://docs.rs/serde/latest/serde/de/trait.Deserialize.html) impls on enums. See [#451](https://github.com/ron-rs/ron/pull/451) for more details. +[^serde-enum-hack]: Deserialising an internally, adjacently, or un-tagged enum requires detecting `serde`'s internal default buffer/content type so that RON can describe the deserialised data structure in serde's internal JSON-like format. A more robust implementation is blocked on [serde#1183](https://github.com/serde-rs/serde/issues/1183). [^serde-flatten-hack]: Deserialising a flattened struct from a map requires that the struct's [`Visitor::expecting`](https://docs.rs/serde/latest/serde/de/trait.Visitor.html#tymethod.expecting) implementation formats a string starting with `"struct "`. This is the case for automatically-derived [`Deserialize`](https://docs.rs/serde/latest/serde/de/trait.Deserialize.html) impls on structs. See [#455](https://github.com/ron-rs/ron/pull/455) for more details. diff --git a/third_party/rust/ron/src/de/mod.rs b/third_party/rust/ron/src/de/mod.rs @@ -175,7 +175,7 @@ impl<'de> Deserializer<'de> { V: Visitor<'de>, { // HACK: switch to JSON enum semantics for JSON content - // Robust impl blocked on https://github.com/serde-rs/serde/pull/2420 + // Robust impl blocked on https://github.com/serde-rs/serde/issues/1183 let is_serde_content = is_serde_content::<V::Value>() || is_serde_tag_or_content::<V::Value>(); @@ -566,7 +566,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { if name == crate::value::raw::RAW_VALUE_TOKEN { let src_before = self.parser.pre_ws_src(); self.parser.skip_ws()?; - let _ignored = self.deserialize_ignored_any(serde::de::IgnoredAny)?; + let _ignored = self.deserialize_ignored_any(de::IgnoredAny)?; self.parser.skip_ws()?; let src_after = self.parser.src(); @@ -1028,7 +1028,7 @@ impl<'de, 'a> de::MapAccess<'de> for SerdeEnumContent<'a, 'de> { { self.ident .take() - .map(|ident| seed.deserialize(serde::de::value::StrDeserializer::new(ident))) + .map(|ident| seed.deserialize(de::value::StrDeserializer::new(ident))) .transpose() } @@ -1047,18 +1047,140 @@ impl<'de, 'a> de::MapAccess<'de> for SerdeEnumContent<'a, 'de> { } } -// ensure that these are the same as in the 449_tagged_enum test fn is_serde_content<T>() -> bool { - matches!( - core::any::type_name::<T>(), - "serde::__private::de::content::Content" | "serde::__private::de::content::Content<'_>" - ) + #[derive(serde_derive::Deserialize)] + enum A {} + type B = A; + + #[derive(serde_derive::Deserialize)] + #[serde(untagged)] + enum UntaggedEnum { + A(A), + B(B), + } + + struct TypeIdDeserializer; + + impl<'de> de::Deserializer<'de> for TypeIdDeserializer { + type Error = TypeIdError; + + fn deserialize_any<V: Visitor<'de>>(self, _visitor: V) -> Result<V::Value, Self::Error> { + Err(TypeIdError(typeid::of::<V::Value>())) + } + + serde::forward_to_deserialize_any! { + bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string + bytes byte_buf option unit unit_struct newtype_struct seq tuple + tuple_struct map struct enum identifier ignored_any + } + } + + #[derive(Debug)] + struct TypeIdError(core::any::TypeId); + + impl core::fmt::Display for TypeIdError { + fn fmt(&self, _fmt: &mut core::fmt::Formatter) -> core::fmt::Result { + Ok(()) + } + } + + impl de::Error for TypeIdError { + #[allow(clippy::unreachable)] + fn custom<T: core::fmt::Display>(_msg: T) -> Self { + unreachable!() + } + } + + impl de::StdError for TypeIdError {} + + fn type_id_of_untagged_enum_default_buffer() -> core::any::TypeId { + static TYPE_ID: once_cell::race::OnceBox<core::any::TypeId> = + once_cell::race::OnceBox::new(); + + *TYPE_ID.get_or_init(|| match Deserialize::deserialize(TypeIdDeserializer) { + Ok(UntaggedEnum::A(void) | UntaggedEnum::B(void)) => match void {}, + Err(TypeIdError(typeid)) => alloc::boxed::Box::new(typeid), + }) + } + + typeid::of::<T>() == type_id_of_untagged_enum_default_buffer() } fn is_serde_tag_or_content<T>() -> bool { - matches!( - core::any::type_name::<T>(), - "serde::__private::de::content::TagOrContent" - | "serde::__private::de::content::TagOrContent<'_>" - ) + #[derive(serde_derive::Deserialize)] + enum A {} + + #[derive(serde_derive::Deserialize)] + #[serde(tag = "tag")] + enum InternallyTaggedEnum { + A { a: A }, + } + + struct TypeIdDeserializer; + + impl<'de> de::Deserializer<'de> for TypeIdDeserializer { + type Error = TypeIdError; + + fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error> + where + V: Visitor<'de>, + { + visitor.visit_map(self) + } + + serde::forward_to_deserialize_any! { + bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string + bytes byte_buf option unit unit_struct newtype_struct seq tuple + tuple_struct map struct enum identifier ignored_any + } + } + + impl<'de> de::MapAccess<'de> for TypeIdDeserializer { + type Error = TypeIdError; + + fn next_key_seed<K: DeserializeSeed<'de>>( + &mut self, + _seed: K, + ) -> Result<Option<K::Value>, Self::Error> { + Err(TypeIdError(typeid::of::<K::Value>())) + } + + #[allow(clippy::unreachable)] + fn next_value_seed<V: DeserializeSeed<'de>>( + &mut self, + _seed: V, + ) -> Result<V::Value, Self::Error> { + unreachable!() + } + } + + #[derive(Debug)] + struct TypeIdError(core::any::TypeId); + + impl core::fmt::Display for TypeIdError { + fn fmt(&self, _fmt: &mut core::fmt::Formatter) -> core::fmt::Result { + Ok(()) + } + } + + impl de::Error for TypeIdError { + #[allow(clippy::unreachable)] + fn custom<T: core::fmt::Display>(_msg: T) -> Self { + unreachable!() + } + } + + impl de::StdError for TypeIdError {} + + fn type_id_of_internally_tagged_enum_default_tag_or_buffer() -> core::any::TypeId { + static TYPE_ID: once_cell::race::OnceBox<core::any::TypeId> = + once_cell::race::OnceBox::new(); + + *TYPE_ID.get_or_init(|| match Deserialize::deserialize(TypeIdDeserializer) { + Ok(InternallyTaggedEnum::A { a: void }) => match void {}, + Err(TypeIdError(typeid)) => alloc::boxed::Box::new(typeid), + }) + } + + typeid::of::<T>() == type_id_of_internally_tagged_enum_default_tag_or_buffer() } diff --git a/third_party/rust/ron/src/error.rs b/third_party/rust/ron/src/error.rs @@ -33,11 +33,6 @@ pub enum Error { Fmt, Io(String), Message(String), - #[deprecated( - since = "0.9.0", - note = "ambiguous base64 byte strings are replaced by strongly typed Rusty b\"byte strings\"" - )] - Base64Error(base64::DecodeError), Eof, ExpectedArray, ExpectedArrayEnd, @@ -131,8 +126,6 @@ impl fmt::Display for Error { match *self { Error::Fmt => f.write_str("Formatting RON failed"), Error::Io(ref s) | Error::Message(ref s) => f.write_str(s), - #[allow(deprecated)] - Error::Base64Error(ref e) => write!(f, "Invalid base64: {}", e), Error::Eof => f.write_str("Unexpected end of RON"), Error::ExpectedArray => f.write_str("Expected opening `[`"), Error::ExpectedArrayEnd => f.write_str("Expected closing `]`"), @@ -530,11 +523,6 @@ mod tests { ); check_error_message(&<Error as SerError>::custom("my-ser-error"), "my-ser-error"); check_error_message(&<Error as DeError>::custom("my-de-error"), "my-de-error"); - #[allow(deprecated)] - check_error_message( - &Error::Base64Error(base64::DecodeError::InvalidPadding), - "Invalid base64: Invalid padding", - ); check_error_message(&Error::Eof, "Unexpected end of RON"); check_error_message(&Error::ExpectedArray, "Expected opening `[`"); check_error_message(&Error::ExpectedArrayEnd, "Expected closing `]`"); diff --git a/third_party/rust/ron/src/lib.rs b/third_party/rust/ron/src/lib.rs @@ -16,7 +16,7 @@ #![warn(clippy::std_instead_of_alloc)] #![warn(clippy::std_instead_of_core)] #![doc = include_str!("../README.md")] -#![doc(html_root_url = "https://docs.rs/ron/0.11.0")] +#![doc(html_root_url = "https://docs.rs/ron/0.12.0")] #![no_std] #[cfg(feature = "std")] diff --git a/third_party/rust/ron/src/parse.rs b/third_party/rust/ron/src/parse.rs @@ -1039,35 +1039,23 @@ impl<'a> Parser<'a> { } } + // FIXME @juntyr: remove in v0.13, since only byte_string_no_base64 will + // be used if self.consume_char('"') { let base64_str = self.escaped_string()?; let base64_result = ParsedByteStr::try_from_base64(&base64_str); - if cfg!(not(test)) { - // FIXME @juntyr: remove in v0.12 - #[allow(deprecated)] - base64_result.map_err(Error::Base64Error) - } else { - match base64_result { - // FIXME @juntyr: enable in v0.12 - Ok(byte_str) => Err(expected_byte_string_found_base64(&base64_str, &byte_str)), - Err(_) => Err(Error::ExpectedByteString), - } + match base64_result { + Some(byte_str) => Err(expected_byte_string_found_base64(&base64_str, &byte_str)), + None => Err(Error::ExpectedByteString), } } else if self.consume_char('r') { let base64_str = self.raw_string()?; let base64_result = ParsedByteStr::try_from_base64(&base64_str); - if cfg!(not(test)) { - // FIXME @juntyr: remove in v0.12 - #[allow(deprecated)] - base64_result.map_err(Error::Base64Error) - } else { - match base64_result { - // FIXME @juntyr: enable in v0.12 - Ok(byte_str) => Err(expected_byte_string_found_base64(&base64_str, &byte_str)), - Err(_) => Err(Error::ExpectedByteString), - } + match base64_result { + Some(byte_str) => Err(expected_byte_string_found_base64(&base64_str, &byte_str)), + None => Err(Error::ExpectedByteString), } } else { self.byte_string_no_base64() @@ -1641,14 +1629,75 @@ impl<'a> ParsedStr<'a> { } impl<'a> ParsedByteStr<'a> { - pub fn try_from_base64(str: &ParsedStr<'a>) -> Result<Self, base64::DecodeError> { + pub fn try_from_base64(str: &ParsedStr<'a>) -> Option<Self> { + // Adapted from MIT licensed Jenin Sutradhar's base 64 decoder + // https://github.com/JeninSutradhar/base64-Rust-Encoder-Decoder/blob/ee1fb08cbb78024ec8cf5e786815acb239169f02/src/lib.rs#L84-L128 + fn try_decode_base64(str: &str) -> Option<Vec<u8>> { + const CHARSET: &[u8; 64] = + b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + const PADDING: u8 = b'='; + + // fast reject for missing padding + if (str.len() % 4) != 0 { + return None; + } + + let bstr_no_padding = str.trim_end_matches(char::from(PADDING)).as_bytes(); + + // fast reject for excessive padding + if (str.len() - bstr_no_padding.len()) > 2 { + return None; + } + + // fast reject for extraneous bytes after padding + if bstr_no_padding.contains(&PADDING) { + return None; + } + + // fast reject for non-ASCII + if !str.is_ascii() { + return None; + } + + let mut collected_bits = 0_u8; + let mut byte_buffer = 0_u16; + let mut bytes = bstr_no_padding.iter().copied(); + let mut binary = Vec::new(); + + 'decodeloop: loop { + while collected_bits < 8 { + if let Some(nextbyte) = bytes.next() { + #[allow(clippy::cast_possible_truncation)] + if let Some(idx) = CHARSET.iter().position(|&x| x == nextbyte) { + byte_buffer |= ((idx & 0b0011_1111) as u16) << (10 - collected_bits); + collected_bits += 6; + } else { + return None; + } + } else { + break 'decodeloop; + } + } + + binary.push(((0b1111_1111_0000_0000 & byte_buffer) >> 8) as u8); + byte_buffer &= 0b0000_0000_1111_1111; + byte_buffer <<= 8; + collected_bits -= 8; + } + + if usize::from(collected_bits) != ((str.len() - bstr_no_padding.len()) * 2) { + return None; + } + + Some(binary) + } + let base64_str = match str { ParsedStr::Allocated(string) => string.as_str(), ParsedStr::Slice(str) => str, }; - base64::engine::Engine::decode(&base64::engine::general_purpose::STANDARD, base64_str) - .map(ParsedByteStr::Allocated) + try_decode_base64(base64_str).map(ParsedByteStr::Allocated) } } @@ -1786,7 +1835,7 @@ mod tests { } #[test] - fn v0_10_base64_deprecation_error() { + fn base64_deprecation_error() { let err = crate::from_str::<bytes::Bytes>("\"SGVsbG8gcm9uIQ==\"").unwrap_err(); assert_eq!( @@ -1810,7 +1859,10 @@ mod tests { assert_eq!( crate::from_str::<bytes::Bytes>("\"invalid=\"").unwrap_err(), SpannedError { - code: Error::ExpectedByteString, + code: Error::InvalidValueForType { + expected: String::from("the Rusty byte string b\"\\x8a{\\xda\\x96\\'\""), + found: String::from("the ambiguous base64 string \"invalid=\"") + }, span: Span { start: Position { line: 1, col: 2 }, end: Position { line: 1, col: 11 }, @@ -1821,12 +1873,26 @@ mod tests { assert_eq!( crate::from_str::<bytes::Bytes>("r\"invalid=\"").unwrap_err(), SpannedError { - code: Error::ExpectedByteString, + code: Error::InvalidValueForType { + expected: String::from("the Rusty byte string b\"\\x8a{\\xda\\x96\\'\""), + found: String::from("the ambiguous base64 string \"invalid=\"") + }, span: Span { start: Position { line: 1, col: 3 }, end: Position { line: 1, col: 12 }, } } ); + + assert_eq!( + crate::from_str::<bytes::Bytes>("r\"invalid\"").unwrap_err(), + SpannedError { + code: Error::ExpectedByteString, + span: Span { + start: Position { line: 1, col: 3 }, + end: Position { line: 1, col: 11 }, + } + } + ); } } diff --git a/third_party/rust/ron/src/ser/mod.rs b/third_party/rust/ron/src/ser/mod.rs @@ -446,6 +446,12 @@ impl<W: fmt::Write> Serializer<W> { }) } + /// Unwrap the `Writer` from the `Serializer`. + #[inline] + pub fn into_inner(self) -> W { + self.output + } + fn separate_tuple_members(&self) -> bool { self.pretty .as_ref() diff --git a/third_party/rust/ron/tests/438_rusty_byte_strings.rs b/third_party/rust/ron/tests/438_rusty_byte_strings.rs @@ -12,86 +12,6 @@ struct BytesStruct { } #[test] -fn v_9_deprecated_base64_bytes_support() { - #![allow(deprecated)] - - // Requires padding of the base64 data - assert_eq!( - Ok(BytesStruct { - small: vec![1, 2], - large: vec![1, 2, 3, 4] - }), - ron::from_str("BytesStruct( small:[1, 2], large:\"AQIDBA==\" )"), - ); - - // Requires no padding of the base64 data - assert_eq!( - Ok(BytesStruct { - small: vec![1, 2], - large: vec![1, 2, 3, 4, 5, 6] - }), - ron::from_str("BytesStruct( small:[1, 2], large:r\"AQIDBAUG\" )"), - ); - - // Parsing an escaped byte string is also possible - assert_eq!( - Ok(BytesStruct { - small: vec![1, 2], - large: vec![1, 2, 3, 4, 5, 6] - }), - ron::from_str("BytesStruct( small:[1, 2], large:\"\\x41Q\\x49DBA\\x55G\" )"), - ); - - // Invalid base64 - assert_eq!( - Err(SpannedError { - code: Error::Base64Error(base64::DecodeError::InvalidByte(0, b'_')), - span: Span { - start: Position { line: 1, col: 35 }, - end: Position { line: 1, col: 40 } - } - }), - ron::from_str::<BytesStruct>("BytesStruct( small:[1, 2], large:\"_+!!\" )"), - ); - - // Invalid last base64 symbol - assert_eq!( - Err(SpannedError { - code: Error::Base64Error(base64::DecodeError::InvalidLastSymbol(1, b'x')), - span: Span { - start: Position { line: 1, col: 35 }, - end: Position { line: 1, col: 40 } - } - }), - ron::from_str::<BytesStruct>("BytesStruct( small:[1, 2], large:\"/x==\" )"), - ); - - // Missing padding - assert_eq!( - Err(SpannedError { - code: Error::Base64Error(base64::DecodeError::InvalidPadding), - span: Span { - start: Position { line: 1, col: 35 }, - end: Position { line: 1, col: 42 } - } - }), - ron::from_str::<BytesStruct>("BytesStruct( small:[1, 2], large:\"AQIDBA\" )"), - ); - - // Too much padding - assert_eq!( - Err(SpannedError { - code: Error::Base64Error(base64::DecodeError::InvalidByte(6, b'=')), - span: Span { - start: Position { line: 1, col: 35 }, - end: Position { line: 1, col: 45 } - } - }), - ron::from_str::<BytesStruct>("BytesStruct( small:[1, 2], large:\"AQIDBA===\" )"), - ); -} - -#[test] fn rusty_byte_string() { assert_eq!( Ok(BytesStruct { diff --git a/third_party/rust/ron/tests/449_tagged_enum.rs b/third_party/rust/ron/tests/449_tagged_enum.rs @@ -42,114 +42,6 @@ enum OuterEnumUntagged { } #[test] -fn test_serde_content_hack() { - assert!(matches!( - std::any::type_name::<serde::__private::de::Content>(), - "serde::__private::de::content::Content" | "serde::__private::de::content::Content<'_>" - )); -} - -#[test] -fn test_serde_internally_tagged_hack() { - // ensure that these are the same as in ron::de module - fn is_serde_content<T>() -> bool { - matches!( - core::any::type_name::<T>(), - "serde::__private::de::content::Content" | "serde::__private::de::content::Content<'_>" - ) - } - - fn is_serde_tag_or_content<T>() -> bool { - matches!( - core::any::type_name::<T>(), - "serde::__private::de::content::TagOrContent" - | "serde::__private::de::content::TagOrContent<'_>" - ) - } - - struct Deserializer { - tag_key: Option<String>, - tag_value: String, - field_key: Option<String>, - field_value: i32, - } - - impl<'de> serde::Deserializer<'de> for Deserializer { - type Error = ron::Error; - - fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error> - where - V: serde::de::Visitor<'de>, - { - visitor.visit_map(self) - } - - // GRCOV_EXCL_START - serde::forward_to_deserialize_any! { - bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string - bytes byte_buf option unit unit_struct newtype_struct seq tuple - tuple_struct map struct enum identifier ignored_any - } - // GRCOV_EXCL_STOP - } - - impl<'de> serde::de::MapAccess<'de> for Deserializer { - type Error = ron::Error; - - fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Self::Error> - where - K: serde::de::DeserializeSeed<'de>, - { - assert!(is_serde_tag_or_content::<K::Value>()); - - if let Some(tag_key) = self.tag_key.take() { - return seed - .deserialize(serde::de::value::StringDeserializer::new(tag_key)) - .map(Some); - } - - if let Some(field_key) = self.field_key.take() { - return seed - .deserialize(serde::de::value::StringDeserializer::new(field_key)) - .map(Some); - } - - Ok(None) - } - - fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Self::Error> - where - V: serde::de::DeserializeSeed<'de>, - { - if self.field_key.is_some() { - assert!(!is_serde_content::<V::Value>()); - return seed.deserialize(serde::de::value::StrDeserializer::new(&self.tag_value)); - } - - assert!(is_serde_content::<V::Value>()); - - seed.deserialize(serde::de::value::I32Deserializer::new(self.field_value)) - } - } - - #[derive(PartialEq, Debug, Deserialize)] - #[serde(tag = "tag")] - enum InternallyTagged { - A { hi: i32 }, - } - - assert_eq!( - InternallyTagged::deserialize(Deserializer { - tag_key: Some(String::from("tag")), - tag_value: String::from("A"), - field_key: Some(String::from("hi")), - field_value: 42, - }), - Ok(InternallyTagged::A { hi: 42 }) - ); -} - -#[test] fn test_enum_in_enum_roundtrip() { let outer = OuterEnum::Variant(Container { field: InnerEnum::Unit, diff --git a/third_party/rust/ron/tests/581_serde_core_content.rs b/third_party/rust/ron/tests/581_serde_core_content.rs @@ -0,0 +1,26 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(tag = "type")] +enum Message { + Request { + id: u32, + resource: String, + operation: String, + }, + Response { + id: u32, + value: String, + }, +} + +#[test] +fn internally_tagged_enum_serde_core_content_detection() { + let value = Message::Response { + id: 60069, + value: "Foobar".into(), + }; + let serialized = ron::to_string(&value).unwrap(); + let deserialized: Message = ron::from_str(&serialized).unwrap(); + assert_eq!(deserialized, value); +} diff --git a/third_party/rust/ron/tests/non_string_tag.rs b/third_party/rust/ron/tests/non_string_tag.rs @@ -1,143 +0,0 @@ -macro_rules! test_non_tag { - ($test_name:ident => $deserialize_method:ident($($deserialize_param:expr),*)) => { - #[test] - fn $test_name() { - use serde::{Deserialize, Deserializer, de::Visitor}; - - struct TagVisitor; - - impl<'de> Visitor<'de> for TagVisitor { - type Value = Tag; - - // GRCOV_EXCL_START - fn expecting(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { - fmt.write_str("an error") - } - // GRCOV_EXCL_STOP - } - - struct Tag; - - impl<'de> Deserialize<'de> for Tag { - fn deserialize<D: Deserializer<'de>>(deserializer: D) - -> Result<Self, D::Error> - { - deserializer.$deserialize_method($($deserialize_param,)* TagVisitor) - } - } - - #[derive(Debug)] // GRCOV_EXCL_LINE - struct InternallyTagged; - - impl<'de> Deserialize<'de> for InternallyTagged { - fn deserialize<D: Deserializer<'de>>(deserializer: D) - -> Result<Self, D::Error> - { - deserializer.deserialize_any(serde::__private::de::TaggedContentVisitor::< - Tag, - >::new( - "tag", - "an internally tagged value", - )).map(|_| unreachable!()) - } - } - - assert_eq!( - ron::from_str::<InternallyTagged>("(tag: err)").unwrap_err().code, - ron::Error::ExpectedString - ) - } - }; -} - -test_non_tag! { test_bool => deserialize_bool() } -test_non_tag! { test_i8 => deserialize_i8() } -test_non_tag! { test_i16 => deserialize_i16() } -test_non_tag! { test_i32 => deserialize_i32() } -test_non_tag! { test_i64 => deserialize_i64() } -#[cfg(feature = "integer128")] -test_non_tag! { test_i128 => deserialize_i128() } -test_non_tag! { test_u8 => deserialize_u8() } -test_non_tag! { test_u16 => deserialize_u16() } -test_non_tag! { test_u32 => deserialize_u32() } -test_non_tag! { test_u64 => deserialize_u64() } -#[cfg(feature = "integer128")] -test_non_tag! { test_u128 => deserialize_u128() } -test_non_tag! { test_f32 => deserialize_f32() } -test_non_tag! { test_f64 => deserialize_f64() } -test_non_tag! { test_char => deserialize_char() } -test_non_tag! { test_bytes => deserialize_bytes() } -test_non_tag! { test_byte_buf => deserialize_byte_buf() } -test_non_tag! { test_option => deserialize_option() } -test_non_tag! { test_unit => deserialize_unit() } -test_non_tag! { test_unit_struct => deserialize_unit_struct("") } -test_non_tag! { test_newtype_struct => deserialize_newtype_struct("") } -test_non_tag! { test_seq => deserialize_seq() } -test_non_tag! { test_tuple => deserialize_tuple(0) } -test_non_tag! { test_tuple_struct => deserialize_tuple_struct("", 0) } -test_non_tag! { test_map => deserialize_map() } -test_non_tag! { test_struct => deserialize_struct("", &[]) } -test_non_tag! { test_enum => deserialize_enum("", &[]) } - -macro_rules! test_tag { - ($test_name:ident => $deserialize_method:ident($($deserialize_param:expr),*)) => { - #[test] - fn $test_name() { - use serde::{Deserialize, Deserializer, de::Visitor}; - - struct TagVisitor; - - impl<'de> Visitor<'de> for TagVisitor { - type Value = Tag; - - // GRCOV_EXCL_START - fn expecting(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { - fmt.write_str("a tag") - } - // GRCOV_EXCL_STOP - - fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> { - assert_eq!(v, "tag"); - Ok(Tag) - } - } - - struct Tag; - - impl<'de> Deserialize<'de> for Tag { - fn deserialize<D: Deserializer<'de>>(deserializer: D) - -> Result<Self, D::Error> - { - deserializer.$deserialize_method($($deserialize_param,)* TagVisitor) - } - } - - #[derive(Debug)] // GRCOV_EXCL_LINE - struct InternallyTagged; - - impl<'de> Deserialize<'de> for InternallyTagged { - fn deserialize<D: Deserializer<'de>>(deserializer: D) - -> Result<Self, D::Error> - { - deserializer.deserialize_any(serde::__private::de::TaggedContentVisitor::< - Tag, - >::new( - "tag", - "an internally tagged value", - )).map(|_| InternallyTagged) - } - } - - assert!( - ron::from_str::<InternallyTagged>("(tag: \"tag\")").is_ok(), - ) - } - }; -} - -test_tag! { test_str => deserialize_string() } -test_tag! { test_string => deserialize_string() } -test_tag! { test_identifier => deserialize_identifier() } - -test_tag! { test_any => deserialize_any() } -test_tag! { test_ignored_any => deserialize_ignored_any() } diff --git a/third_party/rust/typeid/.cargo-checksum.json b/third_party/rust/typeid/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.lock":"0169e18cb453f58d0a6638dfed548168237694977503472f92f7418345b49254","Cargo.toml":"ab161da097e295a92d42ff950c37ab7d9e36db684fd3935dec283a1922adc802","LICENSE-APACHE":"62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a","LICENSE-MIT":"23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3","README.md":"51280285faf5c10009380fab5c955c764b22ca896178e2fbbb8d9f089f8634a9","build.rs":"688afbcaa398ea159c3481b26d74fde6ce3a675d48364d772557c8e91100de46","src/lib.rs":"aab636e68353710cbf242dd7dc433f8a97d7d0df895816149f5ace144bfd17f7","tests/test.rs":"6446761675d8e34c47c3a25f2d8ae129f963ba27018a28fb6c3497692329828c"},"package":"bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"} +\ No newline at end of file diff --git a/third_party/rust/typeid/Cargo.lock b/third_party/rust/typeid/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "typeid" +version = "1.0.3" diff --git a/third_party/rust/typeid/Cargo.toml b/third_party/rust/typeid/Cargo.toml @@ -0,0 +1,49 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +rust-version = "1.34" +name = "typeid" +version = "1.0.3" +authors = ["David Tolnay <dtolnay@gmail.com>"] +build = "build.rs" +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "Const TypeId and non-'static TypeId" +documentation = "https://docs.rs/typeid" +readme = "README.md" +categories = [ + "no-std", + "no-std::no-alloc", +] +license = "MIT OR Apache-2.0" +repository = "https://github.com/dtolnay/typeid" + +[package.metadata.docs.rs] +rustdoc-args = [ + "--generate-link-to-definition", + "--extern-html-root-url=core=https://doc.rust-lang.org", + "--extern-html-root-url=alloc=https://doc.rust-lang.org", + "--extern-html-root-url=std=https://doc.rust-lang.org", +] +targets = ["x86_64-unknown-linux-gnu"] + +[lib] +name = "typeid" +path = "src/lib.rs" + +[[test]] +name = "test" +path = "tests/test.rs" diff --git a/third_party/rust/typeid/LICENSE-APACHE b/third_party/rust/typeid/LICENSE-APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/third_party/rust/typeid/LICENSE-MIT b/third_party/rust/typeid/LICENSE-MIT @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/third_party/rust/typeid/README.md b/third_party/rust/typeid/README.md @@ -0,0 +1,123 @@ +Const `TypeId` and non-'static `TypeId` +======================================= + +[<img alt="github" src="https://img.shields.io/badge/github-dtolnay/typeid-8da0cb?style=for-the-badge&labelColor=555555&logo=github" height="20">](https://github.com/dtolnay/typeid) +[<img alt="crates.io" src="https://img.shields.io/crates/v/typeid.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/typeid) +[<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-typeid-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs" height="20">](https://docs.rs/typeid) +[<img alt="build status" src="https://img.shields.io/github/actions/workflow/status/dtolnay/typeid/ci.yml?branch=master&style=for-the-badge" height="20">](https://github.com/dtolnay/typeid/actions?query=branch%3Amaster) + +[`ConstTypeId`]: https://docs.rs/typeid/1/typeid/struct.ConstTypeId.html +[`typeid::of`]: https://docs.rs/typeid/1/typeid/fn.of.html + +### Const `TypeId` + +This crate provides [`ConstTypeId`], which is like [`core::any::TypeId`] but is +constructible in const in stable Rust. (The standard library's TypeId's is +nightly-only to construct in const; the tracking issue for this is +[rust#77125].) + +[`core::any::TypeId`]: https://doc.rust-lang.org/core/any/struct.TypeId.html +[rust#77125]: https://github.com/rust-lang/rust/issues/77125 + +Being able to construct `ConstTypeId` in const makes it suitable for use cases +that rely on static promotion: + +```rust +use std::fmt::{self, Debug, Display}; +use std::ptr; +use typeid::ConstTypeId; + +pub struct ObjectVTable { + type_id: ConstTypeId, + drop_in_place: unsafe fn(*mut ()), + display: unsafe fn(*const (), &mut fmt::Formatter) -> fmt::Result, + debug: unsafe fn(*const (), &mut fmt::Formatter) -> fmt::Result, +} + +impl ObjectVTable { + pub const fn new<T: Display + Debug>() -> &'static Self { + &ObjectVTable { + type_id: const { ConstTypeId::of::<T>() }, + drop_in_place: |ptr| unsafe { ptr::drop_in_place(ptr.cast::<T>()) }, + display: |ptr, f| unsafe { Display::fmt(&*ptr.cast::<T>(), f) }, + debug: |ptr, f| unsafe { Debug::fmt(&*ptr.cast::<T>(), f) }, + } + } +} +``` + +and in associated constants: + +```rust +use typeid::ConstTypeId; + +pub trait GetTypeId { + const TYPEID: ConstTypeId; +} + +impl<T: 'static> GetTypeId for T { + const TYPEID: ConstTypeId = ConstTypeId::of::<Self>(); +} +``` + +<br> + +### Non-'static `TypeId` + +This crate provides [`typeid::of`], which takes an arbitrary non-'static type +`T` and produces the `TypeId` for the type obtained by replacing all lifetimes +in `T` by `'static`, other than higher-rank lifetimes found in trait objects. + +For example if `T` is `&'b dyn for<'a> Trait<'a, 'c>`, then `typeid::of::<T>()` +produces the TypeId of `&'static dyn for<'a> Trait<'a, 'static>`. + +It should be obvious that unlike with the standard library's TypeId, +`typeid::of::<A>() == typeid::of::<B>()` does **not** mean that `A` and `B` are +the same type. However, there is a common special case where this behavior is +exactly what is needed. If: + +- `A` is an arbitrary non-'static type parameter, _and_ +- `B` is 'static, _and_ +- all types with the same id as `B` are also 'static + +then `typeid::of::<A>() == typeid::of::<B>()` guarantees that `A` and `B` are +the same type. + +```rust +use core::any::TypeId; +use core::slice; + +pub fn example<T>(slice: &[T]) { + // T is arbitrary and non-'static. + + if typeid::of::<T>() == TypeId::of::<u8>() { + // T is definitely u8 + let bytes = unsafe { slice::from_raw_parts(slice.as_ptr().cast(), slice.len()) }; + process_bytes(bytes); + } else { + for t in slice { + process(t); + } + } +} + +fn process<T>(_: &T) {/* ... */} +fn process_bytes(_: &[u8]) {/* ... */} +``` + +<br> + +#### License + +<sup> +Licensed under either of <a href="LICENSE-APACHE">Apache License, Version +2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option. +</sup> + +<br> + +<sub> +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in this crate by you, as defined in the Apache-2.0 license, shall +be dual licensed as above, without any additional terms or conditions. +</sub> diff --git a/third_party/rust/typeid/build.rs b/third_party/rust/typeid/build.rs @@ -0,0 +1,33 @@ +use std::env; +use std::process::Command; +use std::str; + +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + + let compiler = match rustc_minor_version() { + Some(compiler) => compiler, + None => return, + }; + + if compiler >= 80 { + println!("cargo:rustc-check-cfg=cfg(no_const_type_id)"); + } + + if compiler < 61 { + // Function pointer casting in const fn. + // https://blog.rust-lang.org/2022/05/19/Rust-1.61.0.html#more-capabilities-for-const-fn + println!("cargo:rustc-cfg=no_const_type_id"); + } +} + +fn rustc_minor_version() -> Option<u32> { + let rustc = env::var_os("RUSTC")?; + let output = Command::new(rustc).arg("--version").output().ok()?; + let version = str::from_utf8(&output.stdout).ok()?; + let mut pieces = version.split('.'); + if pieces.next() != Some("rustc 1") { + return None; + } + pieces.next()?.parse().ok() +} diff --git a/third_party/rust/typeid/src/lib.rs b/third_party/rust/typeid/src/lib.rs @@ -0,0 +1,222 @@ +//! [![github]](https://github.com/dtolnay/typeid) [![crates-io]](https://crates.io/crates/typeid) [![docs-rs]](https://docs.rs/typeid) +//! +//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github +//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust +//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs +//! +//! # Const `TypeId` and non-'static `TypeId` +//! +//! <br> +//! +//! #### Const `TypeId` +//! +//! This crate provides [`ConstTypeId`], which is like [`core::any::TypeId`] but +//! is constructible in const in stable Rust. (The standard library's TypeId's +//! is nightly-only to construct in const; the tracking issue for this is +//! [rust#77125].) +//! +//! [rust#77125]: https://github.com/rust-lang/rust/issues/77125 +//! +//! Being able to construct `ConstTypeId` in const makes it suitable for use +//! cases that rely on static promotion: +//! +//! ``` +//! use std::fmt::{self, Debug, Display}; +//! use std::ptr; +//! use typeid::ConstTypeId; +//! +//! pub struct ObjectVTable { +//! type_id: ConstTypeId, +//! drop_in_place: unsafe fn(*mut ()), +//! display: unsafe fn(*const (), &mut fmt::Formatter) -> fmt::Result, +//! debug: unsafe fn(*const (), &mut fmt::Formatter) -> fmt::Result, +//! } +//! +//! impl ObjectVTable { +//! pub const fn new<T: Display + Debug>() -> &'static Self { +//! &ObjectVTable { +//! type_id: const { ConstTypeId::of::<T>() }, +//! drop_in_place: |ptr| unsafe { ptr::drop_in_place(ptr.cast::<T>()) }, +//! display: |ptr, f| unsafe { Display::fmt(&*ptr.cast::<T>(), f) }, +//! debug: |ptr, f| unsafe { Debug::fmt(&*ptr.cast::<T>(), f) }, +//! } +//! } +//! } +//! ``` +//! +//! and in associated constants: +//! +//! ``` +//! use typeid::ConstTypeId; +//! +//! pub trait GetTypeId { +//! const TYPEID: ConstTypeId; +//! } +//! +//! impl<T: 'static> GetTypeId for T { +//! const TYPEID: ConstTypeId = ConstTypeId::of::<Self>(); +//! } +//! ``` +//! +//! <br> +//! +//! #### Non-'static `TypeId` +//! +//! This crate provides [`typeid::of`], which takes an arbitrary non-'static +//! type `T` and produces the `TypeId` for the type obtained by replacing all +//! lifetimes in `T` by `'static`, other than higher-rank lifetimes found in +//! trait objects. +//! +//! For example if `T` is `&'b dyn for<'a> Trait<'a, 'c>`, then +//! `typeid::of::<T>()` produces the TypeId of `&'static dyn for<'a> Trait<'a, +//! 'static>`. +//! +//! It should be obvious that unlike with the standard library's TypeId, +//! `typeid::of::<A>() == typeid::of::<B>()` does **not** mean that `A` and `B` +//! are the same type. However, there is a common special case where this +//! behavior is exactly what is needed. If: +//! +//! - `A` is an arbitrary non-'static type parameter, _and_ +//! - `B` is 'static, _and_ +//! - all types with the same id as `B` are also 'static +//! +//! then `typeid::of::<A>() == typeid::of::<B>()` guarantees that `A` and `B` +//! are the same type. +//! +//! ``` +//! use core::any::TypeId; +//! use core::slice; +//! +//! pub fn example<T>(slice: &[T]) { +//! // T is arbitrary and non-'static. +//! +//! if typeid::of::<T>() == TypeId::of::<u8>() { +//! // T is definitely u8 +//! let bytes = unsafe { slice::from_raw_parts(slice.as_ptr().cast(), slice.len()) }; +//! process_bytes(bytes); +//! } else { +//! for t in slice { +//! process(t); +//! } +//! } +//! } +//! +//! fn process<T>(_: &T) {/* ... */} +//! fn process_bytes(_: &[u8]) {/* ... */} +//! ``` + +#![no_std] +#![doc(html_root_url = "https://docs.rs/typeid/1.0.3")] +#![allow(clippy::doc_markdown, clippy::inline_always)] + +extern crate self as typeid; + +use core::any::TypeId; +#[cfg(not(no_const_type_id))] +use core::cmp::Ordering; +#[cfg(not(no_const_type_id))] +use core::fmt::{self, Debug}; +#[cfg(not(no_const_type_id))] +use core::hash::{Hash, Hasher}; +use core::marker::PhantomData; +use core::mem; + +#[cfg(not(no_const_type_id))] +#[derive(Copy, Clone)] +pub struct ConstTypeId { + type_id_fn: fn() -> TypeId, +} + +#[cfg(not(no_const_type_id))] +impl ConstTypeId { + #[must_use] + pub const fn of<T>() -> Self + where + T: ?Sized, + { + ConstTypeId { + type_id_fn: typeid::of::<T>, + } + } + + #[inline] + fn get(self) -> TypeId { + (self.type_id_fn)() + } +} + +#[cfg(not(no_const_type_id))] +impl Debug for ConstTypeId { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + Debug::fmt(&self.get(), formatter) + } +} + +#[cfg(not(no_const_type_id))] +impl PartialEq for ConstTypeId { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.get() == other.get() + } +} + +#[cfg(not(no_const_type_id))] +impl PartialEq<TypeId> for ConstTypeId { + fn eq(&self, other: &TypeId) -> bool { + self.get() == *other + } +} + +#[cfg(not(no_const_type_id))] +impl Eq for ConstTypeId {} + +#[cfg(not(no_const_type_id))] +impl PartialOrd for ConstTypeId { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { + Some(Ord::cmp(self, other)) + } +} + +#[cfg(not(no_const_type_id))] +impl Ord for ConstTypeId { + #[inline] + fn cmp(&self, other: &Self) -> Ordering { + Ord::cmp(&self.get(), &other.get()) + } +} + +#[cfg(not(no_const_type_id))] +impl Hash for ConstTypeId { + fn hash<H: Hasher>(&self, state: &mut H) { + self.get().hash(state); + } +} + +#[must_use] +#[inline(always)] +pub fn of<T>() -> TypeId +where + T: ?Sized, +{ + trait NonStaticAny { + fn get_type_id(&self) -> TypeId + where + Self: 'static; + } + + impl<T: ?Sized> NonStaticAny for PhantomData<T> { + #[inline(always)] + fn get_type_id(&self) -> TypeId + where + Self: 'static, + { + TypeId::of::<T>() + } + } + + let phantom_data = PhantomData::<T>; + NonStaticAny::get_type_id(unsafe { + mem::transmute::<&dyn NonStaticAny, &(dyn NonStaticAny + 'static)>(&phantom_data) + }) +} diff --git a/third_party/rust/typeid/tests/test.rs b/third_party/rust/typeid/tests/test.rs @@ -0,0 +1,25 @@ +#[test] +fn test_non_static_type_id() { + assert_eq!(typeid::of::<usize>(), typeid::of::<usize>()); + assert_eq!(typeid::of::<&str>(), typeid::of::<&'static str>()); + + assert_ne!(typeid::of::<u32>(), typeid::of::<[u8; 4]>()); + assert_ne!(typeid::of::<u32>(), typeid::of::<[u32; 2]>()); + + assert_ne!(typeid::of::<usize>(), typeid::of::<isize>()); + assert_ne!(typeid::of::<usize>(), typeid::of::<&usize>()); + assert_ne!(typeid::of::<&usize>(), typeid::of::<&&usize>()); + assert_ne!(typeid::of::<&usize>(), typeid::of::<&mut usize>()); + + assert_ne!(typeid::of::<fn(&str)>(), typeid::of::<fn(&'static str)>()); + + trait Trait<'a> {} + assert_ne!( + typeid::of::<dyn for<'a> Trait<'a>>(), + typeid::of::<dyn Trait<'static>>(), + ); + + struct A; + struct B; + assert_ne!(typeid::of::<A>(), typeid::of::<B>()); +} diff --git a/third_party/rust/wgpu-core-deps-apple/.cargo-checksum.json b/third_party/rust/wgpu-core-deps-apple/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"6c96bef3ae237c938710184702abb14d8ffb8b7d02a3af5d63a0a388879e7ecf","LICENSE.APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE.MIT":"dc0d97139e8205818c703741c7be7cb3b96888bd5917b8d6fc6133731e403c21","README.md":"729fdd16cb87ad318ed2bfc70593363aa324c804c825ffea7c42670dff681255","src/lib.rs":"54ef7b7d746c6b26b0ee65552cc969d69c6204d0d38b3678c40bffbfbc5a146c"},"package":null} -\ No newline at end of file +{"files":{"Cargo.toml":"f280f2d99c116cb364260ea105df6ddb10eae02778529c7b829abd04486ca00a","LICENSE.APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE.MIT":"dc0d97139e8205818c703741c7be7cb3b96888bd5917b8d6fc6133731e403c21","README.md":"729fdd16cb87ad318ed2bfc70593363aa324c804c825ffea7c42670dff681255","src/lib.rs":"54ef7b7d746c6b26b0ee65552cc969d69c6204d0d38b3678c40bffbfbc5a146c"},"package":null} +\ No newline at end of file diff --git a/third_party/rust/wgpu-core-deps-apple/Cargo.toml b/third_party/rust/wgpu-core-deps-apple/Cargo.toml @@ -13,7 +13,7 @@ edition = "2021" rust-version = "1.76" name = "wgpu-core-deps-apple" -version = "27.0.0" +version = "28.0.0" authors = ["gfx-rs developers"] build = false autolib = false @@ -44,5 +44,5 @@ name = "wgpu_core_deps_apple" path = "src/lib.rs" [target.'cfg(target_vendor = "apple")'.dependencies.wgpu-hal] -version = "27.0.0" +version = "28.0.0" path = "../../../wgpu-hal" diff --git a/third_party/rust/wgpu-core-deps-windows-linux-android/.cargo-checksum.json b/third_party/rust/wgpu-core-deps-windows-linux-android/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"28ba3f78bab861041fa7ba583eff4967fce611252ca87d9eb364df7822f9cdae","LICENSE.APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE.MIT":"dc0d97139e8205818c703741c7be7cb3b96888bd5917b8d6fc6133731e403c21","README.md":"38086a02e134ac959bd061f1161c141a848a1b05a6bf31874035908217ad3eed","src/lib.rs":"a99034037e9c9ddf912f44c05b98af2c11e0ed0d09bb7cb69577826f46062ab9"},"package":null} -\ No newline at end of file +{"files":{"Cargo.toml":"8d8716a56e0602070acd04d880af40cda16a2a71390fd69fc96e77c8100e3132","LICENSE.APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE.MIT":"dc0d97139e8205818c703741c7be7cb3b96888bd5917b8d6fc6133731e403c21","README.md":"38086a02e134ac959bd061f1161c141a848a1b05a6bf31874035908217ad3eed","src/lib.rs":"a99034037e9c9ddf912f44c05b98af2c11e0ed0d09bb7cb69577826f46062ab9"},"package":null} +\ No newline at end of file diff --git a/third_party/rust/wgpu-core-deps-windows-linux-android/Cargo.toml b/third_party/rust/wgpu-core-deps-windows-linux-android/Cargo.toml @@ -13,7 +13,7 @@ edition = "2021" rust-version = "1.76" name = "wgpu-core-deps-windows-linux-android" -version = "27.0.0" +version = "28.0.0" authors = ["gfx-rs developers"] build = false autolib = false @@ -39,5 +39,5 @@ name = "wgpu_core_deps_windows_linux_android" path = "src/lib.rs" [target.'cfg(any(windows, target_os = "linux", target_os = "android", target_os = "freebsd"))'.dependencies.wgpu-hal] -version = "27.0.0" +version = "28.0.0" path = "../../../wgpu-hal" diff --git a/third_party/rust/wgpu-core/.cargo-checksum.json b/third_party/rust/wgpu-core/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"9f8b834d9d755f2d8cfa4b8164a6156292ba849b1d4a464f849ae3f843b2ba8e","LICENSE.APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE.MIT":"dc0d97139e8205818c703741c7be7cb3b96888bd5917b8d6fc6133731e403c21","build.rs":"5e3619e28faeac243cbae1a5f739ad15035dc9e1254957b64fd1deed6f393c8a","src/as_hal.rs":"5d08d8e49b016abb662957d8e42ab748d580a2f3e449caeeb7311f222b8818f6","src/binding_model.rs":"bef3fa63ac64093f496bb2c192f9a54919ceec560c04d5de5a57dd090b4902e6","src/command/allocator.rs":"386cb6e60bd332a881dbbe57ff66a0fa83f35e3ee924559f1689418ac6c7273a","src/command/bind.rs":"550a1bb23d6082fc9a83ebaf6b5deeab15b33ebb764ff9f9fe866b172423b0f6","src/command/bundle.rs":"899faaf507c4b998e5b801bc3f7e8eed617f5aa36686f43af8f613c0308bb87b","src/command/clear.rs":"d9f0b9ba00e568a4948990b8968a50fd10145c8eff6b7fcecf9df866c91f7c7f","src/command/compute.rs":"d3c6039d002a7feedc00ceea433ebe8f63d847238f84300b72ca3dcd628c9331","src/command/compute_command.rs":"6096eeca3b273005a6d2825a39f3c7beea4ef5104c2ff32b528bff76bef81a42","src/command/draw.rs":"5721868b0aa265037c843c4b4a4487a4f2827efbd65cab5046b9fbf4c54f6892","src/command/encoder.rs":"7655f8c248120f39e37f3ad0f84bd9816df78a75657790ab12d5aba144fef65f","src/command/encoder_command.rs":"41c15cf29ec11859bf7fcbd8e0d9908fdea5e5f8e678e5ebaa7aa2e80cf71926","src/command/ffi.rs":"4924adbb42a670b5e9827c0f2175939981e8fc78a36b2fe370426e8c550a4ce7","src/command/memory_init.rs":"f68a796c4262d261620cf85e5762e267dee013f4ef5d61f67fcb173b59048983","src/command/mod.rs":"38c6adb4ff8dc901efd3e757ac44f97bee76dc59d49022e34f6322c798f96951","src/command/pass.rs":"e92efb6eef955cc329e5c5d2aa49e5be78e42dc82ed62e6878b005f4450e9e80","src/command/query.rs":"dfea49ddf018d506882bb88d7763e808e293530f74749dce5bc5c902286467d1","src/command/ray_tracing.rs":"03d1984cfd365b457355a97d6366f8e32debebb538229dc44431f30b2d967c54","src/command/render.rs":"bc81666e95a2eb2fd042f5dfda6c434f94c7bbef52890fd1054f6525c549df75","src/command/render_command.rs":"032b2913e46cf27ede689278ddbe8255c3c2b6b24ff68559c82d25fc1913d36c","src/command/timestamp_writes.rs":"da06fb07b8d0917be3e7fb9212d0ffc6b16526e1c1df617213ef354c3e1fb446","src/command/transfer.rs":"a96400b752ae00060d4d3dd82ac68bec1a2a7ee35505b9b760f9851c0fee95dd","src/command/transition_resources.rs":"753cb02adfee4758404c403d19fd76e6c8de77de01435a2cbe2f60bdbe44bde1","src/conv.rs":"6b7e448bb4bae9ec58fc7738e7d5fb77bb28baa0402052d8a47b8dadf95c71d3","src/device/bgl.rs":"b081fe1393f1dd917151efc3a10ee0269fecd3920eac3b45411c1197a98c5a06","src/device/global.rs":"3c11d7ddfa4530bbee4aba7a3ad2fc2dfd812b1d8c1b8bada7882cd4b8abee31","src/device/life.rs":"44bd34cf5ab1c1a21729f2188df6d2ca0238f11c5f723380c8a4a16fbbd90b88","src/device/mod.rs":"61bd158c41bd1b759f6e748c0c39eae2a2bde7ed6faf6bf9c4fb86d81a5f5948","src/device/queue.rs":"fe7043aaf4eded0813c37991bb03c0a7cda5c3f9677e2e37881f574f57753450","src/device/ray_tracing.rs":"6f8df97f9fb1a3405a651d96619760ba7cf14830e28f65e824413a4f922abd95","src/device/resource.rs":"fef53ca7c80b98dbbbdcfd09ff9e0912f6b0ef0915e62006841149b569af0198","src/device/trace.rs":"022df1e97641b3e615f2974c297ff2c589c61bf1e2a2828236e101165eefa069","src/device/trace/record.rs":"2de83ec5be6e8cae928fe633f527b3eb654e3e58417b31f2c4a7235f8a80102e","src/error.rs":"4f07a0b09b30b2d6cbc855d0091d724000f492018af3b41e80befbeccf2a6f4e","src/global.rs":"bd76f9552496996f9eba6502e5caf1bcd6ca33bf3d5008795b5335f74e56fd55","src/hash_utils.rs":"9f57a627fe3f00659391670141df62f68d67922c7b186f2a9a5958ab16fb576f","src/hub.rs":"308c53e05134e5a48294c61511e43077adfdb5c334237de2016ed4537d562382","src/id.rs":"ec20d5b995178c0e731b1b8cecfd90772ee18dd4d91c28de434039e9c9e50486","src/identity.rs":"712ccda267eb602655c53672a94346aa4c9f88c0044ae6edcd658a814c9a50cf","src/indirect_validation/dispatch.rs":"b15d926e0de732c3c39c965f168f99598fa508ca4b0883061993d99dd644b31a","src/indirect_validation/draw.rs":"6d8338f37f406c6e2cf89713e8493f167e1d8e999737f2976de298810c2ada76","src/indirect_validation/mod.rs":"79466e4f9a59386e833958b3537beed6ffb6a7ee62aaabcf35382c2683998e43","src/indirect_validation/utils.rs":"e6a3b636dd71ff6b01d5abd5a589a0136fb30aa1d517a43f45cf2e5ad54da245","src/indirect_validation/validate_draw.wgsl":"fa7bba5f28c6c181f2d55ecfe386a5e5cd276bcb3c36aa7f963f43c535a9bf9a","src/init_tracker/buffer.rs":"6167a400ab271ba857f1c507e60a46fbd318c185aff87eecf7eb05f7f09b6963","src/init_tracker/mod.rs":"aa87df3428e3b23507377c71eae92dc1dd9f5d5374aa0b03906fb81507fc6ce6","src/init_tracker/texture.rs":"ffdc67d4be23bcd48b22945de94ac0ea8ee571f91eb6d00323b9afe6fa91eef3","src/instance.rs":"e7b9a20e070cc401f75dfe07aceb8f2950a4d99ed8a7d4413ef9f0813f5a8ca1","src/lib.rs":"57d2562e33b04113cc6c2ca3bc9a215ec779a291be82b680ff037fac7d7a8a15","src/lock/mod.rs":"8d3ae3f8d004d7f7d8a3aefe9f30b35669cb8e2409f5fba27b1fcb116b2429c4","src/lock/observing.rs":"5bf62cef9f3ae67e99af640442fadd4e1f762480d990ee69ed9924c9e94a8dce","src/lock/rank.rs":"238e6a97c58ee1a804863c8011bb257864301170344d18596bdaab09f3f74b54","src/lock/ranked.rs":"a95d6bf7f2ef510047a4c33687076beccf38a0148aac3261bd29fa7555e3f488","src/lock/vanilla.rs":"ca8156d4c981473d437db1056487a44c714760d685819eaff8cf82fb0098a608","src/pipeline.rs":"d84fbf90eda12f4acbb0ff8f8166a6b498ce89da52beacf5b81a6b06afa907c9","src/pipeline_cache.rs":"f6a82de6cf362be50335d26e8eed983b53812a8444dff9c8c988a075f3325f8f","src/pool.rs":"e11bfdc73a66321d26715465fa74657dcd26a9b856b80b8369b4aac2132a4295","src/present.rs":"edd9ce3be749a380c99be8e517963844ac04e3320085e7367a5e3a11e28efad9","src/ray_tracing.rs":"ae43907290f003f6247bb599927401bd768865f7773a41c327dc73fade014ede","src/registry.rs":"2a76c6397998d263b6f6347299f04a8d27ea4df7dda200e98f205cfa3850dadb","src/resource.rs":"59a2b8093790d85724960ceec5a421905e20b665c9e9b1b231b079cccbc51291","src/scratch.rs":"ea5c40e4d18a12f09cc1038f2dcdddb69b13e99275ac987d826658229a39b326","src/snatch.rs":"bf422810afd952894e365cd397763d42d6365ce3c5a5b4c1170566432f462939","src/storage.rs":"1a5bbf125a1cf65a36d8580e426ef1847c1638003fea0c6f76e309f5923b7692","src/timestamp_normalization/common.wgsl":"9f65aef0526ff9dde945fae70cef064ad961b0aee4291759ae82974cd2ead0a7","src/timestamp_normalization/mod.rs":"89444ad79900c12c2dc44780f1525cd0554d6ed1d9038938dd0075e8173ae59d","src/timestamp_normalization/timestamp_normalization.wgsl":"4b2202b965e4c67482d03a546ac38c72a602d79ed9a60e6f7217393c49afad49","src/track/blas.rs":"18c7b5b89a60ab49bbc0f4f8b85a24502c1d968714ef4c586095e119848d902a","src/track/buffer.rs":"7f2393d16d0e8f624327c7a3a1de7176f945f3d33bfd3e12640faddc5c2c9f83","src/track/metadata.rs":"04b8bcf8ded7c7c805d9336cfc874da9d8de7d12d99f7525f0540780a1dffc45","src/track/mod.rs":"c28de55e31cdde3feb067945a04446dc5343a39418c4b9c2c76148172e7ba855","src/track/range.rs":"2688b05a0c6e8510ff6ba3a9623e8b83f433a05ba743129928c56c93d9a9c233","src/track/stateless.rs":"3db699f5f48a319fa07fb16cdf51e1623d6ecac7a476467ee366e014ea665b89","src/track/texture.rs":"34e72a20364d3ce642c21b29a089811503aa2fb4e7dab1a1e20d8ddd598742d7","src/validation.rs":"1c2e91bcf5863b1ec00b85dd10588a8772b620d44e36b762f069d35b59a84ae4","src/weak_vec.rs":"a4193add5912b91226a3155cc613365b7fafdf2e7929d21d68bc19d149696e85"},"package":null} -\ No newline at end of file +{"files":{"Cargo.toml":"3fa11b214c7cfa646d5112dd9bee8916304821d09d48174532a0f49c38dd102e","LICENSE.APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE.MIT":"dc0d97139e8205818c703741c7be7cb3b96888bd5917b8d6fc6133731e403c21","build.rs":"5e3619e28faeac243cbae1a5f739ad15035dc9e1254957b64fd1deed6f393c8a","src/as_hal.rs":"5d08d8e49b016abb662957d8e42ab748d580a2f3e449caeeb7311f222b8818f6","src/binding_model.rs":"26c81824371ca9395d9dd3d6aa19c3317f845b10924a2ff80e7f01537cdd6db0","src/command/allocator.rs":"386cb6e60bd332a881dbbe57ff66a0fa83f35e3ee924559f1689418ac6c7273a","src/command/bind.rs":"26e3e693515dd82ae421b5b28d32138e6d9b62031e66b6761f2345bf2aa67148","src/command/bundle.rs":"e7b7cb4123d490769dbffda7f5e112af99c773db0af38c536d65f7ef91d3a422","src/command/clear.rs":"e125c82ff73f08ced329474a04228e656aba362535b630866b4877d42be929a3","src/command/compute.rs":"b22b9856fa08fae52e1fe03805a3faa2b1eda8d71768dd7a1b8f87c0bb38ee22","src/command/compute_command.rs":"92351bef1de6914b5b313dbe0df5a3bd25ef074f68b6d750f2a0f8a9011cfa89","src/command/draw.rs":"6ca62ab86e0b2216a1ac32e0f5b0d12653346a3e39922ffd9df722be7ed263f8","src/command/encoder.rs":"7655f8c248120f39e37f3ad0f84bd9816df78a75657790ab12d5aba144fef65f","src/command/encoder_command.rs":"2f9417c9c6c0cb52db561ed7d8a777fc29b59ade95a98ffaf91e4bb84f3b8cda","src/command/ffi.rs":"4924adbb42a670b5e9827c0f2175939981e8fc78a36b2fe370426e8c550a4ce7","src/command/memory_init.rs":"f68a796c4262d261620cf85e5762e267dee013f4ef5d61f67fcb173b59048983","src/command/mod.rs":"c73244dc4f6c95d967fbb413cb6b20594d7bb9c0654184c6a289217738795e88","src/command/pass.rs":"ebaffd6ddc8692d9b004a1f456694c1ba86dc3bb39ce5873c1bd3e4d940954fc","src/command/query.rs":"f1bf5f54d79833a2bc19a069cd74ab4d6f82ce538d4c3aa7c5c51a1e52c2eac8","src/command/ray_tracing.rs":"818788dd5e37325367832ec969e21ce47363a259754f907d518336ae5069f0bd","src/command/render.rs":"fdd6907ad22555e0410a6d129ef4dcb6fb8b4b6c5c729a758953f9eaaee47e7a","src/command/render_command.rs":"2e639cb0766d8de3047abb6997cc508b07f0c0adc1dfd1beed5f565af8b9eeef","src/command/timestamp_writes.rs":"da06fb07b8d0917be3e7fb9212d0ffc6b16526e1c1df617213ef354c3e1fb446","src/command/transfer.rs":"ce36f5bfaf289ad467c4a7a39a7e47bc1050b28784ad4d6cd84c1b6d7f74aa27","src/command/transition_resources.rs":"753cb02adfee4758404c403d19fd76e6c8de77de01435a2cbe2f60bdbe44bde1","src/conv.rs":"6142ee1793ad9b59dd3b95c87f15539a17333e0780c5dd5bdff92475711f1b36","src/device/bgl.rs":"b081fe1393f1dd917151efc3a10ee0269fecd3920eac3b45411c1197a98c5a06","src/device/global.rs":"c59c3fb9e425987b7ed5c6cb9a917f977e60ca1eed7b41dfc91220d88f3f198a","src/device/life.rs":"44bd34cf5ab1c1a21729f2188df6d2ca0238f11c5f723380c8a4a16fbbd90b88","src/device/mod.rs":"acf3377cb05a02586b2c76ba4e51cdcb5939700d6d6bb167d17f72fd1526af91","src/device/queue.rs":"01d95b1876459974fa0f106cef0011c6bead8300130da8c6cdb41994ccc158a4","src/device/ray_tracing.rs":"6f8df97f9fb1a3405a651d96619760ba7cf14830e28f65e824413a4f922abd95","src/device/resource.rs":"99ddb1d8e2a65e54e84833b82e0e7c1587f8f78c35539ed7546b46d3112c017b","src/device/trace.rs":"022df1e97641b3e615f2974c297ff2c589c61bf1e2a2828236e101165eefa069","src/device/trace/record.rs":"b045e728bcc01accaf22cf0f61eb0cb384b45f10286860597ef0f616a341e456","src/error.rs":"4f07a0b09b30b2d6cbc855d0091d724000f492018af3b41e80befbeccf2a6f4e","src/global.rs":"29a7a03d26e72899fceb6ddfdc540a96f737cf23068c99f09fc29c1fc3f80c75","src/hash_utils.rs":"9f57a627fe3f00659391670141df62f68d67922c7b186f2a9a5958ab16fb576f","src/hub.rs":"308c53e05134e5a48294c61511e43077adfdb5c334237de2016ed4537d562382","src/id.rs":"ec20d5b995178c0e731b1b8cecfd90772ee18dd4d91c28de434039e9c9e50486","src/identity.rs":"712ccda267eb602655c53672a94346aa4c9f88c0044ae6edcd658a814c9a50cf","src/indirect_validation/dispatch.rs":"04c845c36711484e614b379187c85e935feff4a8703d63e925a651456b3da6fa","src/indirect_validation/draw.rs":"80a3b126745a4fbbfcb195b05c4b93d9e7e17b8a166ff50e3b9e487b562ea366","src/indirect_validation/mod.rs":"79466e4f9a59386e833958b3537beed6ffb6a7ee62aaabcf35382c2683998e43","src/indirect_validation/utils.rs":"e6a3b636dd71ff6b01d5abd5a589a0136fb30aa1d517a43f45cf2e5ad54da245","src/indirect_validation/validate_draw.wgsl":"35a34174ac4a61b9684283878eb42468c1843b6778114bff3ef4dc40976e05d7","src/init_tracker/buffer.rs":"6167a400ab271ba857f1c507e60a46fbd318c185aff87eecf7eb05f7f09b6963","src/init_tracker/mod.rs":"aa87df3428e3b23507377c71eae92dc1dd9f5d5374aa0b03906fb81507fc6ce6","src/init_tracker/texture.rs":"ffdc67d4be23bcd48b22945de94ac0ea8ee571f91eb6d00323b9afe6fa91eef3","src/instance.rs":"128d113ba6742d35c5085b6958c45da5f2d544abada4463ada21c0eb614dcd00","src/lib.rs":"57d2562e33b04113cc6c2ca3bc9a215ec779a291be82b680ff037fac7d7a8a15","src/lock/mod.rs":"8d3ae3f8d004d7f7d8a3aefe9f30b35669cb8e2409f5fba27b1fcb116b2429c4","src/lock/observing.rs":"5bf62cef9f3ae67e99af640442fadd4e1f762480d990ee69ed9924c9e94a8dce","src/lock/rank.rs":"238e6a97c58ee1a804863c8011bb257864301170344d18596bdaab09f3f74b54","src/lock/ranked.rs":"a95d6bf7f2ef510047a4c33687076beccf38a0148aac3261bd29fa7555e3f488","src/lock/vanilla.rs":"ca8156d4c981473d437db1056487a44c714760d685819eaff8cf82fb0098a608","src/pipeline.rs":"d84fbf90eda12f4acbb0ff8f8166a6b498ce89da52beacf5b81a6b06afa907c9","src/pipeline_cache.rs":"96a7d2a5decf61a2906fe38f73377c9b785490a9510ee51eae553aeeaef799dc","src/pool.rs":"e11bfdc73a66321d26715465fa74657dcd26a9b856b80b8369b4aac2132a4295","src/present.rs":"65711dbf7305861e2ccbc61db95a43483376ce80c4462bb23da8b995549b5a08","src/ray_tracing.rs":"ae43907290f003f6247bb599927401bd768865f7773a41c327dc73fade014ede","src/registry.rs":"2a76c6397998d263b6f6347299f04a8d27ea4df7dda200e98f205cfa3850dadb","src/resource.rs":"46bc35f20e63487a34c8a94c13c93ecdc497c72a7439465e6f736958dbb920ae","src/scratch.rs":"ea5c40e4d18a12f09cc1038f2dcdddb69b13e99275ac987d826658229a39b326","src/snatch.rs":"bf422810afd952894e365cd397763d42d6365ce3c5a5b4c1170566432f462939","src/storage.rs":"1a5bbf125a1cf65a36d8580e426ef1847c1638003fea0c6f76e309f5923b7692","src/timestamp_normalization/common.wgsl":"9f65aef0526ff9dde945fae70cef064ad961b0aee4291759ae82974cd2ead0a7","src/timestamp_normalization/mod.rs":"4fb7a4d6c53797edc9dd3893a8e25d4efeff14c4846a8927d865101a6d7594a2","src/timestamp_normalization/timestamp_normalization.wgsl":"c0225bf02fdd741542bd20664a6d9195b91936e6f7758c9c4c0ac05b08122e79","src/track/blas.rs":"18c7b5b89a60ab49bbc0f4f8b85a24502c1d968714ef4c586095e119848d902a","src/track/buffer.rs":"7f2393d16d0e8f624327c7a3a1de7176f945f3d33bfd3e12640faddc5c2c9f83","src/track/metadata.rs":"04b8bcf8ded7c7c805d9336cfc874da9d8de7d12d99f7525f0540780a1dffc45","src/track/mod.rs":"c28de55e31cdde3feb067945a04446dc5343a39418c4b9c2c76148172e7ba855","src/track/range.rs":"2688b05a0c6e8510ff6ba3a9623e8b83f433a05ba743129928c56c93d9a9c233","src/track/stateless.rs":"3db699f5f48a319fa07fb16cdf51e1623d6ecac7a476467ee366e014ea665b89","src/track/texture.rs":"34e72a20364d3ce642c21b29a089811503aa2fb4e7dab1a1e20d8ddd598742d7","src/validation.rs":"d68edeec2ad9987c89f7959f443ece88a4df8753736b54d510238a112d750db0","src/weak_vec.rs":"a4193add5912b91226a3155cc613365b7fafdf2e7929d21d68bc19d149696e85"},"package":null} +\ No newline at end of file diff --git a/third_party/rust/wgpu-core/Cargo.toml b/third_party/rust/wgpu-core/Cargo.toml @@ -13,7 +13,7 @@ edition = "2021" rust-version = "1.82.0" name = "wgpu-core" -version = "27.0.0" +version = "28.0.0" authors = ["gfx-rs developers"] build = "build.rs" autolib = false @@ -150,7 +150,7 @@ version = "0.2" optional = true [dependencies.naga] -version = "27.0.0" +version = "28.0.0" path = "../naga" [dependencies.once_cell] @@ -171,7 +171,7 @@ optional = true default-features = false [dependencies.ron] -version = "0.11" +version = "0.12" optional = true [dependencies.rustc-hash] @@ -195,11 +195,11 @@ version = "2.0.12" default-features = false [dependencies.wgpu-hal] -version = "27.0.0" +version = "28.0.0" path = "../wgpu-hal" [dependencies.wgpu-types] -version = "27.0.0" +version = "28.0.0" path = "../wgpu-types" default-features = false @@ -207,12 +207,12 @@ default-features = false version = "0.2.1" [target.'cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))'.dependencies.wgpu-core-deps-wasm] -version = "27.0.0" +version = "28.0.0" path = "platform-deps/wasm" optional = true [target.'cfg(any(windows, target_os = "linux", target_os = "android", target_os = "freebsd"))'.dependencies.wgpu-core-deps-windows-linux-android] -version = "27.0.0" +version = "28.0.0" path = "platform-deps/windows-linux-android" optional = true @@ -221,12 +221,12 @@ version = "1.8" optional = true [target.'cfg(target_os = "emscripten")'.dependencies.wgpu-core-deps-emscripten] -version = "27.0.0" +version = "28.0.0" path = "platform-deps/emscripten" optional = true [target.'cfg(target_vendor = "apple")'.dependencies.wgpu-core-deps-apple] -version = "27.0.0" +version = "28.0.0" path = "platform-deps/apple" optional = true diff --git a/third_party/rust/wgpu-core/src/binding_model.rs b/third_party/rust/wgpu-core/src/binding_model.rs @@ -18,9 +18,7 @@ use serde::Serialize; use wgt::error::{ErrorType, WebGpuError}; use crate::{ - device::{ - bgl, Device, DeviceError, MissingDownlevelFlags, MissingFeatures, SHADER_STAGE_COUNT, - }, + device::{bgl, Device, DeviceError, MissingDownlevelFlags, MissingFeatures}, id::{BindGroupLayoutId, BufferId, ExternalTextureId, SamplerId, TextureViewId, TlasId}, init_tracker::{BufferInitTrackerAction, TextureInitTrackerAction}, pipeline::{ComputePipeline, RenderPipeline}, @@ -168,7 +166,7 @@ pub enum CreateBindGroupError { #[error("Binding declared as a single item, but bind group is using it as an array")] SingleBindingExpected, #[error("Effective buffer binding size {size} for storage buffers is expected to align to {alignment}, but size is {size}")] - UnalignedEffectiveBufferBindingSizeForStorage { alignment: u8, size: u64 }, + UnalignedEffectiveBufferBindingSizeForStorage { alignment: u32, size: u64 }, #[error("Buffer offset {0} does not respect device's requested `{1}` limit {2}")] UnalignedBufferOffset(wgt::BufferAddress, &'static str, u32), #[error( @@ -777,24 +775,16 @@ pub enum CreatePipelineLayoutError { #[error(transparent)] Device(#[from] DeviceError), #[error( - "Push constant at index {index} has range bound {bound} not aligned to {}", - wgt::PUSH_CONSTANT_ALIGNMENT + "Immediate data has range bound {size} which is not aligned to IMMEDIATE_DATA_ALIGNMENT ({})", + wgt::IMMEDIATE_DATA_ALIGNMENT )] - MisalignedPushConstantRange { index: usize, bound: u32 }, + MisalignedImmediateSize { size: u32 }, #[error(transparent)] MissingFeatures(#[from] MissingFeatures), - #[error("Push constant range (index {index}) provides for stage(s) {provided:?} but there exists another range that provides stage(s) {intersected:?}. Each stage may only be provided by one range")] - MoreThanOnePushConstantRangePerStage { - index: usize, - provided: wgt::ShaderStages, - intersected: wgt::ShaderStages, - }, - #[error("Push constant at index {index} has range {}..{} which exceeds device push constant size limit 0..{max}", range.start, range.end)] - PushConstantRangeTooLarge { - index: usize, - range: Range<u32>, - max: u32, - }, + #[error( + "Immediate data has size {size} which exceeds device immediate data size limit 0..{max}" + )] + ImmediateRangeTooLarge { size: u32, max: u32 }, #[error(transparent)] TooManyBindings(BindingTypeMaxCountError), #[error("Bind group layout count {actual} exceeds device bind group limit {max}")] @@ -810,9 +800,8 @@ impl WebGpuError for CreatePipelineLayoutError { Self::MissingFeatures(e) => e, Self::InvalidResource(e) => e, Self::TooManyBindings(e) => e, - Self::MisalignedPushConstantRange { .. } - | Self::MoreThanOnePushConstantRangePerStage { .. } - | Self::PushConstantRangeTooLarge { .. } + Self::MisalignedImmediateSize { .. } + | Self::ImmediateRangeTooLarge { .. } | Self::TooManyGroups { .. } => return ErrorType::Validation, }; e.webgpu_error_type() @@ -821,36 +810,21 @@ impl WebGpuError for CreatePipelineLayoutError { #[derive(Clone, Debug, Error)] #[non_exhaustive] -pub enum PushConstantUploadError { - #[error("Provided push constant with indices {offset}..{end_offset} overruns matching push constant range at index {idx}, with stage(s) {:?} and indices {:?}", range.stages, range.range)] +pub enum ImmediateUploadError { + #[error("Provided immediate data written to offset {offset}..{end_offset} overruns the immediate data range with a size of {size}")] TooLarge { offset: u32, end_offset: u32, - idx: usize, - range: wgt::PushConstantRange, + size: u32, }, - #[error("Provided push constant is for stage(s) {actual:?}, stage with a partial match found at index {idx} with stage(s) {matched:?}, however push constants must be complete matches")] - PartialRangeMatch { - actual: wgt::ShaderStages, - idx: usize, - matched: wgt::ShaderStages, - }, - #[error("Provided push constant is for stage(s) {actual:?}, but intersects a push constant range (at index {idx}) with stage(s) {missing:?}. Push constants must provide the stages for all ranges they intersect")] - MissingStages { - actual: wgt::ShaderStages, - idx: usize, - missing: wgt::ShaderStages, - }, - #[error("Provided push constant is for stage(s) {actual:?}, however the pipeline layout has no push constant range for the stage(s) {unmatched:?}")] - UnmatchedStages { - actual: wgt::ShaderStages, - unmatched: wgt::ShaderStages, - }, - #[error("Provided push constant offset {0} does not respect `PUSH_CONSTANT_ALIGNMENT`")] + #[error( + "Provided immediate data offset {0} does not respect `IMMEDIATE_DATA_ALIGNMENT` ({ida})", + ida = wgt::IMMEDIATE_DATA_ALIGNMENT + )] Unaligned(u32), } -impl WebGpuError for PushConstantUploadError { +impl WebGpuError for ImmediateUploadError { fn webgpu_error_type(&self) -> ErrorType { ErrorType::Validation } @@ -878,14 +852,12 @@ where serde(bound(deserialize = "<[BGL] as ToOwned>::Owned: Deserialize<'de>")) )] pub bind_group_layouts: Cow<'a, [BGL]>, - /// Set of push constant ranges this pipeline uses. Each shader stage that - /// uses push constants must define the range in push constant memory that - /// corresponds to its single `layout(push_constant)` uniform block. + /// The number of bytes of immediate data that are allocated for use + /// in the shader. The `var<immediate>`s in the shader attached to + /// this pipeline must be equal or smaller than this size. /// - /// If this array is non-empty, the - /// [`Features::PUSH_CONSTANTS`](wgt::Features::PUSH_CONSTANTS) feature must - /// be enabled. - pub push_constant_ranges: Cow<'a, [wgt::PushConstantRange]>, + /// If this value is non-zero, [`wgt::Features::IMMEDIATES`] must be enabled. + pub immediate_size: u32, } /// cbindgen:ignore @@ -899,7 +871,7 @@ pub struct PipelineLayout { /// The `label` from the descriptor used to create the resource. pub(crate) label: String, pub(crate) bind_group_layouts: ArrayVec<Arc<BindGroupLayout>, { hal::MAX_BIND_GROUPS }>, - pub(crate) push_constant_ranges: ArrayVec<wgt::PushConstantRange, { SHADER_STAGE_COUNT }>, + pub(crate) immediate_size: u32, } impl Drop for PipelineLayout { @@ -925,79 +897,25 @@ impl PipelineLayout { .collect() } - /// Validate push constants match up with expected ranges. - pub(crate) fn validate_push_constant_ranges( + /// Validate immediates match up with expected ranges. + pub(crate) fn validate_immediates_ranges( &self, - stages: wgt::ShaderStages, offset: u32, end_offset: u32, - ) -> Result<(), PushConstantUploadError> { - // Don't need to validate size against the push constant size limit here, - // as push constant ranges are already validated to be within bounds, + ) -> Result<(), ImmediateUploadError> { + // Don't need to validate size against the immediate data size limit here, + // as immediate data ranges are already validated to be within bounds, // and we validate that they are within the ranges. - if offset % wgt::PUSH_CONSTANT_ALIGNMENT != 0 { - return Err(PushConstantUploadError::Unaligned(offset)); + if offset % wgt::IMMEDIATE_DATA_ALIGNMENT != 0 { + return Err(ImmediateUploadError::Unaligned(offset)); } - // Push constant validation looks very complicated on the surface, but - // the problem can be range-reduced pretty well. - // - // Push constants require (summarized from the vulkan spec): - // 1. For each byte in the range and for each shader stage in stageFlags, - // there must be a push constant range in the layout that includes that - // byte and that stage. - // 2. For each byte in the range and for each push constant range that overlaps that byte, - // `stage` must include all stages in that push constant range’s `stage`. - // - // However there are some additional constraints that help us: - // 3. All push constant ranges are the only range that can access that stage. - // i.e. if one range has VERTEX, no other range has VERTEX - // - // Therefore we can simplify the checks in the following ways: - // - Because 3 guarantees that the push constant range has a unique stage, - // when we check for 1, we can simply check that our entire updated range - // is within a push constant range. i.e. our range for a specific stage cannot - // intersect more than one push constant range. - let mut used_stages = wgt::ShaderStages::NONE; - for (idx, range) in self.push_constant_ranges.iter().enumerate() { - // contains not intersects due to 2 - if stages.contains(range.stages) { - if !(range.range.start <= offset && end_offset <= range.range.end) { - return Err(PushConstantUploadError::TooLarge { - offset, - end_offset, - idx, - range: range.clone(), - }); - } - used_stages |= range.stages; - } else if stages.intersects(range.stages) { - // Will be caught by used stages check below, but we can do this because of 1 - // and is more helpful to the user. - return Err(PushConstantUploadError::PartialRangeMatch { - actual: stages, - idx, - matched: range.stages, - }); - } - - // The push constant range intersects range we are uploading - if offset < range.range.end && range.range.start < end_offset { - // But requires stages we don't provide - if !stages.contains(range.stages) { - return Err(PushConstantUploadError::MissingStages { - actual: stages, - idx, - missing: stages, - }); - } - } - } - if used_stages != stages { - return Err(PushConstantUploadError::UnmatchedStages { - actual: stages, - unmatched: stages - used_stages, + if end_offset > self.immediate_size { + return Err(ImmediateUploadError::TooLarge { + offset, + end_offset, + size: self.immediate_size, }); } Ok(()) @@ -1015,7 +933,15 @@ crate::impl_storage_item!(PipelineLayout); pub struct BufferBinding<B = BufferId> { pub buffer: B, pub offset: wgt::BufferAddress, - pub size: Option<wgt::BufferSize>, + + /// Size of the binding. If `None`, the binding spans from `offset` to the + /// end of the buffer. + /// + /// We use `BufferAddress` to allow a size of zero on this `wgpu_core` type, + /// because JavaScript bindings cannot readily express `Option<NonZeroU64>`. + /// The `wgpu` API uses `Option<BufferSize>` (i.e. `NonZeroU64`) for this + /// field. + pub size: Option<wgt::BufferAddress>, } pub type ResolvedBufferBinding = BufferBinding<Arc<Buffer>>; diff --git a/third_party/rust/wgpu-core/src/command/bind.rs b/third_party/rust/wgpu-core/src/command/bind.rs @@ -2,14 +2,12 @@ use core::{iter::zip, ops::Range}; use alloc::{boxed::Box, sync::Arc, vec::Vec}; -use arrayvec::ArrayVec; use thiserror::Error; use crate::{ binding_model::{BindGroup, LateMinBufferBindingSizeMismatch, PipelineLayout}, - device::SHADER_STAGE_COUNT, pipeline::LateSizedBufferGroup, - resource::{Labeled, ResourceErrorIdent}, + resource::{Labeled, ParentDevice, ResourceErrorIdent}, }; mod compat { @@ -338,12 +336,20 @@ impl Binder { } } + /// Returns `true` if the pipeline layout has been changed, i.e. if the + /// new PL was not the same as the old PL. pub(super) fn change_pipeline_layout<'a>( &'a mut self, new: &Arc<PipelineLayout>, late_sized_buffer_groups: &[LateSizedBufferGroup], - ) { - let old_id_opt = self.pipeline_layout.replace(new.clone()); + ) -> bool { + if let Some(old) = self.pipeline_layout.as_ref() { + if old.is_equal(new) { + return false; + } + } + + let old = self.pipeline_layout.replace(new.clone()); self.manager.update_expectations(&new.bind_group_layouts); @@ -374,12 +380,14 @@ impl Binder { } } - if let Some(old) = old_id_opt { + if let Some(old) = old { // root constants are the base compatibility property - if old.push_constant_ranges != new.push_constant_ranges { + if old.immediate_size != new.immediate_size { self.manager.update_start_index(0); } } + + true } pub(super) fn assign_group<'a>( @@ -498,54 +506,3 @@ impl Binder { Ok(()) } } - -struct PushConstantChange { - stages: wgt::ShaderStages, - offset: u32, - enable: bool, -} - -/// Break up possibly overlapping push constant ranges into a set of -/// non-overlapping ranges which contain all the stage flags of the -/// original ranges. This allows us to zero out (or write any value) -/// to every possible value. -pub fn compute_nonoverlapping_ranges( - ranges: &[wgt::PushConstantRange], -) -> ArrayVec<wgt::PushConstantRange, { SHADER_STAGE_COUNT * 2 }> { - if ranges.is_empty() { - return ArrayVec::new(); - } - debug_assert!(ranges.len() <= SHADER_STAGE_COUNT); - - let mut breaks: ArrayVec<PushConstantChange, { SHADER_STAGE_COUNT * 2 }> = ArrayVec::new(); - for range in ranges { - breaks.push(PushConstantChange { - stages: range.stages, - offset: range.range.start, - enable: true, - }); - breaks.push(PushConstantChange { - stages: range.stages, - offset: range.range.end, - enable: false, - }); - } - breaks.sort_unstable_by_key(|change| change.offset); - - let mut output_ranges = ArrayVec::new(); - let mut position = 0_u32; - let mut stages = wgt::ShaderStages::NONE; - - for bk in breaks { - if bk.offset - position > 0 && !stages.is_empty() { - output_ranges.push(wgt::PushConstantRange { - stages, - range: position..bk.offset, - }) - } - position = bk.offset; - stages.set(bk.stages, bk.enable); - } - - output_ranges -} diff --git a/third_party/rust/wgpu-core/src/command/bundle.rs b/third_party/rust/wgpu-core/src/command/bundle.rs @@ -29,9 +29,9 @@ Render passes are also isolated from the effects of bundles. After executing a render bundle, a render pass's pipeline, bind groups, and vertex and index buffers are are unset, so the bundle cannot affect later draw calls in the pass. -A render pass is not fully isolated from a bundle's effects on push constant +A render pass is not fully isolated from a bundle's effects on immediate data values. Draw calls following a bundle's execution will see whatever values the -bundle writes to push constant storage. Setting a pipeline initializes any push +bundle writes to immediate data storage. Setting a pipeline initializes any push constant storage it could access to zero, and this initialization may also be visible after bundle execution. @@ -101,13 +101,10 @@ use crate::command::ArcReferences; use crate::{ binding_model::{BindError, BindGroup, PipelineLayout}, command::{ - BasePass, BindGroupStateChange, ColorAttachmentError, DrawError, IdReferences, MapPassErr, - PassErrorScope, RenderCommand, RenderCommandError, StateChange, - }, - device::{ - AttachmentData, Device, DeviceError, MissingDownlevelFlags, RenderPassContext, - SHADER_STAGE_COUNT, + bind::Binder, BasePass, BindGroupStateChange, ColorAttachmentError, DrawError, + IdReferences, MapPassErr, PassErrorScope, RenderCommand, RenderCommandError, StateChange, }, + device::{AttachmentData, Device, DeviceError, MissingDownlevelFlags, RenderPassContext}, hub::Hub, id, init_tracker::{BufferInitTrackerAction, MemoryInitKind, TextureInitTrackerAction}, @@ -280,7 +277,6 @@ impl RenderBundleEncoder { let mut state = State { trackers: RenderBundleScope::new(), pipeline: None, - bind: (0..hal::MAX_BIND_GROUPS).map(|_| None).collect(), vertex: Default::default(), index: None, flat_dynamic_offsets: Vec::new(), @@ -289,6 +285,7 @@ impl RenderBundleEncoder { buffer_memory_init_actions: Vec::new(), texture_memory_init_actions: Vec::new(), next_dynamic_offset: 0, + binder: Binder::new(), }; let indices = &state.device.tracker_indices; @@ -354,14 +351,13 @@ impl RenderBundleEncoder { set_vertex_buffer(&mut state, &buffer_guard, slot, buffer, offset, size) .map_pass_err(scope)?; } - &RenderCommand::SetPushConstant { - stages, + &RenderCommand::SetImmediate { offset, size_bytes, values_offset, } => { - let scope = PassErrorScope::SetPushConstant; - set_push_constant(&mut state, stages, offset, size_bytes, values_offset) + let scope = PassErrorScope::SetImmediate; + set_immediates(&mut state, offset, size_bytes, values_offset) .map_pass_err(scope)?; } &RenderCommand::Draw { @@ -376,7 +372,6 @@ impl RenderBundleEncoder { }; draw( &mut state, - &base.dynamic_offsets, vertex_count, instance_count, first_vertex, @@ -397,7 +392,6 @@ impl RenderBundleEncoder { }; draw_indexed( &mut state, - &base.dynamic_offsets, index_count, instance_count, first_index, @@ -415,14 +409,8 @@ impl RenderBundleEncoder { kind: DrawKind::Draw, family: DrawCommandFamily::DrawMeshTasks, }; - draw_mesh_tasks( - &mut state, - &base.dynamic_offsets, - group_count_x, - group_count_y, - group_count_z, - ) - .map_pass_err(scope)?; + draw_mesh_tasks(&mut state, group_count_x, group_count_y, group_count_z) + .map_pass_err(scope)?; } &RenderCommand::DrawIndirect { buffer, @@ -436,15 +424,8 @@ impl RenderBundleEncoder { kind: DrawKind::DrawIndirect, family, }; - multi_draw_indirect( - &mut state, - &base.dynamic_offsets, - &buffer_guard, - buffer, - offset, - family, - ) - .map_pass_err(scope)?; + multi_draw_indirect(&mut state, &buffer_guard, buffer, offset, family) + .map_pass_err(scope)?; } &RenderCommand::DrawIndirect { count, @@ -498,7 +479,7 @@ impl RenderBundleEncoder { commands, dynamic_offsets: flat_dynamic_offsets, string_data: self.base.string_data, - push_constant_data: self.base.push_constant_data, + immediates_data: self.base.immediates_data, }, is_depth_read_only: self.is_depth_read_only, is_stencil_read_only: self.is_stencil_read_only, @@ -570,18 +551,13 @@ fn set_bind_group( bind_group.validate_dynamic_bindings(index, offsets)?; + unsafe { state.trackers.merge_bind_group(&bind_group.used)? }; + let bind_group = state.trackers.bind_groups.insert_single(bind_group); + state - .buffer_memory_init_actions - .extend_from_slice(&bind_group.used_buffer_ranges); - state - .texture_memory_init_actions - .extend_from_slice(&bind_group.used_texture_ranges); + .binder + .assign_group(index as usize, bind_group, offsets); - state.set_bind_group(index, &bind_group, offsets_range); - unsafe { state.trackers.merge_bind_group(&bind_group.used)? }; - state.trackers.bind_groups.insert_single(bind_group); - // Note: stateless trackers are not merged: the lifetime reference - // is held to the bind group itself. Ok(()) } @@ -614,14 +590,17 @@ fn set_pipeline( .commands .push(ArcRenderCommand::SetPipeline(pipeline.clone())); - // If this pipeline uses push constants, zero out their values. - if let Some(iter) = pipeline_state.zero_push_constants() { - state.commands.extend(iter) + // If this pipeline uses immediates, zero out their values. + if let Some(cmd) = pipeline_state.zero_immediates() { + state.commands.push(cmd); } - state.invalidate_bind_groups(&pipeline_state, &pipeline.layout); state.pipeline = Some(pipeline_state); + state + .binder + .change_pipeline_layout(&pipeline.layout, &pipeline.late_sized_buffer_groups); + state.trackers.render_pipelines.insert_single(pipeline); Ok(()) } @@ -709,9 +688,8 @@ fn set_vertex_buffer( Ok(()) } -fn set_push_constant( +fn set_immediates( state: &mut State, - stages: wgt::ShaderStages, offset: u32, size_bytes: u32, values_offset: Option<u32>, @@ -723,10 +701,9 @@ fn set_push_constant( pipeline_state .pipeline .layout - .validate_push_constant_ranges(stages, offset, end_offset)?; + .validate_immediates_ranges(offset, end_offset)?; - state.commands.push(ArcRenderCommand::SetPushConstant { - stages, + state.commands.push(ArcRenderCommand::SetImmediate { offset, size_bytes, values_offset, @@ -736,14 +713,13 @@ fn set_push_constant( fn draw( state: &mut State, - dynamic_offsets: &[u32], vertex_count: u32, instance_count: u32, first_vertex: u32, first_instance: u32, ) -> Result<(), RenderBundleErrorInner> { + state.is_ready()?; let pipeline = state.pipeline()?; - let used_bind_groups = pipeline.used_bind_groups; let vertex_limits = super::VertexLimits::new(state.vertex_buffer_sizes(), &pipeline.steps); vertex_limits.validate_vertex_limit(first_vertex, vertex_count)?; @@ -751,7 +727,7 @@ fn draw( if instance_count > 0 && vertex_count > 0 { state.flush_vertices(); - state.flush_binds(used_bind_groups, dynamic_offsets); + state.flush_bindings(); state.commands.push(ArcRenderCommand::Draw { vertex_count, instance_count, @@ -764,15 +740,15 @@ fn draw( fn draw_indexed( state: &mut State, - dynamic_offsets: &[u32], index_count: u32, instance_count: u32, first_index: u32, base_vertex: i32, first_instance: u32, ) -> Result<(), RenderBundleErrorInner> { + state.is_ready()?; let pipeline = state.pipeline()?; - let used_bind_groups = pipeline.used_bind_groups; + let index = match state.index { Some(ref index) => index, None => return Err(DrawError::MissingIndexBuffer.into()), @@ -794,7 +770,7 @@ fn draw_indexed( if instance_count > 0 && index_count > 0 { state.flush_index(); state.flush_vertices(); - state.flush_binds(used_bind_groups, dynamic_offsets); + state.flush_bindings(); state.commands.push(ArcRenderCommand::DrawIndexed { index_count, instance_count, @@ -808,16 +784,14 @@ fn draw_indexed( fn draw_mesh_tasks( state: &mut State, - dynamic_offsets: &[u32], group_count_x: u32, group_count_y: u32, group_count_z: u32, ) -> Result<(), RenderBundleErrorInner> { - let pipeline = state.pipeline()?; - let used_bind_groups = pipeline.used_bind_groups; + state.is_ready()?; - let groups_size_limit = state.device.limits.max_task_workgroups_per_dimension; - let max_groups = state.device.limits.max_task_workgroup_total_count; + let groups_size_limit = state.device.limits.max_task_mesh_workgroups_per_dimension; + let max_groups = state.device.limits.max_task_mesh_workgroup_total_count; if group_count_x > groups_size_limit || group_count_y > groups_size_limit || group_count_z > groups_size_limit @@ -831,7 +805,7 @@ fn draw_mesh_tasks( } if group_count_x > 0 && group_count_y > 0 && group_count_z > 0 { - state.flush_binds(used_bind_groups, dynamic_offsets); + state.flush_bindings(); state.commands.push(ArcRenderCommand::DrawMeshTasks { group_count_x, group_count_y, @@ -843,18 +817,17 @@ fn draw_mesh_tasks( fn multi_draw_indirect( state: &mut State, - dynamic_offsets: &[u32], buffer_guard: &crate::storage::Storage<Fallible<Buffer>>, buffer_id: id::Id<id::markers::Buffer>, offset: u64, family: DrawCommandFamily, ) -> Result<(), RenderBundleErrorInner> { + state.is_ready()?; state .device .require_downlevel_flags(wgt::DownlevelFlags::INDIRECT_EXECUTION)?; let pipeline = state.pipeline()?; - let used_bind_groups = pipeline.used_bind_groups; let buffer = buffer_guard.get(buffer_id).get()?; @@ -893,7 +866,7 @@ fn multi_draw_indirect( state.trackers.buffers.merge_single(&buffer, buffer_uses)?; state.flush_vertices(); - state.flush_binds(used_bind_groups, dynamic_offsets); + state.flush_bindings(); state.commands.push(ArcRenderCommand::DrawIndirect { buffer, offset, @@ -1055,8 +1028,7 @@ impl RenderBundle { let bb = hal::BufferBinding::new_unchecked(buffer, *offset, *size); unsafe { raw.set_vertex_buffer(*slot, bb) }; } - Cmd::SetPushConstant { - stages, + Cmd::SetImmediate { offset, size_bytes, values_offset, @@ -1065,27 +1037,19 @@ impl RenderBundle { if let Some(values_offset) = *values_offset { let values_end_offset = - (values_offset + size_bytes / wgt::PUSH_CONSTANT_ALIGNMENT) as usize; - let data_slice = &self.base.push_constant_data - [(values_offset as usize)..values_end_offset]; - - unsafe { - raw.set_push_constants( - pipeline_layout.raw(), - *stages, - *offset, - data_slice, - ) - } + (values_offset + size_bytes / wgt::IMMEDIATE_DATA_ALIGNMENT) as usize; + let data_slice = + &self.base.immediates_data[(values_offset as usize)..values_end_offset]; + + unsafe { raw.set_immediates(pipeline_layout.raw(), *offset, data_slice) } } else { - super::push_constant_clear( + super::immediates_clear( *offset, *size_bytes, |clear_offset, clear_data| { unsafe { - raw.set_push_constants( + raw.set_immediates( pipeline_layout.raw(), - *stages, clear_offset, clear_data, ) @@ -1314,21 +1278,6 @@ impl VertexState { } } -/// A bind group that has been set at a particular index during render bundle encoding. -#[derive(Debug)] -struct BindState { - /// The id of the bind group set at this index. - bind_group: Arc<BindGroup>, - - /// The range of dynamic offsets for this bind group, in the original - /// command stream's `BassPass::dynamic_offsets` array. - dynamic_offsets: Range<usize>, - - /// True if this index's contents have been changed since the last time we - /// generated a `SetBindGroup` command. - is_dirty: bool, -} - /// The bundle's current pipeline, and some cached information needed for validation. struct PipelineState { /// The pipeline @@ -1338,12 +1287,8 @@ struct PipelineState { /// by vertex buffer slot number. steps: Vec<VertexStep>, - /// Ranges of push constants this pipeline uses, copied from the pipeline - /// layout. - push_constant_ranges: ArrayVec<wgt::PushConstantRange, { SHADER_STAGE_COUNT }>, - - /// The number of bind groups this pipeline uses. - used_bind_groups: usize, + /// Size of the immediate data ranges this pipeline uses. Copied from the pipeline layout. + immediate_size: u32, } impl PipelineState { @@ -1351,36 +1296,22 @@ impl PipelineState { Self { pipeline: pipeline.clone(), steps: pipeline.vertex_steps.to_vec(), - push_constant_ranges: pipeline - .layout - .push_constant_ranges - .iter() - .cloned() - .collect(), - used_bind_groups: pipeline.layout.bind_group_layouts.len(), + immediate_size: pipeline.layout.immediate_size, } } - /// Return a sequence of commands to zero the push constant ranges this + /// Return a sequence of commands to zero the immediate data ranges this /// pipeline uses. If no initialization is necessary, return `None`. - fn zero_push_constants(&self) -> Option<impl Iterator<Item = ArcRenderCommand>> { - if !self.push_constant_ranges.is_empty() { - let nonoverlapping_ranges = - super::bind::compute_nonoverlapping_ranges(&self.push_constant_ranges); - - Some( - nonoverlapping_ranges - .into_iter() - .map(|range| ArcRenderCommand::SetPushConstant { - stages: range.stages, - offset: range.range.start, - size_bytes: range.range.end - range.range.start, - values_offset: None, // write zeros - }), - ) - } else { - None + fn zero_immediates(&self) -> Option<ArcRenderCommand> { + if self.immediate_size == 0 { + return None; } + + Some(ArcRenderCommand::SetImmediate { + offset: 0, + size_bytes: self.immediate_size, + values_offset: None, + }) } } @@ -1401,9 +1332,6 @@ struct State { /// The currently set pipeline, if any. pipeline: Option<PipelineState>, - /// The bind group set at each index, if any. - bind: ArrayVec<Option<BindState>, { hal::MAX_BIND_GROUPS }>, - /// The state of each vertex buffer slot. vertex: [Option<VertexState>; hal::MAX_VERTEX_BUFFERS], @@ -1424,6 +1352,7 @@ struct State { buffer_memory_init_actions: Vec<BufferInitTrackerAction>, texture_memory_init_actions: Vec<TextureInitTrackerAction>, next_dynamic_offset: usize, + binder: Binder, } impl State { @@ -1434,86 +1363,6 @@ impl State { .ok_or(DrawError::MissingPipeline(pass::MissingPipeline).into()) } - /// Mark all non-empty bind group table entries from `index` onwards as dirty. - fn invalidate_bind_group_from(&mut self, index: usize) { - for contents in self.bind[index..].iter_mut().flatten() { - contents.is_dirty = true; - } - } - - fn set_bind_group( - &mut self, - slot: u32, - bind_group: &Arc<BindGroup>, - dynamic_offsets: Range<usize>, - ) { - // If this call wouldn't actually change this index's state, we can - // return early. (If there are dynamic offsets, the range will always - // be different.) - if dynamic_offsets.is_empty() { - if let Some(ref contents) = self.bind[slot as usize] { - if contents.bind_group.is_equal(bind_group) { - return; - } - } - } - - // Record the index's new state. - self.bind[slot as usize] = Some(BindState { - bind_group: bind_group.clone(), - dynamic_offsets, - is_dirty: true, - }); - - // Once we've changed the bind group at a particular index, all - // subsequent indices need to be rewritten. - self.invalidate_bind_group_from(slot as usize + 1); - } - - /// Determine which bind group slots need to be re-set after a pipeline change. - /// - /// Given that we are switching from the current pipeline state to `new`, - /// whose layout is `layout`, mark all the bind group slots that we need to - /// emit new `SetBindGroup` commands for as dirty. - /// - /// According to `wgpu_hal`'s rules: - /// - /// - If the layout of any bind group slot changes, then that slot and - /// all following slots must have their bind groups re-established. - /// - /// - Changing the push constant ranges at all requires re-establishing - /// all bind groups. - fn invalidate_bind_groups(&mut self, new: &PipelineState, layout: &PipelineLayout) { - match self.pipeline { - None => { - // Establishing entirely new pipeline state. - self.invalidate_bind_group_from(0); - } - Some(ref old) => { - if old.pipeline.is_equal(&new.pipeline) { - // Everything is derived from the pipeline, so if the id has - // not changed, there's no need to consider anything else. - return; - } - - // Any push constant change invalidates all groups. - if old.push_constant_ranges != new.push_constant_ranges { - self.invalidate_bind_group_from(0); - } else { - let first_changed = self.bind.iter().zip(&layout.bind_group_layouts).position( - |(entry, layout)| match *entry { - Some(ref contents) => !contents.bind_group.layout.is_equal(layout), - None => false, - }, - ); - if let Some(slot) = first_changed { - self.invalidate_bind_group_from(slot); - } - } - } - } - } - /// Set the bundle's current index buffer and its associated parameters. fn set_index_buffer( &mut self, @@ -1556,37 +1405,44 @@ impl State { self.commands.extend(commands); } - /// Generate `SetBindGroup` commands for any bind groups that need to be updated. - fn flush_binds(&mut self, used_bind_groups: usize, dynamic_offsets: &[wgt::DynamicOffset]) { - // Append each dirty bind group's dynamic offsets to `flat_dynamic_offsets`. - for contents in self.bind[..used_bind_groups].iter().flatten() { - if contents.is_dirty { - self.flat_dynamic_offsets - .extend_from_slice(&dynamic_offsets[contents.dynamic_offsets.clone()]); - } + /// Validation for a draw command. + /// + /// This should be further deduplicated with similar validation on render/compute passes. + fn is_ready(&mut self) -> Result<(), DrawError> { + if let Some(pipeline) = self.pipeline.as_ref() { + self.binder + .check_compatibility(pipeline.pipeline.as_ref())?; + self.binder.check_late_buffer_bindings()?; + Ok(()) + } else { + Err(DrawError::MissingPipeline(pass::MissingPipeline)) } + } - // Then, generate `SetBindGroup` commands to update the dirty bind - // groups. After this, all bind groups are clean. - let commands = self.bind[..used_bind_groups] - .iter_mut() - .enumerate() - .flat_map(|(i, entry)| { - if let Some(ref mut contents) = *entry { - if contents.is_dirty { - contents.is_dirty = false; - let offsets = &contents.dynamic_offsets; - return Some(ArcRenderCommand::SetBindGroup { - index: i.try_into().unwrap(), - bind_group: Some(contents.bind_group.clone()), - num_dynamic_offsets: offsets.end - offsets.start, - }); - } - } - None - }); - - self.commands.extend(commands); + /// Generate `SetBindGroup` commands for any bind groups that need to be updated. + /// + /// This should be further deduplicated with similar code on render/compute passes. + fn flush_bindings(&mut self) { + let range = self.binder.take_rebind_range(); + let entries = self.binder.entries(range); + + self.commands.extend(entries.map(|(i, entry)| { + let bind_group = entry.group.as_ref().unwrap(); + + self.buffer_memory_init_actions + .extend_from_slice(&bind_group.used_buffer_ranges); + self.texture_memory_init_actions + .extend_from_slice(&bind_group.used_texture_ranges); + + self.flat_dynamic_offsets + .extend_from_slice(&entry.dynamic_offsets); + + ArcRenderCommand::SetBindGroup { + index: i.try_into().unwrap(), + bind_group: Some(bind_group.clone()), + num_dynamic_offsets: entry.dynamic_offsets.len(), + } + })); } fn vertex_buffer_sizes(&self) -> impl Iterator<Item = Option<wgt::BufferAddress>> + '_ { @@ -1747,36 +1603,34 @@ pub mod bundle_ffi { /// /// This function is unsafe as there is no guarantee that the given pointer is /// valid for `data` elements. - pub unsafe fn wgpu_render_bundle_set_push_constants( + pub unsafe fn wgpu_render_bundle_set_immediates( pass: &mut RenderBundleEncoder, - stages: wgt::ShaderStages, offset: u32, size_bytes: u32, data: *const u8, ) { assert_eq!( - offset & (wgt::PUSH_CONSTANT_ALIGNMENT - 1), + offset & (wgt::IMMEDIATE_DATA_ALIGNMENT - 1), 0, - "Push constant offset must be aligned to 4 bytes." + "Immediate data offset must be aligned to 4 bytes." ); assert_eq!( - size_bytes & (wgt::PUSH_CONSTANT_ALIGNMENT - 1), + size_bytes & (wgt::IMMEDIATE_DATA_ALIGNMENT - 1), 0, - "Push constant size must be aligned to 4 bytes." + "Immediate data size must be aligned to 4 bytes." ); let data_slice = unsafe { slice::from_raw_parts(data, size_bytes as usize) }; - let value_offset = pass.base.push_constant_data.len().try_into().expect( - "Ran out of push constant space. Don't set 4gb of push constants per RenderBundle.", + let value_offset = pass.base.immediates_data.len().try_into().expect( + "Ran out of immediate data space. Don't set 4gb of immediates per RenderBundle.", ); - pass.base.push_constant_data.extend( + pass.base.immediates_data.extend( data_slice - .chunks_exact(wgt::PUSH_CONSTANT_ALIGNMENT as usize) + .chunks_exact(wgt::IMMEDIATE_DATA_ALIGNMENT as usize) .map(|arr| u32::from_ne_bytes([arr[0], arr[1], arr[2], arr[3]])), ); - pass.base.commands.push(RenderCommand::SetPushConstant { - stages, + pass.base.commands.push(RenderCommand::SetImmediate { offset, size_bytes, values_offset: Some(value_offset), diff --git a/third_party/rust/wgpu-core/src/command/clear.rs b/third_party/rust/wgpu-core/src/command/clear.rs @@ -189,6 +189,8 @@ pub(super) fn clear_buffer( }); } + // This must happen after parameter validation (so that errors are reported + // as required by the spec), but before any side effects. if offset == end_offset { log::trace!("Ignoring fill_buffer of size 0"); return Ok(()); @@ -498,7 +500,7 @@ fn clear_texture_via_render_passes( }, depth_slice: None, resolve_target: None, - ops: hal::AttachmentOps::STORE, + ops: hal::AttachmentOps::STORE | hal::AttachmentOps::LOAD_CLEAR, clear_value: wgt::Color::TRANSPARENT, })]; (&color_attachments_tmp[..], None) @@ -515,8 +517,8 @@ fn clear_texture_via_render_passes( ), usage: wgt::TextureUses::DEPTH_STENCIL_WRITE, }, - depth_ops: hal::AttachmentOps::STORE, - stencil_ops: hal::AttachmentOps::STORE, + depth_ops: hal::AttachmentOps::STORE | hal::AttachmentOps::LOAD_CLEAR, + stencil_ops: hal::AttachmentOps::STORE | hal::AttachmentOps::LOAD_CLEAR, clear_value: (0.0, 0), }), ) diff --git a/third_party/rust/wgpu-core/src/command/compute.rs b/third_party/rust/wgpu-core/src/command/compute.rs @@ -14,7 +14,7 @@ use crate::{ resource::{RawResourceAccess, Trackable}, }; use crate::{ - binding_model::{LateMinBufferBindingSizeMismatch, PushConstantUploadError}, + binding_model::{ImmediateUploadError, LateMinBufferBindingSizeMismatch}, command::{ bind::{Binder, BinderError}, compute_command::ArcComputeCommand, @@ -177,13 +177,13 @@ pub enum ComputePassErrorInner { #[error(transparent)] Bind(#[from] BindError), #[error(transparent)] - PushConstants(#[from] PushConstantUploadError), - #[error("Push constant offset must be aligned to 4 bytes")] - PushConstantOffsetAlignment, - #[error("Push constant size must be aligned to 4 bytes")] - PushConstantSizeAlignment, - #[error("Ran out of push constant space. Don't set 4gb of push constants per ComputePass.")] - PushConstantOutOfMemory, + ImmediateData(#[from] ImmediateUploadError), + #[error("Immediate data offset must be aligned to 4 bytes")] + ImmediateOffsetAlignment, + #[error("Immediate data size must be aligned to 4 bytes")] + ImmediateDataizeAlignment, + #[error("Ran out of immediate data space. Don't set 4gb of immediates per ComputePass.")] + ImmediateOutOfMemory, #[error(transparent)] QueryUse(#[from] QueryUseError), #[error(transparent)] @@ -241,7 +241,7 @@ impl WebGpuError for ComputePassError { ComputePassErrorInner::MissingBufferUsage(e) => e, ComputePassErrorInner::Dispatch(e) => e, ComputePassErrorInner::Bind(e) => e, - ComputePassErrorInner::PushConstants(e) => e, + ComputePassErrorInner::ImmediateData(e) => e, ComputePassErrorInner::QueryUse(e) => e, ComputePassErrorInner::MissingFeatures(e) => e, ComputePassErrorInner::MissingDownlevelFlags(e) => e, @@ -253,9 +253,9 @@ impl WebGpuError for ComputePassError { | ComputePassErrorInner::BindGroupIndexOutOfRange { .. } | ComputePassErrorInner::UnalignedIndirectBufferOffset(_) | ComputePassErrorInner::IndirectBufferOverrun { .. } - | ComputePassErrorInner::PushConstantOffsetAlignment - | ComputePassErrorInner::PushConstantSizeAlignment - | ComputePassErrorInner::PushConstantOutOfMemory + | ComputePassErrorInner::ImmediateOffsetAlignment + | ComputePassErrorInner::ImmediateDataizeAlignment + | ComputePassErrorInner::ImmediateOutOfMemory | ComputePassErrorInner::PassEnded => return ErrorType::Validation, }; e.webgpu_error_type() @@ -269,7 +269,7 @@ struct State<'scope, 'snatch_guard, 'cmd_enc> { active_query: Option<(Arc<resource::QuerySet>, u32)>, - push_constants: Vec<u32>, + immediates: Vec<u32>, intermediate_trackers: Tracker, } @@ -491,22 +491,21 @@ impl Global { let base = pass.base.take(); - if matches!( - base, - Err(ComputePassError { - inner: ComputePassErrorInner::EncoderState(EncoderStateError::Ended), - scope: _, - }) - ) { - // If the encoder was already finished at time of pass creation, - // then it was not put in the locked state, so we need to - // generate a validation error here and now due to the encoder not - // being locked. The encoder already holds an error from when the - // pass was opened, or earlier. + if let Err(ComputePassError { + inner: + ComputePassErrorInner::EncoderState( + err @ (EncoderStateError::Locked | EncoderStateError::Ended), + ), + scope: _, + }) = base + { + // Most encoding errors are detected and raised within `finish()`. // - // All other errors are propagated to the encoder within `push_with`, - // and will be reported later. - return Err(EncoderStateError::Ended); + // However, we raise a validation error here if the pass was opened + // within another pass, or on a finished encoder. The latter is + // particularly important, because in that case reporting errors via + // `CommandEncoder::finish` is not possible. + return Err(err.clone()); } cmd_buf_data.push_with(|| -> Result<_, ComputePassError> { @@ -566,7 +565,7 @@ pub(super) fn encode_compute_pass( }, active_query: None, - push_constants: Vec::new(), + immediates: Vec::new(), intermediate_trackers: Tracker::new(), }; @@ -660,23 +659,23 @@ pub(super) fn encode_compute_pass( let scope = PassErrorScope::SetPipelineCompute; set_pipeline(&mut state, device, pipeline).map_pass_err(scope)?; } - ArcComputeCommand::SetPushConstant { + ArcComputeCommand::SetImmediate { offset, size_bytes, values_offset, } => { - let scope = PassErrorScope::SetPushConstant; - pass::set_push_constant::<ComputePassErrorInner, _>( + let scope = PassErrorScope::SetImmediate; + pass::set_immediates::<ComputePassErrorInner, _>( &mut state.pass, - &base.push_constant_data, - wgt::ShaderStages::COMPUTE, + &base.immediates_data, offset, size_bytes, Some(values_offset), |data_slice| { - let offset_in_elements = (offset / wgt::PUSH_CONSTANT_ALIGNMENT) as usize; - let size_in_elements = (size_bytes / wgt::PUSH_CONSTANT_ALIGNMENT) as usize; - state.push_constants[offset_in_elements..][..size_in_elements] + let offset_in_elements = (offset / wgt::IMMEDIATE_DATA_ALIGNMENT) as usize; + let size_in_elements = + (size_bytes / wgt::IMMEDIATE_DATA_ALIGNMENT) as usize; + state.immediates[offset_in_elements..][..size_in_elements] .copy_from_slice(data_slice); }, ) @@ -825,20 +824,15 @@ fn set_pipeline( &pipeline.layout, &pipeline.late_sized_buffer_groups, || { - // This only needs to be here for compute pipelines because they use push constants for + // This only needs to be here for compute pipelines because they use immediates for // validating indirect draws. - state.push_constants.clear(); - // Note that can only be one range for each stage. See the `MoreThanOnePushConstantRangePerStage` error. - if let Some(push_constant_range) = - pipeline.layout.push_constant_ranges.iter().find_map(|pcr| { - pcr.stages - .contains(wgt::ShaderStages::COMPUTE) - .then_some(pcr.range.clone()) - }) - { + state.immediates.clear(); + // Note that can only be one range for each stage. See the `MoreThanOneImmediateRangePerStage` error. + if pipeline.layout.immediate_size != 0 { // Note that non-0 range start doesn't work anyway https://github.com/gfx-rs/wgpu/issues/4502 - let len = push_constant_range.len() / wgt::PUSH_CONSTANT_ALIGNMENT as usize; - state.push_constants.extend(core::iter::repeat_n(0, len)); + let len = pipeline.layout.immediate_size as usize + / wgt::IMMEDIATE_DATA_ALIGNMENT as usize; + state.immediates.extend(core::iter::repeat_n(0, len)); } }, ) @@ -895,7 +889,6 @@ fn dispatch_indirect( .require_downlevel_flags(wgt::DownlevelFlags::INDIRECT_EXECUTION)?; buffer.check_usage(wgt::BufferUsages::INDIRECT)?; - buffer.check_destroyed(state.pass.base.snatch_guard)?; if offset % 4 != 0 { return Err(ComputePassErrorInner::UnalignedIndirectBufferOffset(offset)); @@ -910,6 +903,8 @@ fn dispatch_indirect( }); } + buffer.check_destroyed(state.pass.base.snatch_guard)?; + let stride = 3 * 4; // 3 integers, x/y/z group size state.pass.base.buffer_memory_init_actions.extend( buffer.initialization_status.read().create_action( @@ -935,9 +930,8 @@ fn dispatch_indirect( } unsafe { - state.pass.base.raw_encoder.set_push_constants( + state.pass.base.raw_encoder.set_immediates( params.pipeline_layout, - wgt::ShaderStages::COMPUTE, 0, &[params.offset_remainder as u32 / 4], ); @@ -1011,13 +1005,12 @@ fn dispatch_indirect( .set_compute_pipeline(pipeline.raw()); } - if !state.push_constants.is_empty() { + if !state.immediates.is_empty() { unsafe { - state.pass.base.raw_encoder.set_push_constants( + state.pass.base.raw_encoder.set_immediates( pipeline.layout.raw(), - wgt::ShaderStages::COMPUTE, 0, - &state.push_constants, + &state.immediates, ); } } @@ -1111,9 +1104,7 @@ impl Global { } let mut bind_group = None; - if bind_group_id.is_some() { - let bind_group_id = bind_group_id.unwrap(); - + if let Some(bind_group_id) = bind_group_id { let hub = &self.hub; bind_group = Some(pass_try!( base, @@ -1156,45 +1147,45 @@ impl Global { Ok(()) } - pub fn compute_pass_set_push_constants( + pub fn compute_pass_set_immediates( &self, pass: &mut ComputePass, offset: u32, data: &[u8], ) -> Result<(), PassStateError> { - let scope = PassErrorScope::SetPushConstant; + let scope = PassErrorScope::SetImmediate; let base = pass_base!(pass, scope); - if offset & (wgt::PUSH_CONSTANT_ALIGNMENT - 1) != 0 { + if offset & (wgt::IMMEDIATE_DATA_ALIGNMENT - 1) != 0 { pass_try!( base, scope, - Err(ComputePassErrorInner::PushConstantOffsetAlignment), + Err(ComputePassErrorInner::ImmediateOffsetAlignment), ); } - if data.len() as u32 & (wgt::PUSH_CONSTANT_ALIGNMENT - 1) != 0 { + if data.len() as u32 & (wgt::IMMEDIATE_DATA_ALIGNMENT - 1) != 0 { pass_try!( base, scope, - Err(ComputePassErrorInner::PushConstantSizeAlignment), + Err(ComputePassErrorInner::ImmediateDataizeAlignment), ) } let value_offset = pass_try!( base, scope, - base.push_constant_data + base.immediates_data .len() .try_into() - .map_err(|_| ComputePassErrorInner::PushConstantOutOfMemory) + .map_err(|_| ComputePassErrorInner::ImmediateOutOfMemory) ); - base.push_constant_data.extend( - data.chunks_exact(wgt::PUSH_CONSTANT_ALIGNMENT as usize) + base.immediates_data.extend( + data.chunks_exact(wgt::IMMEDIATE_DATA_ALIGNMENT as usize) .map(|arr| u32::from_ne_bytes([arr[0], arr[1], arr[2], arr[3]])), ); - base.commands.push(ArcComputeCommand::SetPushConstant { + base.commands.push(ArcComputeCommand::SetImmediate { offset, size_bytes: data.len() as u32, values_offset: value_offset, diff --git a/third_party/rust/wgpu-core/src/command/compute_command.rs b/third_party/rust/wgpu-core/src/command/compute_command.rs @@ -17,20 +17,20 @@ pub enum ComputeCommand<R: ReferenceType> { SetPipeline(R::ComputePipeline), - /// Set a range of push constants to values stored in `push_constant_data`. - SetPushConstant { - /// The byte offset within the push constant storage to write to. This + /// Set a range of immediates to values stored in `immediates_data`. + SetImmediate { + /// The byte offset within the immediate data storage to write to. This /// must be a multiple of four. offset: u32, /// The number of bytes to write. This must be a multiple of four. size_bytes: u32, - /// Index in `push_constant_data` of the start of the data + /// Index in `immediates_data` of the start of the data /// to be written. /// /// Note: this is not a byte offset like `offset`. Rather, it is the - /// index of the first `u32` element in `push_constant_data` to read. + /// index of the first `u32` element in `immediates_data` to read. values_offset: u32, }, diff --git a/third_party/rust/wgpu-core/src/command/draw.rs b/third_party/rust/wgpu-core/src/command/draw.rs @@ -7,7 +7,7 @@ use wgt::error::{ErrorType, WebGpuError}; use super::bind::BinderError; use crate::command::pass; use crate::{ - binding_model::{BindingError, LateMinBufferBindingSizeMismatch, PushConstantUploadError}, + binding_model::{BindingError, ImmediateUploadError, LateMinBufferBindingSizeMismatch}, resource::{ DestroyedResourceError, MissingBufferUsageError, MissingTextureUsageError, ResourceErrorIdent, @@ -116,7 +116,7 @@ pub enum RenderCommandError { #[error(transparent)] MissingTextureUsage(#[from] MissingTextureUsageError), #[error(transparent)] - PushConstants(#[from] PushConstantUploadError), + ImmediateData(#[from] ImmediateUploadError), #[error(transparent)] BindingError(#[from] BindingError), #[error("Viewport size {{ w: {w}, h: {h} }} greater than device's requested `max_texture_dimension_2d` limit {max}, or less than zero")] @@ -139,7 +139,7 @@ impl WebGpuError for RenderCommandError { Self::DestroyedResource(e) => e, Self::MissingBufferUsage(e) => e, Self::MissingTextureUsage(e) => e, - Self::PushConstants(e) => e, + Self::ImmediateData(e) => e, Self::BindingError(e) => e, Self::BindGroupIndexOutOfRange { .. } diff --git a/third_party/rust/wgpu-core/src/command/encoder_command.rs b/third_party/rust/wgpu-core/src/command/encoder_command.rs @@ -35,7 +35,7 @@ pub struct IdReferences; /// This is used for trace recording and playback. Recording stores the pointer /// value of `Arc` references in the trace. Playback uses the integer values /// as keys to a `HashMap`. -#[cfg(feature = "serde")] +#[cfg(any(feature = "trace", feature = "replay"))] #[doc(hidden)] #[derive(Clone, Debug)] pub struct PointerReferences; @@ -59,7 +59,7 @@ impl ReferenceType for IdReferences { type Tlas = id::TlasId; } -#[cfg(feature = "serde")] +#[cfg(any(feature = "trace", feature = "replay"))] impl ReferenceType for PointerReferences { type Buffer = id::PointerId<id::markers::Buffer>; type Surface = id::PointerId<id::markers::Surface>; diff --git a/third_party/rust/wgpu-core/src/command/mod.rs b/third_party/rust/wgpu-core/src/command/mod.rs @@ -96,7 +96,7 @@ pub type TexelCopyTextureInfo = ffi::TexelCopyTextureInfo; /// cbindgen:ignore pub type CopyExternalImageDestInfo = ffi::CopyExternalImageDestInfo; -const PUSH_CONSTANT_CLEAR_ARRAY: &[u32] = &[0_u32; 64]; +const IMMEDIATES_CLEAR_ARRAY: &[u32] = &[0_u32; 64]; /// The current state of a command or pass encoder. /// @@ -1282,11 +1282,11 @@ pub struct BasePass<C, E> { /// instruction consumes the next `len` bytes from this vector. pub string_data: Vec<u8>, - /// Data used by `SetPushConstant` instructions. + /// Data used by `SetImmediate` instructions. /// - /// See the documentation for [`RenderCommand::SetPushConstant`] - /// and [`ComputeCommand::SetPushConstant`] for details. - pub push_constant_data: Vec<u32>, + /// See the documentation for [`RenderCommand::SetImmediate`] + /// and [`ComputeCommand::SetImmediate`] for details. + pub immediates_data: Vec<u32>, } impl<C: Clone, E: Clone> BasePass<C, E> { @@ -1297,7 +1297,7 @@ impl<C: Clone, E: Clone> BasePass<C, E> { commands: Vec::new(), dynamic_offsets: Vec::new(), string_data: Vec::new(), - push_constant_data: Vec::new(), + immediates_data: Vec::new(), } } @@ -1308,7 +1308,7 @@ impl<C: Clone, E: Clone> BasePass<C, E> { commands: Vec::new(), dynamic_offsets: Vec::new(), string_data: Vec::new(), - push_constant_data: Vec::new(), + immediates_data: Vec::new(), } } @@ -1327,7 +1327,7 @@ impl<C: Clone, E: Clone> BasePass<C, E> { commands: mem::take(&mut self.commands), dynamic_offsets: mem::take(&mut self.dynamic_offsets), string_data: mem::take(&mut self.string_data), - push_constant_data: mem::take(&mut self.push_constant_data), + immediates_data: mem::take(&mut self.immediates_data), }), } } @@ -1771,20 +1771,20 @@ pub(crate) fn pop_debug_group(state: &mut EncodingState) -> Result<(), CommandEn Ok(()) } -fn push_constant_clear<PushFn>(offset: u32, size_bytes: u32, mut push_fn: PushFn) +fn immediates_clear<PushFn>(offset: u32, size_bytes: u32, mut push_fn: PushFn) where PushFn: FnMut(u32, &[u32]), { let mut count_words = 0_u32; - let size_words = size_bytes / wgt::PUSH_CONSTANT_ALIGNMENT; + let size_words = size_bytes / wgt::IMMEDIATE_DATA_ALIGNMENT; while count_words < size_words { - let count_bytes = count_words * wgt::PUSH_CONSTANT_ALIGNMENT; + let count_bytes = count_words * wgt::IMMEDIATE_DATA_ALIGNMENT; let size_to_write_words = - (size_words - count_words).min(PUSH_CONSTANT_CLEAR_ARRAY.len() as u32); + (size_words - count_words).min(IMMEDIATES_CLEAR_ARRAY.len() as u32); push_fn( offset + count_bytes, - &PUSH_CONSTANT_CLEAR_ARRAY[0..size_to_write_words as usize], + &IMMEDIATES_CLEAR_ARRAY[0..size_to_write_words as usize], ); count_words += size_to_write_words; @@ -1927,8 +1927,8 @@ pub enum PassErrorScope { SetPipelineRender, #[error("In a set_pipeline command")] SetPipelineCompute, - #[error("In a set_push_constant command")] - SetPushConstant, + #[error("In a set_immediates command")] + SetImmediate, #[error("In a set_vertex_buffer command")] SetVertexBuffer, #[error("In a set_index_buffer command")] diff --git a/third_party/rust/wgpu-core/src/command/pass.rs b/third_party/rust/wgpu-core/src/command/pass.rs @@ -1,6 +1,6 @@ //! Generic pass functions that both compute and render passes need. -use crate::binding_model::{BindError, BindGroup, PushConstantUploadError}; +use crate::binding_model::{BindError, BindGroup, ImmediateUploadError}; use crate::command::bind::Binder; use crate::command::encoder::EncodingState; use crate::command::memory_init::SurfacesInDiscardState; @@ -74,13 +74,10 @@ where + From<DestroyedResourceError> + From<BindError>, { - if bind_group.is_none() { - api_log!("Pass::set_bind_group {index} None"); + if let Some(ref bind_group) = bind_group { + api_log!("Pass::set_bind_group {index} {}", bind_group.error_ident()); } else { - api_log!( - "Pass::set_bind_group {index} {}", - bind_group.as_ref().unwrap().error_ident() - ); + api_log!("Pass::set_bind_group {index} None"); } let max_bind_groups = state.base.device.limits.max_bind_groups; @@ -202,59 +199,45 @@ pub(super) fn change_pipeline_layout<E, F: FnOnce()>( where E: From<DestroyedResourceError>, { - if state.binder.pipeline_layout.is_none() - || !state - .binder - .pipeline_layout - .as_ref() - .unwrap() - .is_equal(pipeline_layout) + if state + .binder + .change_pipeline_layout(pipeline_layout, late_sized_buffer_groups) { - state - .binder - .change_pipeline_layout(pipeline_layout, late_sized_buffer_groups); - f(); - let non_overlapping = - super::bind::compute_nonoverlapping_ranges(&pipeline_layout.push_constant_ranges); - - // Clear push constant ranges - for range in non_overlapping { - let offset = range.range.start; - let size_bytes = range.range.end - offset; - super::push_constant_clear(offset, size_bytes, |clear_offset, clear_data| unsafe { - state.base.raw_encoder.set_push_constants( + super::immediates_clear( + 0, + pipeline_layout.immediate_size, + |clear_offset, clear_data| unsafe { + state.base.raw_encoder.set_immediates( pipeline_layout.raw(), - range.stages, clear_offset, clear_data, ); - }); - } + }, + ); } Ok(()) } -pub(crate) fn set_push_constant<E, F: FnOnce(&[u32])>( +pub(crate) fn set_immediates<E, F: FnOnce(&[u32])>( state: &mut PassState, - push_constant_data: &[u32], - stages: wgt::ShaderStages, + immediates_data: &[u32], offset: u32, size_bytes: u32, values_offset: Option<u32>, f: F, ) -> Result<(), E> where - E: From<PushConstantUploadError> + From<InvalidValuesOffset> + From<MissingPipeline>, + E: From<ImmediateUploadError> + From<InvalidValuesOffset> + From<MissingPipeline>, { - api_log!("Pass::set_push_constants"); + api_log!("Pass::set_immediates"); let values_offset = values_offset.ok_or(InvalidValuesOffset)?; let end_offset_bytes = offset + size_bytes; - let values_end_offset = (values_offset + size_bytes / wgt::PUSH_CONSTANT_ALIGNMENT) as usize; - let data_slice = &push_constant_data[(values_offset as usize)..values_end_offset]; + let values_end_offset = (values_offset + size_bytes / wgt::IMMEDIATE_DATA_ALIGNMENT) as usize; + let data_slice = &immediates_data[(values_offset as usize)..values_end_offset]; let pipeline_layout = state .binder @@ -262,7 +245,7 @@ where .as_ref() .ok_or(MissingPipeline)?; - pipeline_layout.validate_push_constant_ranges(stages, offset, end_offset_bytes)?; + pipeline_layout.validate_immediates_ranges(offset, end_offset_bytes)?; f(data_slice); @@ -270,7 +253,7 @@ where state .base .raw_encoder - .set_push_constants(pipeline_layout.raw(), stages, offset, data_slice) + .set_immediates(pipeline_layout.raw(), offset, data_slice) } Ok(()) } diff --git a/third_party/rust/wgpu-core/src/command/query.rs b/third_party/rust/wgpu-core/src/command/query.rs @@ -150,6 +150,8 @@ pub enum QueryUseError { set_type: SimplifiedQueryType, query_type: SimplifiedQueryType, }, + #[error("A query of type {query_type:?} was not ended before the encoder was finished")] + MissingEnd { query_type: SimplifiedQueryType }, } impl WebGpuError for QueryUseError { @@ -160,7 +162,8 @@ impl WebGpuError for QueryUseError { | Self::UsedTwiceInsideRenderpass { .. } | Self::AlreadyStarted { .. } | Self::AlreadyStopped - | Self::IncompatibleType { .. } => ErrorType::Validation, + | Self::IncompatibleType { .. } + | Self::MissingEnd { .. } => ErrorType::Validation, } } } diff --git a/third_party/rust/wgpu-core/src/command/ray_tracing.rs b/third_party/rust/wgpu-core/src/command/ray_tracing.rs @@ -340,7 +340,7 @@ pub(crate) fn build_acceleration_structures( } in &tlas_storage { if tlas.update_mode == wgt::AccelerationStructureUpdateMode::PreferUpdate { - log::info!("only rebuild implemented") + log::warn!("build_acceleration_structures called with PreferUpdate, but only rebuild is implemented"); } tlas_descriptors.push(hal::BuildAccelerationStructureDescriptor { entries, @@ -941,7 +941,7 @@ fn map_blas<'a>( scratch_buffer_offset, } = storage; if blas.update_mode == wgt::AccelerationStructureUpdateMode::PreferUpdate { - log::info!("only rebuild implemented") + log::debug!("only rebuild implemented") } let raw = blas.try_raw(snatch_guard)?; diff --git a/third_party/rust/wgpu-core/src/command/render.rs b/third_party/rust/wgpu-core/src/command/render.rs @@ -6,8 +6,8 @@ use arrayvec::ArrayVec; use thiserror::Error; use wgt::{ error::{ErrorType, WebGpuError}, - BufferAddress, BufferSize, BufferUsages, Color, DynamicOffset, IndexFormat, ShaderStages, - TextureSelector, TextureUsages, TextureViewDimension, VertexStepMode, + BufferAddress, BufferSize, BufferUsages, Color, DynamicOffset, IndexFormat, TextureSelector, + TextureUsages, TextureViewDimension, VertexStepMode, }; use crate::command::{ @@ -44,7 +44,7 @@ use crate::{ ParentDevice, QuerySet, Texture, TextureView, TextureViewNotRenderableReason, }, track::{ResourceUsageCompatibilityError, Tracker, UsageScope}, - Label, + validation, Label, }; #[cfg(feature = "serde")] @@ -59,20 +59,21 @@ use super::{ }; use super::{DrawCommandFamily, DrawKind, Rect}; -use crate::binding_model::{BindError, PushConstantUploadError}; +use crate::binding_model::{BindError, ImmediateUploadError}; pub use wgt::{LoadOp, StoreOp}; fn load_hal_ops<V>(load: LoadOp<V>) -> hal::AttachmentOps { match load { LoadOp::Load => hal::AttachmentOps::LOAD, - LoadOp::Clear(_) => hal::AttachmentOps::empty(), + LoadOp::Clear(_) => hal::AttachmentOps::LOAD_CLEAR, + LoadOp::DontCare(_) => hal::AttachmentOps::LOAD_DONT_CARE, } } fn store_hal_ops(store: StoreOp) -> hal::AttachmentOps { match store { StoreOp::Store => hal::AttachmentOps::STORE, - StoreOp::Discard => hal::AttachmentOps::empty(), + StoreOp::Discard => hal::AttachmentOps::STORE_DISCARD, } } @@ -115,6 +116,7 @@ impl<V: Copy + Default> PassChannel<Option<V>> { Ok(ResolvedPassChannel::Operational(wgt::Operations { load: match self.load_op.ok_or(AttachmentError::NoLoad)? { LoadOp::Clear(clear_value) => LoadOp::Clear(handle_clear(clear_value)?), + LoadOp::DontCare(token) => LoadOp::DontCare(token), LoadOp::Load => LoadOp::Load, }, store: self.store_op.ok_or(AttachmentError::NoStore)?, @@ -204,7 +206,7 @@ impl ArcRenderPassColorAttachment { fn clear_value(&self) -> Color { match self.load_op { LoadOp::Clear(clear_value) => clear_value, - LoadOp::Load => Color::default(), + LoadOp::DontCare(_) | LoadOp::Load => Color::default(), } } } @@ -357,10 +359,7 @@ impl fmt::Debug for RenderPass { .field("depth_stencil_target", &self.depth_stencil_attachment) .field("command count", &self.base.commands.len()) .field("dynamic offset count", &self.base.dynamic_offsets.len()) - .field( - "push constant u32 count", - &self.base.push_constant_data.len(), - ) + .field("immediate data u32 count", &self.base.immediates_data.len()) .field("multiview mask", &self.multiview_mask) .finish() } @@ -801,12 +800,12 @@ pub enum RenderPassErrorInner { Draw(#[from] DrawError), #[error(transparent)] Bind(#[from] BindError), - #[error("Push constant offset must be aligned to 4 bytes")] - PushConstantOffsetAlignment, - #[error("Push constant size must be aligned to 4 bytes")] - PushConstantSizeAlignment, - #[error("Ran out of push constant space. Don't set 4gb of push constants per ComputePass.")] - PushConstantOutOfMemory, + #[error("Immediate data offset must be aligned to 4 bytes")] + ImmediateOffsetAlignment, + #[error("Immediate data size must be aligned to 4 bytes")] + ImmediateDataizeAlignment, + #[error("Ran out of immediate data space. Don't set 4gb of immediates per ComputePass.")] + ImmediateOutOfMemory, #[error(transparent)] QueryUse(#[from] QueryUseError), #[error("Multiview layer count must match")] @@ -853,8 +852,8 @@ impl From<pass::MissingPipeline> for RenderPassErrorInner { } } -impl From<PushConstantUploadError> for RenderPassErrorInner { - fn from(error: PushConstantUploadError) -> Self { +impl From<ImmediateUploadError> for RenderPassErrorInner { + fn from(error: ImmediateUploadError) -> Self { Self::RenderCommand(error.into()) } } @@ -913,9 +912,9 @@ impl WebGpuError for RenderPassError { | RenderPassErrorInner::IndirectCountBufferOverrun { .. } | RenderPassErrorInner::ResourceUsageCompatibility(..) | RenderPassErrorInner::IncompatibleBundleReadOnlyDepthStencil { .. } - | RenderPassErrorInner::PushConstantOffsetAlignment - | RenderPassErrorInner::PushConstantSizeAlignment - | RenderPassErrorInner::PushConstantOutOfMemory + | RenderPassErrorInner::ImmediateOffsetAlignment + | RenderPassErrorInner::ImmediateDataizeAlignment + | RenderPassErrorInner::ImmediateOutOfMemory | RenderPassErrorInner::MultiViewMismatch | RenderPassErrorInner::MultiViewDimensionMismatch | RenderPassErrorInner::TooManyMultiviewViews @@ -1278,6 +1277,15 @@ impl RenderPassInfo { )); } + validation::validate_color_attachment_bytes_per_sample( + color_attachments + .iter() + .flatten() + .map(|at| at.view.desc.format), + device.limits.max_color_attachment_bytes_per_sample, + ) + .map_err(RenderPassErrorInner::ColorAttachment)?; + fn check_attachment_overlap( attachment_set: &mut crate::FastHashSet<(crate::track::TrackerIndex, u32, u32)>, view: &TextureView, @@ -1555,13 +1563,13 @@ impl RenderPassInfo { if let Some((aspect, view)) = self.divergent_discarded_depth_stencil_aspect { let (depth_ops, stencil_ops) = if aspect == wgt::TextureAspect::DepthOnly { ( - hal::AttachmentOps::STORE, // clear depth - hal::AttachmentOps::LOAD | hal::AttachmentOps::STORE, // unchanged stencil + hal::AttachmentOps::LOAD_CLEAR | hal::AttachmentOps::STORE, // clear depth + hal::AttachmentOps::LOAD | hal::AttachmentOps::STORE, // unchanged stencil ) } else { ( hal::AttachmentOps::LOAD | hal::AttachmentOps::STORE, // unchanged stencil - hal::AttachmentOps::STORE, // clear depth + hal::AttachmentOps::LOAD_CLEAR | hal::AttachmentOps::STORE, // clear depth ) }; let desc = hal::RenderPassDescriptor::<'_, _, dyn hal::DynTextureView> { @@ -1823,22 +1831,21 @@ impl Global { let base = pass.base.take(); - if matches!( - base, - Err(RenderPassError { - inner: RenderPassErrorInner::EncoderState(EncoderStateError::Ended), - scope: _, - }) - ) { - // If the encoder was already finished at time of pass creation, - // then it was not put in the locked state, so we need to - // generate a validation error here and now due to the encoder not - // being locked. The encoder already holds an error from when the - // pass was opened, or earlier. + if let Err(RenderPassError { + inner: + RenderPassErrorInner::EncoderState( + err @ (EncoderStateError::Locked | EncoderStateError::Ended), + ), + scope: _, + }) = base + { + // Most encoding errors are detected and raised within `finish()`. // - // All other errors are propagated to the encoder within `push_with`, - // and will be reported later. - return Err(EncoderStateError::Ended); + // However, we raise a validation error here if the pass was opened + // within another pass, or on a finished encoder. The latter is + // particularly important, because in that case reporting errors via + // `CommandEncoder::finish` is not possible. + return Err(err.clone()); } cmd_buf_data.push_with(|| -> Result<_, RenderPassError> { @@ -2013,17 +2020,15 @@ pub(super) fn encode_render_pass( let scope = PassErrorScope::SetViewport; set_viewport(&mut state, rect, depth_min, depth_max).map_pass_err(scope)?; } - ArcRenderCommand::SetPushConstant { - stages, + ArcRenderCommand::SetImmediate { offset, size_bytes, values_offset, } => { - let scope = PassErrorScope::SetPushConstant; - pass::set_push_constant::<RenderPassErrorInner, _>( + let scope = PassErrorScope::SetImmediate; + pass::set_immediates::<RenderPassErrorInner, _>( &mut state.pass, - &base.push_constant_data, - stages, + &base.immediates_data, offset, size_bytes, values_offset, @@ -2243,6 +2248,18 @@ pub(super) fn encode_render_pass( .map_pass_err(pass_scope), )?; } + if state.active_occlusion_query.is_some() { + Err(RenderPassErrorInner::QueryUse(QueryUseError::MissingEnd { + query_type: super::SimplifiedQueryType::Occlusion, + }) + .map_pass_err(pass_scope))?; + } + if state.active_pipeline_statistics_query.is_some() { + Err(RenderPassErrorInner::QueryUse(QueryUseError::MissingEnd { + query_type: super::SimplifiedQueryType::PipelineStatistics, + }) + .map_pass_err(pass_scope))?; + } state .info @@ -2709,8 +2726,13 @@ fn draw_mesh_tasks( .base .device .limits - .max_task_workgroups_per_dimension; - let max_groups = state.pass.base.device.limits.max_task_workgroup_total_count; + .max_task_mesh_workgroups_per_dimension; + let max_groups = state + .pass + .base + .device + .limits + .max_task_mesh_workgroup_total_count; if group_count_x > groups_size_limit || group_count_y > groups_size_limit || group_count_z > groups_size_limit @@ -3162,9 +3184,7 @@ impl Global { } let mut bind_group = None; - if bind_group_id.is_some() { - let bind_group_id = bind_group_id.unwrap(); - + if let Some(bind_group_id) = bind_group_id { let hub = &self.hub; bind_group = Some(pass_try!( base, @@ -3316,47 +3336,45 @@ impl Global { Ok(()) } - pub fn render_pass_set_push_constants( + pub fn render_pass_set_immediates( &self, pass: &mut RenderPass, - stages: ShaderStages, offset: u32, data: &[u8], ) -> Result<(), PassStateError> { - let scope = PassErrorScope::SetPushConstant; + let scope = PassErrorScope::SetImmediate; let base = pass_base!(pass, scope); - if offset & (wgt::PUSH_CONSTANT_ALIGNMENT - 1) != 0 { + if offset & (wgt::IMMEDIATE_DATA_ALIGNMENT - 1) != 0 { pass_try!( base, scope, - Err(RenderPassErrorInner::PushConstantOffsetAlignment) + Err(RenderPassErrorInner::ImmediateOffsetAlignment) ); } - if data.len() as u32 & (wgt::PUSH_CONSTANT_ALIGNMENT - 1) != 0 { + if data.len() as u32 & (wgt::IMMEDIATE_DATA_ALIGNMENT - 1) != 0 { pass_try!( base, scope, - Err(RenderPassErrorInner::PushConstantSizeAlignment) + Err(RenderPassErrorInner::ImmediateDataizeAlignment) ); } let value_offset = pass_try!( base, scope, - base.push_constant_data + base.immediates_data .len() .try_into() - .map_err(|_| RenderPassErrorInner::PushConstantOutOfMemory), + .map_err(|_| RenderPassErrorInner::ImmediateOutOfMemory), ); - base.push_constant_data.extend( - data.chunks_exact(wgt::PUSH_CONSTANT_ALIGNMENT as usize) + base.immediates_data.extend( + data.chunks_exact(wgt::IMMEDIATE_DATA_ALIGNMENT as usize) .map(|arr| u32::from_ne_bytes([arr[0], arr[1], arr[2], arr[3]])), ); - base.commands.push(ArcRenderCommand::SetPushConstant { - stages, + base.commands.push(ArcRenderCommand::SetImmediate { offset, size_bytes: data.len() as u32, values_offset: Some(value_offset), diff --git a/third_party/rust/wgpu-core/src/command/render_command.rs b/third_party/rust/wgpu-core/src/command/render_command.rs @@ -41,29 +41,26 @@ pub enum RenderCommand<R: ReferenceType> { }, SetScissor(Rect<u32>), - /// Set a range of push constants to values stored in [`BasePass::push_constant_data`]. + /// Set a range of immediates to values stored in [`BasePass::immediates_data`]. /// - /// See [`wgpu::RenderPass::set_push_constants`] for a detailed explanation + /// See [`wgpu::RenderPass::set_immediates`] for a detailed explanation /// of the restrictions these commands must satisfy. - SetPushConstant { - /// Which stages we are setting push constant values for. - stages: wgt::ShaderStages, - - /// The byte offset within the push constant storage to write to. This + SetImmediate { + /// The byte offset within the immediate data storage to write to. This /// must be a multiple of four. offset: u32, /// The number of bytes to write. This must be a multiple of four. size_bytes: u32, - /// Index in [`BasePass::push_constant_data`] of the start of the data + /// Index in [`BasePass::immediates_data`] of the start of the data /// to be written. /// /// Note: this is not a byte offset like `offset`. Rather, it is the - /// index of the first `u32` element in `push_constant_data` to read. + /// index of the first `u32` element in `immediates_data` to read. /// /// `None` means zeros should be written to the destination range, and - /// there is no corresponding data in `push_constant_data`. This is used + /// there is no corresponding data in `immediates_data`. This is used /// by render bundles, which explicitly clear out any state that /// post-bundle code might see. values_offset: Option<u32>, diff --git a/third_party/rust/wgpu-core/src/command/transfer.rs b/third_party/rust/wgpu-core/src/command/transfer.rs @@ -1037,6 +1037,8 @@ pub(super) fn copy_buffer_to_buffer( .into()); } + // This must happen after parameter validation (so that errors are reported + // as required by the spec), but before any side effects. if size == 0 { log::trace!("Ignoring copy_buffer_to_buffer of size 0"); return Ok(()); @@ -1099,39 +1101,14 @@ pub(super) fn copy_buffer_to_texture( let (dst_range, dst_base) = extract_texture_selector(destination, copy_size, dst_texture)?; let src_raw = src_buffer.try_raw(state.snatch_guard)?; - let dst_raw = dst_texture.try_raw(state.snatch_guard)?; - - 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(()); - } - - // Handle texture init *before* dealing with barrier transitions so we - // have an easier time inserting "immediate-inits" that may be required - // by prior discards in rare cases. - handle_dst_texture_init(state, destination, copy_size, dst_texture)?; - - let src_pending = state - .tracker - .buffers - .set_single(src_buffer, wgt::BufferUses::COPY_SRC); - src_buffer .check_usage(BufferUsages::COPY_SRC) .map_err(TransferError::MissingBufferUsage)?; - let src_barrier = src_pending.map(|pending| pending.into_hal(src_buffer, state.snatch_guard)); - let dst_pending = - state - .tracker - .textures - .set_single(dst_texture, dst_range, wgt::TextureUses::COPY_DST); + let dst_raw = dst_texture.try_raw(state.snatch_guard)?; dst_texture .check_usage(TextureUsages::COPY_DST) .map_err(TransferError::MissingTextureUsage)?; - let dst_barrier = dst_pending - .map(|pending| pending.into_hal(dst_raw)) - .collect::<Vec<_>>(); validate_texture_copy_dst_format(dst_texture.desc.format, destination.aspect)?; @@ -1160,6 +1137,33 @@ pub(super) fn copy_buffer_to_texture( .map_err(TransferError::from)?; } + // This must happen after parameter validation (so that errors are reported + // as required by the spec), but before any side effects. + 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(()); + } + + // Handle texture init *before* dealing with barrier transitions so we + // have an easier time inserting "immediate-inits" that may be required + // by prior discards in rare cases. + handle_dst_texture_init(state, destination, copy_size, dst_texture)?; + + let src_pending = state + .tracker + .buffers + .set_single(src_buffer, wgt::BufferUses::COPY_SRC); + let src_barrier = src_pending.map(|pending| pending.into_hal(src_buffer, state.snatch_guard)); + + let dst_pending = + state + .tracker + .textures + .set_single(dst_texture, dst_range, wgt::TextureUses::COPY_DST); + let dst_barrier = dst_pending + .map(|pending| pending.into_hal(dst_raw)) + .collect::<Vec<_>>(); + handle_buffer_init( state, source, @@ -1255,6 +1259,8 @@ pub(super) fn copy_texture_to_buffer( .check_usage(BufferUsages::COPY_DST) .map_err(TransferError::MissingBufferUsage)?; + // This must happen after parameter validation (so that errors are reported + // as required by the spec), but before any side effects. if copy_size.width == 0 || copy_size.height == 0 || copy_size.depth_or_array_layers == 0 { log::trace!("Ignoring copy_texture_to_buffer of size 0"); return Ok(()); @@ -1376,12 +1382,6 @@ pub(super) fn copy_texture_to_texture( .into()); } - // Handle texture init *before* dealing with barrier transitions so we - // have an easier time inserting "immediate-inits" that may be required - // by prior discards in rare cases. - handle_src_texture_init(state, source, copy_size, src_texture)?; - handle_dst_texture_init(state, destination, copy_size, dst_texture)?; - let src_raw = src_texture.try_raw(state.snatch_guard)?; src_texture .check_usage(TextureUsages::COPY_SRC) @@ -1391,11 +1391,19 @@ pub(super) fn copy_texture_to_texture( .check_usage(TextureUsages::COPY_DST) .map_err(TransferError::MissingTextureUsage)?; + // This must happen after parameter validation (so that errors are reported + // as required by the spec), but before any side effects. if copy_size.width == 0 || copy_size.height == 0 || copy_size.depth_or_array_layers == 0 { log::trace!("Ignoring copy_texture_to_texture of size 0"); return Ok(()); } + // Handle texture init *before* dealing with barrier transitions so we + // have an easier time inserting "immediate-inits" that may be required + // by prior discards in rare cases. + handle_src_texture_init(state, source, copy_size, src_texture)?; + handle_dst_texture_init(state, destination, copy_size, dst_texture)?; + let src_pending = state .tracker diff --git a/third_party/rust/wgpu-core/src/conv.rs b/third_party/rust/wgpu-core/src/conv.rs @@ -199,6 +199,17 @@ pub fn map_texture_usage_from_hal(uses: wgt::TextureUses) -> wgt::TextureUsages u } +/// Check the requested texture size against the supported limits. +/// +/// This function implements the texture size and sample count checks in [vtd +/// dimension step]. The format checks are elsewhere in [`create_texture`]`. +/// +/// Note that while there is some basic checking of the sample count here, there +/// is an additional set of checks when `sample_count > 1` elsewhere in +/// [`create_texture`]`. +/// +/// [vtd dimension step]: https://www.w3.org/TR/2025/CRD-webgpu-20251120/#:~:text=or%204.-,If%20descriptor.dimension%20is +/// [`create_texture`]: crate::device::Device::create_texture pub fn check_texture_dimension_size( dimension: wgt::TextureDimension, wgt::Extent3d { diff --git a/third_party/rust/wgpu-core/src/device/global.rs b/third_party/rust/wgpu-core/src/device/global.rs @@ -215,6 +215,23 @@ impl Global { fid.assign(Fallible::Invalid(Arc::new(desc.label.to_string()))); } + /// Assign `id_in` an error with the given `label`. + /// + /// In JavaScript environments, it is possible to call `GPUDevice.createBindGroupLayout` with + /// entries that are invalid. Because our Rust's types for bind group layouts prevent even + /// calling [`Self::device_create_bind_group`], we let standards-compliant environments + /// register an invalid bind group layout so this crate's API can still be consistently used. + /// + /// See [`Self::create_buffer_error`] for additional context and explanation. + pub fn create_bind_group_layout_error( + &self, + id_in: Option<id::BindGroupLayoutId>, + label: Option<Cow<'_, str>>, + ) { + let fid = self.hub.bind_group_layouts.prepare(id_in); + fid.assign(Fallible::Invalid(Arc::new(label.to_string()))); + } + pub fn buffer_destroy(&self, buffer_id: id::BufferId) { profiling::scope!("Buffer::destroy"); api_log!("Buffer::destroy {buffer_id:?}"); @@ -722,7 +739,7 @@ impl Global { let desc = binding_model::ResolvedPipelineLayoutDescriptor { label: desc.label.clone(), bind_group_layouts: Cow::Owned(bind_group_layouts), - push_constant_ranges: desc.push_constant_ranges.clone(), + immediate_size: desc.immediate_size, }; let layout = match device.create_pipeline_layout(&desc) { diff --git a/third_party/rust/wgpu-core/src/device/mod.rs b/third_party/rust/wgpu-core/src/device/mod.rs @@ -400,8 +400,8 @@ pub fn create_validator( use naga::valid::Capabilities as Caps; let mut caps = Caps::empty(); caps.set( - Caps::PUSH_CONSTANT, - features.contains(wgt::Features::PUSH_CONSTANTS), + Caps::IMMEDIATES, + features.contains(wgt::Features::IMMEDIATES), ); caps.set(Caps::FLOAT64, features.contains(wgt::Features::SHADER_F64)); caps.set( @@ -417,21 +417,38 @@ pub fn create_validator( features.contains(wgt::Features::SHADER_PRIMITIVE_INDEX), ); caps.set( - Caps::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, + Caps::TEXTURE_AND_SAMPLER_BINDING_ARRAY, + features.contains(wgt::Features::TEXTURE_BINDING_ARRAY), + ); + caps.set( + Caps::BUFFER_BINDING_ARRAY, + features.contains(wgt::Features::BUFFER_BINDING_ARRAY), + ); + caps.set( + Caps::STORAGE_TEXTURE_BINDING_ARRAY, + features.contains(wgt::Features::TEXTURE_BINDING_ARRAY) + && features.contains(wgt::Features::STORAGE_RESOURCE_BINDING_ARRAY), + ); + caps.set( + Caps::STORAGE_BUFFER_BINDING_ARRAY, + features.contains(wgt::Features::BUFFER_BINDING_ARRAY) + && features.contains(wgt::Features::STORAGE_RESOURCE_BINDING_ARRAY), + ); + caps.set( + Caps::TEXTURE_AND_SAMPLER_BINDING_ARRAY_NON_UNIFORM_INDEXING, features .contains(wgt::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING), ); caps.set( - Caps::STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING, - features.contains(wgt::Features::STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING), + Caps::BUFFER_BINDING_ARRAY_NON_UNIFORM_INDEXING, + features.contains(wgt::Features::UNIFORM_BUFFER_BINDING_ARRAYS), ); caps.set( - Caps::UNIFORM_BUFFER_ARRAY_NON_UNIFORM_INDEXING, - features.contains(wgt::Features::UNIFORM_BUFFER_BINDING_ARRAYS), + Caps::STORAGE_TEXTURE_BINDING_ARRAY_NON_UNIFORM_INDEXING, + features.contains(wgt::Features::STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING), ); - // TODO: This needs a proper wgpu feature caps.set( - Caps::SAMPLER_NON_UNIFORM_INDEXING, + Caps::STORAGE_BUFFER_BINDING_ARRAY_NON_UNIFORM_INDEXING, features .contains(wgt::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING), ); diff --git a/third_party/rust/wgpu-core/src/device/queue.rs b/third_party/rust/wgpu-core/src/device/queue.rs @@ -537,6 +537,8 @@ impl Queue { let data_size = if let Some(data_size) = wgt::BufferSize::new(data_size) { data_size } else { + // This must happen after parameter validation (so that errors are reported + // as required by the spec), but before any side effects. log::trace!("Ignoring write_buffer of size 0"); return Ok(()); }; @@ -791,6 +793,8 @@ impl Queue { let dst_raw = dst.try_raw(&snatch_guard)?; + // This must happen after parameter validation (so that errors are reported + // as required by the spec), but before any side effects. if size.width == 0 || size.height == 0 || size.depth_or_array_layers == 0 { log::trace!("Ignoring write_texture of size 0"); return Ok(()); @@ -963,11 +967,6 @@ impl Queue { self.device.check_is_valid()?; - if size.width == 0 || size.height == 0 || size.depth_or_array_layers == 0 { - log::trace!("Ignoring write_texture of size 0"); - return Ok(()); - } - let mut needs_flag = false; needs_flag |= matches!(source.source, wgt::ExternalImageSource::OffscreenCanvas(_)); needs_flag |= source.origin != wgt::Origin2d::ZERO; @@ -1051,6 +1050,13 @@ impl Queue { let (selector, dst_base) = extract_texture_selector(&destination, &size, &dst)?; + // This must happen after parameter validation (so that errors are reported + // as required by the spec), but before any side effects. + if size.width == 0 || size.height == 0 || size.depth_or_array_layers == 0 { + log::trace!("Ignoring copy_external_image_to_texture of size 0"); + return Ok(()); + } + let mut pending_writes = self.pending_writes.lock(); let encoder = pending_writes.activate(); diff --git a/third_party/rust/wgpu-core/src/device/resource.rs b/third_party/rust/wgpu-core/src/device/resource.rs @@ -52,7 +52,7 @@ use crate::{ snatch::{SnatchGuard, SnatchLock, Snatchable}, timestamp_normalization::TIMESTAMP_NORMALIZATION_BUFFER_USES, track::{BindGroupStates, DeviceTracker, TrackerIndexAllocators, UsageScope, UsageScopePool}, - validation::{self, validate_color_attachment_bytes_per_sample}, + validation, weak_vec::WeakVec, FastHashMap, LabelHelpers, OnceCellOrLock, }; @@ -433,14 +433,17 @@ impl Device { } .map_err(DeviceError::from_hal)?; + // Cloned as we need them below anyway. let alignments = adapter.raw.capabilities.alignments.clone(); let downlevel = adapter.raw.capabilities.downlevel.clone(); + let limits = &adapter.raw.capabilities.limits; let enable_indirect_validation = instance_flags .contains(wgt::InstanceFlags::VALIDATION_INDIRECT_CALL) - && downlevel - .flags - .contains(wgt::DownlevelFlags::INDIRECT_EXECUTION); + && downlevel.flags.contains( + wgt::DownlevelFlags::INDIRECT_EXECUTION | wgt::DownlevelFlags::COMPUTE_SHADERS, + ) + && limits.max_storage_buffers_per_shader_stage >= 2; let indirect_validation = if enable_indirect_validation { Some(crate::indirect_validation::IndirectValidation::new( @@ -851,7 +854,20 @@ impl Device { user_closures.mappings, user_closures.blas_compact_ready, queue_empty, - ) = queue_result + ) = queue_result; + // DEADLOCK PREVENTION: We must drop `snatch_guard` before `queue` goes out of scope. + // + // `Queue::drop` acquires the snatch guard. If we still hold it when `queue` is dropped + // at the end of this block, we would deadlock. This can happen in the following scenario: + // + // - Thread A calls `Device::maintain` while Thread B holds the last strong ref to the queue. + // - Thread A calls `self.get_queue()`, obtaining a new strong ref, and enters this branch. + // - Thread B drops its strong ref, making Thread A's ref the last one. + // - When `queue` goes out of scope here, `Queue::drop` runs and tries to acquire the + // snatch guard — but Thread A (this thread) still holds it, causing a deadlock. + drop(snatch_guard); + } else { + drop(snatch_guard); }; // Based on the queue empty status, and the current finished submission index, determine the result of the poll. @@ -906,7 +922,6 @@ impl Device { // Don't hold the locks while calling release_gpu_resources. drop(fence); - drop(snatch_guard); if should_release_gpu_resource { self.release_gpu_resources(); @@ -1424,6 +1439,11 @@ impl Device { .map_err(|error| CreateTextureError::MissingFeatures(desc.format, error))?; if desc.sample_count > 1 { + // <https://www.w3.org/TR/2025/CRD-webgpu-20251120/#:~:text=If%20descriptor%2EsampleCount%20%3E%201> + // + // Note that there are also some checks related to the sample count + // in [`conv::check_texture_dimension_size`]. + if desc.mip_level_count != 1 { return Err(CreateTextureError::InvalidMipLevelCount { requested: desc.mip_level_count, @@ -1431,7 +1451,9 @@ impl Device { }); } - if desc.size.depth_or_array_layers != 1 { + if desc.size.depth_or_array_layers != 1 + && !self.features.contains(wgt::Features::MULTISAMPLE_ARRAY) + { return Err(CreateTextureError::InvalidDimension( TextureDimensionError::MultisampledDepthOrArrayLayer( desc.size.depth_or_array_layers, @@ -1736,11 +1758,17 @@ impl Device { // check if multisampled texture is seen as anything but 2D if texture.desc.sample_count > 1 && resolved_dimension != TextureViewDimension::D2 { - return Err( - resource::CreateTextureViewError::InvalidMultisampledTextureViewDimension( - resolved_dimension, - ), - ); + // Multisample is allowed on 2D arrays, only if explicitly supported + let multisample_array_exception = resolved_dimension == TextureViewDimension::D2Array + && self.features.contains(wgt::Features::MULTISAMPLE_ARRAY); + + if !multisample_array_exception { + return Err( + resource::CreateTextureViewError::InvalidMultisampledTextureViewDimension( + resolved_dimension, + ), + ); + } } // check if the dimension is compatible with the texture @@ -2301,9 +2329,6 @@ impl Device { self.check_is_valid()?; self.require_features(wgt::Features::EXPERIMENTAL_PASSTHROUGH_SHADERS)?; - // TODO: when we get to use if-let chains, this will be a little nicer! - - log::info!("Backend: {}", self.backend()); let hal_shader = match self.backend() { wgt::Backend::Vulkan => hal::ShaderInput::SpirV( descriptor @@ -2769,18 +2794,27 @@ impl Device { buffer.check_usage(pub_usage)?; - let (bb, bind_size) = buffer.binding(bb.offset, bb.size, snatch_guard)?; - - if matches!(binding_ty, wgt::BufferBindingType::Storage { .. }) { - let storage_buf_size_alignment = 4; - - let aligned = bind_size % u64::from(storage_buf_size_alignment) == 0; - if !aligned { - return Err(Error::UnalignedEffectiveBufferBindingSizeForStorage { - alignment: storage_buf_size_alignment, - size: bind_size, - }); + let req_size = match bb.size.map(wgt::BufferSize::new) { + // Requested a non-zero size + Some(non_zero @ Some(_)) => non_zero, + // Requested size not specified + None => None, + // Requested zero size + Some(None) => { + return Err(binding_model::CreateBindGroupError::BindingZeroSize( + buffer.error_ident(), + )) } + }; + let (bb, bind_size) = buffer.binding(bb.offset, req_size, snatch_guard)?; + + if matches!(binding_ty, wgt::BufferBindingType::Storage { .. }) + && bind_size % u64::from(wgt::STORAGE_BINDING_SIZE_ALIGNMENT) != 0 + { + return Err(Error::UnalignedEffectiveBufferBindingSizeForStorage { + alignment: wgt::STORAGE_BINDING_SIZE_ALIGNMENT, + size: bind_size, + }); } let bind_end = bb.offset + bind_size; @@ -3557,42 +3591,19 @@ impl Device { }); } - if !desc.push_constant_ranges.is_empty() { - self.require_features(wgt::Features::PUSH_CONSTANTS)?; + if desc.immediate_size != 0 { + self.require_features(wgt::Features::IMMEDIATES)?; } - - let mut used_stages = wgt::ShaderStages::empty(); - for (index, pc) in desc.push_constant_ranges.iter().enumerate() { - if pc.stages.intersects(used_stages) { - return Err(Error::MoreThanOnePushConstantRangePerStage { - index, - provided: pc.stages, - intersected: pc.stages & used_stages, - }); - } - used_stages |= pc.stages; - - let device_max_pc_size = self.limits.max_push_constant_size; - if device_max_pc_size < pc.range.end { - return Err(Error::PushConstantRangeTooLarge { - index, - range: pc.range.clone(), - max: device_max_pc_size, - }); - } - - if pc.range.start % wgt::PUSH_CONSTANT_ALIGNMENT != 0 { - return Err(Error::MisalignedPushConstantRange { - index, - bound: pc.range.start, - }); - } - if pc.range.end % wgt::PUSH_CONSTANT_ALIGNMENT != 0 { - return Err(Error::MisalignedPushConstantRange { - index, - bound: pc.range.end, - }); - } + if self.limits.max_immediate_size < desc.immediate_size { + return Err(Error::ImmediateRangeTooLarge { + size: desc.immediate_size, + max: self.limits.max_immediate_size, + }); + } + if desc.immediate_size % wgt::IMMEDIATE_DATA_ALIGNMENT != 0 { + return Err(Error::MisalignedImmediateSize { + size: desc.immediate_size, + }); } let mut count_validator = binding_model::BindingTypeMaxCountValidator::default(); @@ -3630,7 +3641,7 @@ impl Device { | hal::PipelineLayoutFlags::NUM_WORK_GROUPS | additional_flags, bind_group_layouts: &raw_bind_group_layouts, - push_constant_ranges: desc.push_constant_ranges.as_ref(), + immediate_size: desc.immediate_size, }; let raw = unsafe { self.raw().create_pipeline_layout(&hal_desc) } @@ -3643,7 +3654,7 @@ impl Device { device: self.clone(), label: desc.label.to_string(), bind_group_layouts, - push_constant_ranges: desc.push_constant_ranges.iter().cloned().collect(), + immediate_size: desc.immediate_size, }; let layout = Arc::new(layout); @@ -3690,7 +3701,7 @@ impl Device { let layout_desc = binding_model::ResolvedPipelineLayoutDescriptor { label: None, bind_group_layouts: Cow::Owned(bind_group_layouts), - push_constant_ranges: Cow::Borrowed(&[]), //TODO? + immediate_size: 0, //TODO? }; let layout = self.create_pipeline_layout(&layout_desc)?; @@ -3972,7 +3983,7 @@ impl Device { self.require_features(wgt::Features::VERTEX_ATTRIBUTE_64BIT)?; } - let previous = io.insert( + let previous = io.varyings.insert( attribute.shader_location, validation::InterfaceVar::vertex_attribute(attribute.format), ); @@ -4121,15 +4132,11 @@ impl Device { } } - let limit = self.limits.max_color_attachment_bytes_per_sample; - let formats = color_targets - .iter() - .map(|cs| cs.as_ref().map(|cs| cs.format)); - if let Err(total) = validate_color_attachment_bytes_per_sample(formats, limit) { - return Err(pipeline::CreateRenderPipelineError::ColorAttachment( - command::ColorAttachmentError::TooManyBytesPerSample { total, limit }, - )); - } + validation::validate_color_attachment_bytes_per_sample( + color_targets.iter().flatten().map(|cs| cs.format), + self.limits.max_color_attachment_bytes_per_sample, + ) + .map_err(pipeline::CreateRenderPipelineError::ColorAttachment)?; if let Some(ds) = depth_stencil_state { target_specified = true; @@ -4363,20 +4370,18 @@ impl Device { ) .map_err(stage_err)?; - if validated_stages == wgt::ShaderStages::VERTEX { - if let Some(ref interface) = shader_module.interface { - io = interface - .check_stage( - &mut binding_layout_source, - &mut shader_binding_sizes, - &fragment_entry_point_name, - stage, - io, - desc.depth_stencil.as_ref().map(|d| d.depth_compare), - ) - .map_err(stage_err)?; - validated_stages |= stage; - } + if let Some(ref interface) = shader_module.interface { + io = interface + .check_stage( + &mut binding_layout_source, + &mut shader_binding_sizes, + &fragment_entry_point_name, + stage, + io, + desc.depth_stencil.as_ref().map(|d| d.depth_compare), + ) + .map_err(stage_err)?; + validated_stages |= stage; } if let Some(ref interface) = shader_module.interface { @@ -4412,7 +4417,7 @@ impl Device { } if validated_stages.contains(wgt::ShaderStages::FRAGMENT) { - for (i, output) in io.iter() { + for (i, output) in io.varyings.iter() { match color_targets.get(*i as usize) { Some(Some(state)) => { validation::check_texture_format(state.format, &output.ty).map_err( @@ -4428,7 +4433,7 @@ impl Device { )?; } _ => { - log::warn!( + log::debug!( "The fragment stage {:?} output @location({}) values are ignored", fragment_stage .as_ref() diff --git a/third_party/rust/wgpu-core/src/device/trace/record.rs b/third_party/rust/wgpu-core/src/device/trace/record.rs @@ -53,7 +53,7 @@ pub struct Trace { impl Trace { pub fn new(path: std::path::PathBuf) -> Result<Self, std::io::Error> { - log::info!("Tracing into '{path:?}'"); + log::debug!("Tracing into '{path:?}'"); let mut file = std::fs::File::create(path.join(FILE_NAME))?; file.write_all(b"[\n")?; Ok(Self { @@ -363,7 +363,7 @@ impl<C: IntoTrace> IntoTrace for BasePass<C, Infallible> { .collect(), dynamic_offsets: self.dynamic_offsets, string_data: self.string_data, - push_constant_data: self.push_constant_data, + immediates_data: self.immediates_data, } } } @@ -383,11 +383,11 @@ impl IntoTrace for ArcComputeCommand { bind_group: bind_group.map(|bg| bg.into_trace()), }, C::SetPipeline(id) => C::SetPipeline(id.into_trace()), - C::SetPushConstant { + C::SetImmediate { offset, size_bytes, values_offset, - } => C::SetPushConstant { + } => C::SetImmediate { offset, size_bytes, values_offset, @@ -468,13 +468,11 @@ impl IntoTrace for ArcRenderCommand { depth_max, }, C::SetScissor(rect) => C::SetScissor(rect), - C::SetPushConstant { - stages, + C::SetImmediate { offset, size_bytes, values_offset, - } => C::SetPushConstant { - stages, + } => C::SetImmediate { offset, size_bytes, values_offset, @@ -578,7 +576,7 @@ impl<'a> IntoTrace for crate::binding_model::ResolvedPipelineLayoutDescriptor<'a .iter() .map(|bgl| bgl.to_trace()) .collect(), - push_constant_ranges: self.push_constant_ranges.clone(), + immediate_size: self.immediate_size, } } } diff --git a/third_party/rust/wgpu-core/src/global.rs b/third_party/rust/wgpu-core/src/global.rs @@ -31,10 +31,14 @@ pub struct Global { } impl Global { - pub fn new(name: &str, instance_desc: &wgt::InstanceDescriptor) -> Self { + pub fn new( + name: &str, + instance_desc: &wgt::InstanceDescriptor, + telemetry: Option<hal::Telemetry>, + ) -> Self { profiling::scope!("Global::new"); Self { - instance: Instance::new(name, instance_desc), + instance: Instance::new(name, instance_desc, telemetry), surfaces: Registry::new(), hub: Hub::new(), } diff --git a/third_party/rust/wgpu-core/src/indirect_validation/dispatch.rs b/third_party/rust/wgpu-core/src/indirect_validation/dispatch.rs @@ -12,7 +12,7 @@ use core::num::NonZeroU64; /// - max_dynamic_storage_buffers_per_pipeline_layout: 1, /// - max_storage_buffers_per_shader_stage: 2, /// - max_storage_buffer_binding_size: 3 * min_storage_buffer_offset_alignment, -/// - max_push_constant_size: 4, +/// - max_immediate_size: 4, /// - max_compute_invocations_per_workgroup 1 /// /// These are all indirectly satisfied by `DownlevelFlags::INDIRECT_EXECUTION`, which is also @@ -53,7 +53,7 @@ impl Dispatch { struct OffsetPc {{ inner: u32, }} - var<push_constant> offset: OffsetPc; + var<immediate> offset: OffsetPc; @compute @workgroup_size(1) fn main() {{ @@ -96,7 +96,7 @@ impl Dispatch { let module = panic!("Indirect validation requires the wgsl feature flag to be enabled!"); let info = crate::device::create_validator( - wgt::Features::PUSH_CONSTANTS, + wgt::Features::IMMEDIATES, wgt::DownlevelFlags::empty(), naga::valid::ValidationFlags::all(), ) @@ -177,10 +177,7 @@ impl Dispatch { dst_bind_group_layout.as_ref(), src_bind_group_layout.as_ref(), ], - push_constant_ranges: &[wgt::PushConstantRange { - stages: wgt::ShaderStages::COMPUTE, - range: 0..4, - }], + immediate_size: 4, }; let pipeline_layout = unsafe { device @@ -302,10 +299,10 @@ impl Dispatch { // min_storage_buffer_offset_alignment (256 bytes by default). // // So, we work around this limitation by calculating an aligned offset - // and pass the remainder through a push constant. + // and pass the remainder through a immediate data. // // We could bind the whole buffer and only have to pass the offset - // through a push constant but we might run into the + // through a immediate data but we might run into the // max_storage_buffer_binding_size limit. // // See the inner docs of `calculate_src_buffer_binding_size` to diff --git a/third_party/rust/wgpu-core/src/indirect_validation/draw.rs b/third_party/rust/wgpu-core/src/indirect_validation/draw.rs @@ -3,10 +3,11 @@ use super::{ CreateIndirectValidationPipelineError, }; use crate::{ + command::RenderPassErrorInner, device::{queue::TempResource, Device, DeviceError}, lock::{rank, Mutex}, pipeline::{CreateComputePipelineError, CreateShaderModuleError}, - resource::{StagingBuffer, Trackable}, + resource::{RawResourceAccess as _, StagingBuffer, Trackable}, snatch::SnatchGuard, track::TrackerIndex, FastHashMap, @@ -41,7 +42,7 @@ const BUFFER_SIZE: wgt::BufferSize = unsafe { wgt::BufferSize::new_unchecked(1_0 /// - max_bind_groups: 3, /// - max_dynamic_storage_buffers_per_pipeline_layout: 1, /// - max_storage_buffers_per_shader_stage: 3, -/// - max_push_constant_size: 8, +/// - max_immediate_size: 8, /// /// These are all indirectly satisfied by `DownlevelFlags::INDIRECT_EXECUTION`, which is also /// required for this module's functionality to work. @@ -80,10 +81,7 @@ impl Draw { src_bind_group_layout.as_ref(), dst_bind_group_layout.as_ref(), ], - push_constant_ranges: &[wgt::PushConstantRange { - stages: wgt::ShaderStages::COMPUTE, - range: 0..8, - }], + immediate_size: 8, }; let pipeline_layout = unsafe { device @@ -199,7 +197,7 @@ impl Draw { temp_resources: &mut Vec<TempResource>, encoder: &mut dyn hal::DynCommandEncoder, batcher: DrawBatcher, - ) -> Result<(), DeviceError> { + ) -> Result<(), RenderPassErrorInner> { let mut batches = batcher.batches; if batches.is_empty() { @@ -364,12 +362,7 @@ impl Draw { (batch.metadata_buffer_offset / size_of::<MetadataEntry>() as u64) as u32; let metadata_count = batch.entries.len() as u32; unsafe { - encoder.set_push_constants( - pipeline_layout, - wgt::ShaderStages::COMPUTE, - 0, - &[metadata_start, metadata_count], - ); + encoder.set_immediates(pipeline_layout, 0, &[metadata_start, metadata_count]); } let metadata_bind_group = @@ -378,6 +371,9 @@ impl Draw { encoder.set_bind_group(pipeline_layout, 0, Some(metadata_bind_group), &[]); } + // Make sure the indirect buffer is still valid. + batch.src_buffer.try_raw(snatch_guard)?; + let src_bind_group = batch .src_buffer .indirect_validation_bind_groups @@ -483,7 +479,7 @@ fn create_validation_module( let module = panic!("Indirect validation requires the wgsl feature flag to be enabled!"); let info = crate::device::create_validator( - wgt::Features::PUSH_CONSTANTS, + wgt::Features::IMMEDIATES, wgt::DownlevelFlags::empty(), naga::valid::ValidationFlags::all(), ) diff --git a/third_party/rust/wgpu-core/src/indirect_validation/validate_draw.wgsl b/third_party/rust/wgpu-core/src/indirect_validation/validate_draw.wgsl @@ -17,7 +17,7 @@ struct MetadataRange { start: u32, count: u32, } -var<push_constant> metadata_range: MetadataRange; +var<immediate> metadata_range: MetadataRange; @group(0) @binding(0) var<storage, read> metadata: array<MetadataEntry>; diff --git a/third_party/rust/wgpu-core/src/instance.rs b/third_party/rust/wgpu-core/src/instance.rs @@ -92,7 +92,11 @@ pub struct Instance { } impl Instance { - pub fn new(name: &str, instance_desc: &wgt::InstanceDescriptor) -> Self { + pub fn new( + name: &str, + instance_desc: &wgt::InstanceDescriptor, + telemetry: Option<hal::Telemetry>, + ) -> Self { let mut this = Self { name: name.to_owned(), instance_per_backend: Vec::new(), @@ -102,21 +106,26 @@ impl Instance { }; #[cfg(vulkan)] - this.try_add_hal(hal::api::Vulkan, instance_desc); + this.try_add_hal(hal::api::Vulkan, instance_desc, telemetry); #[cfg(metal)] - this.try_add_hal(hal::api::Metal, instance_desc); + this.try_add_hal(hal::api::Metal, instance_desc, telemetry); #[cfg(dx12)] - this.try_add_hal(hal::api::Dx12, instance_desc); + this.try_add_hal(hal::api::Dx12, instance_desc, telemetry); #[cfg(gles)] - this.try_add_hal(hal::api::Gles, instance_desc); + this.try_add_hal(hal::api::Gles, instance_desc, telemetry); #[cfg(feature = "noop")] - this.try_add_hal(hal::api::Noop, instance_desc); + this.try_add_hal(hal::api::Noop, instance_desc, telemetry); this } /// Helper for `Instance::new()`; attempts to add a single `wgpu-hal` backend to this instance. - fn try_add_hal<A: hal::Api>(&mut self, _: A, instance_desc: &wgt::InstanceDescriptor) { + fn try_add_hal<A: hal::Api>( + &mut self, + _: A, + instance_desc: &wgt::InstanceDescriptor, + telemetry: Option<hal::Telemetry>, + ) { // Whether or not the backend was requested, and whether or not it succeeds, // note that we *could* try it. self.supported_backends |= A::VARIANT.into(); @@ -131,6 +140,7 @@ impl Instance { flags: self.flags, memory_budget_thresholds: instance_desc.memory_budget_thresholds, backend_options: instance_desc.backend_options.clone(), + telemetry, }; use hal::Instance as _; diff --git a/third_party/rust/wgpu-core/src/pipeline_cache.rs b/third_party/rust/wgpu-core/src/pipeline_cache.rs @@ -323,6 +323,8 @@ mod tests { driver: String::new(), driver_info: String::new(), backend: wgt::Backend::Vulkan, + subgroup_min_size: 32, + subgroup_max_size: 32, transient_saves_memory: true, }; diff --git a/third_party/rust/wgpu-core/src/present.rs b/third_party/rust/wgpu-core/src/present.rs @@ -293,7 +293,11 @@ impl Surface { .take() .ok_or(SurfaceError::AlreadyAcquired)?; - let result = match texture.inner.snatch(&mut device.snatchable_lock.write()) { + let mut exclusive_snatch_guard = device.snatchable_lock.write(); + let inner = texture.inner.snatch(&mut exclusive_snatch_guard); + drop(exclusive_snatch_guard); + + let result = match inner { None => return Err(SurfaceError::TextureDestroyed), Some(resource::TextureInner::Surface { raw }) => { let raw_surface = self.raw(device.backend()).unwrap(); @@ -337,7 +341,11 @@ impl Surface { .take() .ok_or(SurfaceError::AlreadyAcquired)?; - match texture.inner.snatch(&mut device.snatchable_lock.write()) { + let mut exclusive_snatch_guard = device.snatchable_lock.write(); + let inner = texture.inner.snatch(&mut exclusive_snatch_guard); + drop(exclusive_snatch_guard); + + match inner { None => return Err(SurfaceError::TextureDestroyed), Some(resource::TextureInner::Surface { raw }) => { let raw_surface = self.raw(device.backend()).unwrap(); diff --git a/third_party/rust/wgpu-core/src/resource.rs b/third_party/rust/wgpu-core/src/resource.rs @@ -875,7 +875,6 @@ impl Buffer { range, host, } => { - #[cfg_attr(not(feature = "trace"), expect(clippy::collapsible_if))] if host == HostMap::Write { #[cfg(feature = "trace")] if let Some(ref mut trace) = *device.trace.lock() { diff --git a/third_party/rust/wgpu-core/src/timestamp_normalization/mod.rs b/third_party/rust/wgpu-core/src/timestamp_normalization/mod.rs @@ -32,7 +32,6 @@ use core::num::NonZeroU64; use alloc::{boxed::Box, string::String, string::ToString, sync::Arc}; use hashbrown::HashMap; -use wgt::PushConstantRange; use crate::{ device::{Device, DeviceError}, @@ -152,7 +151,7 @@ impl TimestampNormalizer { panic!("Timestamp normalization requires the wgsl feature flag to be enabled!"); let info = crate::device::create_validator( - wgt::Features::PUSH_CONSTANTS, + wgt::Features::IMMEDIATES, wgt::DownlevelFlags::empty(), naga::valid::ValidationFlags::all(), ) @@ -191,10 +190,7 @@ impl TimestampNormalizer { .create_pipeline_layout(&hal::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[temporary_bind_group_layout.as_ref()], - push_constant_ranges: &[PushConstantRange { - stages: wgt::ShaderStages::COMPUTE, - range: 0..8, - }], + immediate_size: 8, flags: hal::PipelineLayoutFlags::empty(), }) .map_err(|e| { @@ -343,9 +339,8 @@ impl TimestampNormalizer { }); encoder.set_compute_pipeline(&*state.pipeline); encoder.set_bind_group(&*state.pipeline_layout, 0, Some(bind_group), &[]); - encoder.set_push_constants( + encoder.set_immediates( &*state.pipeline_layout, - wgt::ShaderStages::COMPUTE, 0, &[buffer_offset_timestamps, total_timestamps], ); diff --git a/third_party/rust/wgpu-core/src/timestamp_normalization/timestamp_normalization.wgsl b/third_party/rust/wgpu-core/src/timestamp_normalization/timestamp_normalization.wgsl @@ -17,20 +17,20 @@ override TIMESTAMP_PERIOD_SHIFT: u32 = 0; @group(0) @binding(0) var<storage, read_write> timestamps: array<Uint64>; -struct PushConstants { +struct ImmediateData { timestamp_offset: u32, timestamp_count: u32, } -var<push_constant> pc: PushConstants; +var<immediate> im: ImmediateData; @compute @workgroup_size(64) fn main(@builtin(global_invocation_id) id: vec3u) { - if id.x >= pc.timestamp_count { + if id.x >= im.timestamp_count { return; } - let index = id.x + pc.timestamp_offset; + let index = id.x + im.timestamp_offset; let input_value = timestamps[index]; diff --git a/third_party/rust/wgpu-core/src/validation.rs b/third_party/rust/wgpu-core/src/validation.rs @@ -129,6 +129,7 @@ pub struct InterfaceVar { pub ty: NumericType, interpolation: Option<naga::Interpolation>, sampling: Option<naga::Sampling>, + per_primitive: bool, } impl InterfaceVar { @@ -137,6 +138,7 @@ impl InterfaceVar { ty: NumericType::from_vertex_format(format), interpolation: None, sampling: None, + per_primitive: false, } } } @@ -164,6 +166,12 @@ struct SpecializationConstant { ty: NumericType, } +#[derive(Debug)] +struct EntryPointMeshInfo { + max_vertices: u32, + max_primitives: u32, +} + #[derive(Debug, Default)] struct EntryPoint { inputs: Vec<Varying>, @@ -174,6 +182,8 @@ struct EntryPoint { sampling_pairs: FastHashSet<(naga::Handle<Resource>, naga::Handle<Resource>)>, workgroup_size: [u32; 3], dual_source_blending: bool, + task_payload_size: Option<u32>, + mesh_info: Option<EntryPointMeshInfo>, } #[derive(Debug)] @@ -260,6 +270,8 @@ pub enum InputError { InterpolationMismatch(Option<naga::Interpolation>), #[error("Input sampling doesn't match provided {0:?}")] SamplingMismatch(Option<naga::Sampling>), + #[error("Pipeline input has per_primitive={pipeline_input}, but shader expects per_primitive={shader}")] + WrongPerPrimitive { pipeline_input: bool, shader: bool }, } impl WebGpuError for InputError { @@ -273,13 +285,16 @@ impl WebGpuError for InputError { #[non_exhaustive] pub enum StageError { #[error( - "Shader entry point's workgroup size {current:?} ({current_total} total invocations) must be less or equal to the per-dimension limit {limit:?} and the total invocation limit {total}" + "Shader entry point's workgroup size {current:?} ({current_total} total invocations) must be less or equal to the per-dimension + limit `Limits::{per_dimension_limit}` of {limit:?} and the total invocation limit `Limits::{total_limit}` of {total}" )] InvalidWorkgroupSize { current: [u32; 3], current_total: u32, limit: [u32; 3], total: u32, + per_dimension_limit: &'static str, + total_limit: &'static str, }, #[error("Shader uses {used} inter-stage components above the limit of {limit}")] TooManyVaryings { used: u32, limit: u32 }, @@ -321,6 +336,23 @@ pub enum StageError { var: InterfaceVar, limit: u32, }, + #[error("Mesh shaders are limited to {limit} output vertices by `Limits::max_mesh_output_vertices`, but the shader has a maximum number of {value}")] + TooManyMeshVertices { limit: u32, value: u32 }, + #[error("Mesh shaders are limited to {limit} output primitives by `Limits::max_mesh_output_primitives`, but the shader has a maximum number of {value}")] + TooManyMeshPrimitives { limit: u32, value: u32 }, + #[error("Mesh or task shaders are limited to {limit} bytes of task payload by `Limits::max_task_payload_size`, but the shader has a task payload of size {value}")] + TaskPayloadTooLarge { limit: u32, value: u32 }, + #[error("Mesh shader's task payload has size ({shader:?}), which doesn't match the payload declared in the task stage ({input:?})")] + TaskPayloadMustMatch { + input: Option<u32>, + shader: Option<u32>, + }, + #[error("Primitive index can only be used in a fragment shader if the preceding shader was a vertex shader or a mesh shader that writes to primitive index.")] + InvalidPrimitiveIndex, + #[error("If a mesh shader writes to primitive index, it must be read by the fragment shader.")] + MissingPrimitiveIndex, + #[error("DrawId cannot be used in the same pipeline as a task shader")] + DrawIdError, } impl WebGpuError for StageError { @@ -343,7 +375,14 @@ impl WebGpuError for StageError { | Self::MissingEntryPoint(..) | Self::NoEntryPointFound | Self::MultipleEntryPointsFound - | Self::ColorAttachmentLocationTooLarge { .. } => return ErrorType::Validation, + | Self::ColorAttachmentLocationTooLarge { .. } + | Self::TooManyMeshVertices { .. } + | Self::TooManyMeshPrimitives { .. } + | Self::TaskPayloadTooLarge { .. } + | Self::TaskPayloadMustMatch { .. } + | Self::InvalidPrimitiveIndex + | Self::MissingPrimitiveIndex + | Self::DrawIdError => return ErrorType::Validation, }; e.webgpu_error_type() } @@ -916,7 +955,18 @@ impl<'a> BindingLayoutSource<'a> { } } -pub type StageIo = FastHashMap<wgt::ShaderLocation, InterfaceVar>; +#[derive(Debug, Clone, Default)] +pub struct StageIo { + pub varyings: FastHashMap<wgt::ShaderLocation, InterfaceVar>, + /// This must match between mesh & task shaders + pub task_payload_size: Option<u32>, + /// Fragment shaders cannot input primitive index on mesh shaders that don't output it on DX12. + /// Therefore, we track between shader stages if primitive index is written (or if vertex shader + /// is used). + /// + /// This is Some if it was a mesh shader. + pub primitive_index: Option<bool>, +} impl Interface { fn populate( @@ -952,8 +1002,9 @@ impl Interface { //Note: technically this should be at least `log::error`, but // the reality is - every shader coming from `glslc` outputs an array // of clip distances and hits this path :( - // So we lower it to `log::warn` to be less annoying. - log::warn!("Unexpected varying type: {other:?}"); + // So we lower it to `log::debug` to be less annoying as + // there's nothing the user can do about it. + log::debug!("Unexpected varying type: {other:?}"); return; } }; @@ -963,13 +1014,15 @@ impl Interface { location, interpolation, sampling, - .. // second_blend_source + per_primitive, + blend_src: _, }) => Varying::Local { location, iv: InterfaceVar { ty: numeric_ty, interpolation, sampling, + per_primitive, }, }, Some(&naga::Binding::BuiltIn(built_in)) => Varying::BuiltIn(built_in), @@ -1057,6 +1110,32 @@ impl Interface { ep.dual_source_blending = info.dual_source_blending; ep.workgroup_size = entry_point.workgroup_size; + if let Some(task_payload) = entry_point.task_payload { + ep.task_payload_size = Some( + module.types[module.global_variables[task_payload].ty] + .inner + .size(module.to_ctx()), + ); + } + if let Some(ref mesh_info) = entry_point.mesh_info { + ep.mesh_info = Some(EntryPointMeshInfo { + max_vertices: mesh_info.max_vertices, + max_primitives: mesh_info.max_primitives, + }); + Self::populate( + &mut ep.outputs, + None, + mesh_info.vertex_output_type, + &module.types, + ); + Self::populate( + &mut ep.outputs, + None, + mesh_info.primitive_output_type, + &module.types, + ); + } + entry_points.insert((entry_point.stage, entry_point.name.clone()), ep); } @@ -1117,7 +1196,7 @@ impl Interface { Some(some) => some, None => return Err(StageError::MissingEntryPoint(pair.1)), }; - let (_stage, entry_point_name) = pair; + let (_, entry_point_name) = pair; // check resources visibility for &handle in entry_point.resources.iter() { @@ -1241,47 +1320,85 @@ impl Interface { // check workgroup size limits if shader_stage.compute_like() { - let max_workgroup_size_limits = [ - self.limits.max_compute_workgroup_size_x, - self.limits.max_compute_workgroup_size_y, - self.limits.max_compute_workgroup_size_z, - ]; + let ( + max_workgroup_size_limits, + max_workgroup_size_total, + per_dimension_limit, + total_limit, + ) = match shader_stage { + naga::ShaderStage::Compute => ( + [ + self.limits.max_compute_workgroup_size_x, + self.limits.max_compute_workgroup_size_y, + self.limits.max_compute_workgroup_size_z, + ], + self.limits.max_compute_invocations_per_workgroup, + "max_compute_workgroup_size_*", + "max_compute_invocations_per_workgroup", + ), + naga::ShaderStage::Task => ( + [ + self.limits.max_task_invocations_per_dimension, + self.limits.max_task_invocations_per_dimension, + self.limits.max_task_invocations_per_dimension, + ], + self.limits.max_task_invocations_per_workgroup, + "max_task_invocations_per_dimension", + "max_task_invocations_per_workgroup", + ), + naga::ShaderStage::Mesh => ( + [ + self.limits.max_mesh_invocations_per_dimension, + self.limits.max_mesh_invocations_per_dimension, + self.limits.max_mesh_invocations_per_dimension, + ], + self.limits.max_mesh_invocations_per_workgroup, + "max_mesh_invocations_per_dimension", + "max_mesh_invocations_per_workgroup", + ), + _ => unreachable!(), + }; let total_invocations = entry_point.workgroup_size.iter().product::<u32>(); - if entry_point.workgroup_size.contains(&0) - || total_invocations > self.limits.max_compute_invocations_per_workgroup - || entry_point.workgroup_size[0] > max_workgroup_size_limits[0] + let workgroup_size_is_zero = entry_point.workgroup_size.contains(&0); + let too_many_invocations = total_invocations > max_workgroup_size_total; + let dimension_too_large = entry_point.workgroup_size[0] > max_workgroup_size_limits[0] || entry_point.workgroup_size[1] > max_workgroup_size_limits[1] - || entry_point.workgroup_size[2] > max_workgroup_size_limits[2] - { + || entry_point.workgroup_size[2] > max_workgroup_size_limits[2]; + if workgroup_size_is_zero || too_many_invocations || dimension_too_large { return Err(StageError::InvalidWorkgroupSize { current: entry_point.workgroup_size, current_total: total_invocations, limit: max_workgroup_size_limits, - total: self.limits.max_compute_invocations_per_workgroup, + total: max_workgroup_size_total, + per_dimension_limit, + total_limit, }); } } let mut inter_stage_components = 0; + let mut this_stage_primitive_index = false; + let mut has_draw_id = false; // check inputs compatibility for input in entry_point.inputs.iter() { match *input { Varying::Local { location, ref iv } => { - let result = - inputs - .get(&location) - .ok_or(InputError::Missing) - .and_then(|provided| { - let (compatible, num_components) = match shader_stage { + let result = inputs + .varyings + .get(&location) + .ok_or(InputError::Missing) + .and_then(|provided| { + let (compatible, num_components, per_primitive_correct) = + match shader_stage { // For vertex attributes, there are defaults filled out // by the driver if data is not provided. naga::ShaderStage::Vertex => { let is_compatible = iv.ty.scalar.kind == provided.ty.scalar.kind; // vertex inputs don't count towards inter-stage - (is_compatible, 0) + (is_compatible, 0, !iv.per_primitive) } naga::ShaderStage::Fragment => { if iv.interpolation != provided.interpolation { @@ -1297,20 +1414,24 @@ impl Interface { ( iv.ty.is_subtype_of(&provided.ty), iv.ty.dim.num_components(), + iv.per_primitive == provided.per_primitive, ) } - naga::ShaderStage::Compute => (false, 0), - // TODO: add validation for these, see https://github.com/gfx-rs/wgpu/issues/8003 - naga::ShaderStage::Task | naga::ShaderStage::Mesh => { - unreachable!() - } + // These can't have varying inputs + naga::ShaderStage::Compute + | naga::ShaderStage::Task + | naga::ShaderStage::Mesh => (false, 0, false), }; - if compatible { - Ok(num_components) - } else { - Err(InputError::WrongType(provided.ty)) - } - }); + if !compatible { + return Err(InputError::WrongType(provided.ty)); + } else if !per_primitive_correct { + return Err(InputError::WrongPerPrimitive { + pipeline_input: provided.per_primitive, + shader: iv.per_primitive, + }); + } + Ok(num_components) + }); match result { Ok(num_components) => { inter_stage_components += num_components; @@ -1324,6 +1445,12 @@ impl Interface { } } } + Varying::BuiltIn(naga::BuiltIn::PrimitiveIndex) => { + this_stage_primitive_index = true; + } + Varying::BuiltIn(naga::BuiltIn::DrawID) => { + has_draw_id = true; + } Varying::BuiltIn(_) => {} } } @@ -1386,6 +1513,56 @@ impl Interface { }); } + if let Some(ref mesh_info) = entry_point.mesh_info { + if mesh_info.max_vertices > self.limits.max_mesh_output_vertices { + return Err(StageError::TooManyMeshVertices { + limit: self.limits.max_mesh_output_vertices, + value: mesh_info.max_vertices, + }); + } + if mesh_info.max_primitives > self.limits.max_mesh_output_primitives { + return Err(StageError::TooManyMeshPrimitives { + limit: self.limits.max_mesh_output_primitives, + value: mesh_info.max_primitives, + }); + } + } + if let Some(task_payload_size) = entry_point.task_payload_size { + if task_payload_size > self.limits.max_task_payload_size { + return Err(StageError::TaskPayloadTooLarge { + limit: self.limits.max_task_payload_size, + value: task_payload_size, + }); + } + } + if shader_stage == naga::ShaderStage::Mesh + && entry_point.task_payload_size != inputs.task_payload_size + { + return Err(StageError::TaskPayloadMustMatch { + input: inputs.task_payload_size, + shader: entry_point.task_payload_size, + }); + } + + // Fragment shader primitive index is treated like a varying + if shader_stage == naga::ShaderStage::Fragment + && this_stage_primitive_index + && inputs.primitive_index == Some(false) + { + return Err(StageError::InvalidPrimitiveIndex); + } else if shader_stage == naga::ShaderStage::Fragment + && !this_stage_primitive_index + && inputs.primitive_index == Some(true) + { + return Err(StageError::MissingPrimitiveIndex); + } + if shader_stage == naga::ShaderStage::Mesh + && inputs.task_payload_size.is_some() + && has_draw_id + { + return Err(StageError::DrawIdError); + } + let outputs = entry_point .outputs .iter() @@ -1395,7 +1572,15 @@ impl Interface { }) .collect(); - Ok(outputs) + Ok(StageIo { + task_payload_size: entry_point.task_payload_size, + varyings: outputs, + primitive_index: if shader_stage == naga::ShaderStage::Mesh { + Some(this_stage_primitive_index) + } else { + None + }, + }) } pub fn fragment_uses_dual_source_blending( @@ -1410,17 +1595,17 @@ impl Interface { } } -// https://gpuweb.github.io/gpuweb/#abstract-opdef-calculating-color-attachment-bytes-per-sample +/// Validate a list of color attachment formats against `maxColorAttachmentBytesPerSample`. +/// +/// The color attachments can be from a render pass descriptor or a pipeline descriptor. +/// +/// Implements <https://gpuweb.github.io/gpuweb/#abstract-opdef-calculating-color-attachment-bytes-per-sample>. pub fn validate_color_attachment_bytes_per_sample( - attachment_formats: impl Iterator<Item = Option<wgt::TextureFormat>>, + attachment_formats: impl IntoIterator<Item = wgt::TextureFormat>, limit: u32, -) -> Result<(), u32> { +) -> Result<(), crate::command::ColorAttachmentError> { let mut total_bytes_per_sample: u32 = 0; for format in attachment_formats { - let Some(format) = format else { - continue; - }; - let byte_cost = format.target_pixel_byte_cost().unwrap(); let alignment = format.target_component_alignment().unwrap(); @@ -1429,7 +1614,12 @@ pub fn validate_color_attachment_bytes_per_sample( } if total_bytes_per_sample > limit { - return Err(total_bytes_per_sample); + return Err( + crate::command::ColorAttachmentError::TooManyBytesPerSample { + total: total_bytes_per_sample, + limit, + }, + ); } Ok(()) diff --git a/third_party/rust/wgpu-hal/.cargo-checksum.json b/third_party/rust/wgpu-hal/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"4848d8ea1e40adb0bb6378bd2bf7fe0726dcf7f39b966003b4bef5a428a152a6","LICENSE.APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE.MIT":"dc0d97139e8205818c703741c7be7cb3b96888bd5917b8d6fc6133731e403c21","README.md":"cf9e84804a635e4a8a9fefc596be9da6bf7354dde0d105e27d56a12cb20dd8e3","build.rs":"e720cf033fecfdc7e4f34010af2a86340c99b8aaabf69559d32391521e25de6c","examples/halmark/main.rs":"642f00cedd20c87d357e48fbc194af41731e75c12f626d3ecf9fdb5e3ae5ee0c","examples/halmark/shader.wgsl":"26c256ec36d6f0e9a1647431ca772766bee4382d64eaa718ba7b488dcfb6bcca","examples/raw-gles.em.html":"70fbe68394a1a4522192de1dcfaf7d399f60d7bdf5de70b708f9bb0417427546","examples/raw-gles.rs":"a8fa13b191e1596f6895fa44fb7503638d818b8c373e4383247e82c23e5974da","examples/ray-traced-triangle/main.rs":"8cbfdf3e4cd29d23e2c9fd543a36976d849a7161670e094baeda390db9306b57","examples/ray-traced-triangle/shader.wgsl":"cc10caf92746724a71f6dd0dbc3a71e57b37c7d1d83278556805a535c0728a9d","src/auxil/dxgi/conv.rs":"3ca856c93916c0be714924ff077aa1f366be5557cc164210154d252e7e49fb77","src/auxil/dxgi/exception.rs":"7138831914a59dc7cbb71512068e9134144b9bc8f43312682edccb0b3ee24a48","src/auxil/dxgi/factory.rs":"81e479e550a09127384d8080e43d5f5bae8e8dda6082fe41a87bea8f872eb0f1","src/auxil/dxgi/mod.rs":"e6c5cc3b73bb97742135d6f35308c42f0822304764978fb8dabb0e848863352a","src/auxil/dxgi/name.rs":"ff942da0da1a497ee4d2be21604f7ba9fae963588105b3d1f63aae1a0c536e82","src/auxil/dxgi/result.rs":"e7a9dfb48d8ef8cbe58b28b1ace5caf7818ee50505ba3220bb0509e66ae469b7","src/auxil/dxgi/time.rs":"b6911800be3873cbe277b2534b3839c6f005f3d9a09341aace4752e207d584a2","src/auxil/mod.rs":"540b9250d9f0e0af709245ce1e284eaca15b27d47550b0ebba2a512da1666c48","src/auxil/renderdoc.rs":"94898f747476e269b2910d42e769f2bbb6be0cb466ad92f9d381f55799d2756e","src/dx12/adapter.rs":"0473f3a57c534fec80e7788ac95b457f038ff47df2eada910c7fb09e52ff8c59","src/dx12/command.rs":"d0fa8baa2954f26521643caaee6e93801b58da7000b2527348361b6f25ec137a","src/dx12/conv.rs":"a320fca45fd1990762ff10dad1d8bbb29833d9b693b0887bf2898021a737612c","src/dx12/dcomp.rs":"053068fbd393a4b8610c7070a72e5887f0fe09b61d35d2be806083d800b48b08","src/dx12/descriptor.rs":"ccd4feb6bd3e0a0ffc26142f8a81fca26180d0f50146b6eb2f670cbc89ea7064","src/dx12/device.rs":"f6c1c0d65bfb5f7b81ef9ab23fd936a4124fc2a1b43c1d5212a701cfa115409a","src/dx12/instance.rs":"75bddc3807756c691ede3ff944915e443a8bb2b5ac6d0d99babd4ed50d1e3fb9","src/dx12/mod.rs":"3bb54a4f423e49ec3df926c891340e439b27c28efe87ace0a4ece6c81745a591","src/dx12/pipeline_desc.rs":"631c72108f6735debe9f7059e22574ad50b11aca4dc0ca804e6d89b1373d19ff","src/dx12/sampler.rs":"d18d243efe4fa667dbab5b75b5b91d47de94d441976d9f44a46a2b49ba38473d","src/dx12/shader_compilation.rs":"a0ce9ca4b9143715ffc4b45a56a18182d54b118161553173c1a546cc6041560c","src/dx12/suballocation.rs":"6e0323277749a8bd9fb8a3cb458366d79a4c2f27f8210067026048eb65c4247e","src/dx12/types.rs":"3fc7619fc09303eb3c936d4ded6889f94ce9e8b9aa62742ce900baa1b1e1cca7","src/dx12/view.rs":"79b3f7331d9795e60f9b53023cbf0df46c3a05b1e8bd5c7bcca8acdd235b124f","src/dynamic/adapter.rs":"e93f7d082a3950c9e8ccff8a631d251c7598b4b25dda9fe6347dadfa3ba07829","src/dynamic/command.rs":"f1615cc3cae357e0608adfd97106240fc7c77195e97d8dba44c71adfabf6229b","src/dynamic/device.rs":"d5f3c1374c7eb8c8c3737de2b22691c08385cb768d4aac34182feb4d314d7ec9","src/dynamic/instance.rs":"7b515c201e1ca24f24439544dbfa1d19ea1412a4f89bd803e009aed13b021e55","src/dynamic/mod.rs":"2577d3aef6441f5b42b427d80ba3cf7fee2a97b2fc12335a1fba383e8a79a9b2","src/dynamic/queue.rs":"d76abb4797e90253386d24584f186dbe1909e772560156b2e891fa043cfefbdc","src/dynamic/surface.rs":"4328c2fe86931f50aa00ac3d6982d0879b774eebf7a507903d1b1898c891fb4d","src/gles/adapter.rs":"335dc7fbfeb781cd61148b5f8dd653b1d0b0ba79ef552beddcaf772d28b14dda","src/gles/command.rs":"e8bcf8ffb42697abf1d64447b2c92075814e0a4956af41df5fa6ce5f093af155","src/gles/conv.rs":"6ffb8688de38c2fdd956571dd549535259e2811225adc1df02509e8e642ee775","src/gles/device.rs":"ebe95bea12b3e04792611d66deaa6b5e52ac73e39489a247f88a58fbcb30e0b5","src/gles/egl.rs":"2509e5a5c422e213a3050d852d70a13dfc06737c829a8fc169f45791d8e27fed","src/gles/emscripten.rs":"316d2bb6f2a4bb126dbe68a223f7393399080d116b61c39504454acdf4f9cfaf","src/gles/fence.rs":"a2e7b6abd2fd4a438de90b9f58452c64cd8bba83999c90fd6390e3db128b9c6c","src/gles/mod.rs":"aad205606b61660a19e6de5c3a6b92b47000f36ad8bb07256056a059896951a8","src/gles/queue.rs":"17dfbc36cdd19958d842823109a398d35e771cb57e00a7cbbdafc488301fc0e4","src/gles/shaders/clear.frag":"9133ed8ed97d3641fbb6b5f5ea894a3554c629ccc1b80a5fc9221d7293aa1954","src/gles/shaders/clear.vert":"a543768725f4121ff2e9e1fb5b00644931e9d6f2f946c0ef01968afb5a135abd","src/gles/shaders/srgb_present.frag":"dd9a43c339a2fa4ccf7f6a1854c6f400cabf271a7d5e9230768e9f39d47f3ff5","src/gles/shaders/srgb_present.vert":"6e85d489403d80b81cc94790730bb53b309dfc5eeede8f1ea3412a660f31d357","src/gles/web.rs":"cb5940bf7b2381811675011b640040274f407a7d1908d0f82c813d6a9d3b00f7","src/gles/wgl.rs":"81b2a961fb92e22613224539f85fe982599bfdb6364457578c6b0d9778295d89","src/lib.rs":"acd981799345a8332dda471d5d17367c9d595dfb71946666c59ae5f141347061","src/metal/adapter.rs":"1bdf6199693a960139a0c66b366efe5453525d7fab6dc8358009e6ea0159d46c","src/metal/command.rs":"6e2b5750192733d0f9b2b3188ea950f4db5a828e8ce1d23cc2336fb9cde8a131","src/metal/conv.rs":"85e8168be334ba24d109575a0a7e91b2ad3459403173e99e5cdd5d977cc5c18f","src/metal/device.rs":"7dac4f59a12c0695735ef4b0e9fd3fa1c257ccd32966399f1d12b14d425b34b4","src/metal/layer_observer.rs":"8370a6e443d01739b951b8538ee719a03b69fc0cbac92c748db418fbcc8837b5","src/metal/mod.rs":"3c39b76818584f734849538ccc8cb4dfde124d0d7d4de2759dcb583d501f089e","src/metal/surface.rs":"22dc6da86ac74b044b6498764920f0467bb5060f4dffb156b6c1e260db0c48b7","src/metal/time.rs":"c32d69f30e846dfcc0e39e01097fb80df63b2bebb6586143bb62494999850246","src/noop/buffer.rs":"b5edc5e2c52287dfbd4f619e36097ac233041eb9ab287b889b6ee477d740fa09","src/noop/command.rs":"3de99a1a260cfea2e6ca2e76797c1923cc26b069b08362c38798ce27cdc75543","src/noop/mod.rs":"6e1d15602da06f63b790b938dff2c32cefa2a314e56ab116c9e80d54f8de753f","src/validation_canary.rs":"2e8f84e5f85671b3e55ddd410476171c762e34cbea315b37921cbb6ff18bfb4f","src/vulkan/adapter.rs":"95693944091b494b1bfe26ca472b9dedc59782bec93ac561124c7d58b9e06087","src/vulkan/command.rs":"42a4d8e463d9a74f8000e691d36361f5c9adb792d18019deebefcd57ee9488ed","src/vulkan/conv.rs":"03eb28b81d3e41ccfcb2b3f7f029cd671ccdf9556594c6d8b6cc2b22144ec4f2","src/vulkan/device.rs":"fcaeb8b3d4ef914305519a4f10e16b2d4c2f6167eccd452c1298b7202cc02160","src/vulkan/drm.rs":"45f7bf1dd854688a65261e4674d80f90c997b193a162fd2ae658acf4e2003552","src/vulkan/instance.rs":"435fb4f22c3c39463f1a33f1a946180656cc7177eeb97035c0458f5d97a1591f","src/vulkan/mod.rs":"15771b5d598a2c675e9140c199bfcf3eacf2077918e6cee3d3db51805fb1df95","src/vulkan/sampler.rs":"f65729d6df5cce681b7756b3e48074017f0c7f42da69ca55e26cc723cd14ad59","src/vulkan/semaphore_list.rs":"6e548d810d75daf5ed31b6e520ece32c8ef97e4b66926c17f0d4317f355802e5","src/vulkan/swapchain/mod.rs":"1baef6ef36d005fe03b27ea15a91d1c5c7114eb7f777a50e67ca5f448a53291b","src/vulkan/swapchain/native.rs":"6d9aadeeb1d6774afe070e0f5d995576c1d5c1097dfc471d914b2e16782a4d69"},"package":null} -\ No newline at end of file +{"files":{"Cargo.toml":"da95b276985ef9f6846fc2a3741036c2213920a4fcc339fe7a6b6f7a8bf9cfe6","LICENSE.APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE.MIT":"dc0d97139e8205818c703741c7be7cb3b96888bd5917b8d6fc6133731e403c21","README.md":"cf9e84804a635e4a8a9fefc596be9da6bf7354dde0d105e27d56a12cb20dd8e3","build.rs":"92e5ba25cb39fb1eb568ffc84e417d5f272a52a4f7128488c42c26f1f0428a7e","examples/halmark/main.rs":"96c3b9b9ec07cc410cd2e538294808d7b8d83a4253fb4169bd787d99ef700256","examples/halmark/shader.wgsl":"26c256ec36d6f0e9a1647431ca772766bee4382d64eaa718ba7b488dcfb6bcca","examples/raw-gles.em.html":"70fbe68394a1a4522192de1dcfaf7d399f60d7bdf5de70b708f9bb0417427546","examples/raw-gles.rs":"7583874835f178f49c9784f669e53fd22256d06d8b2d0fd8d284b09d0eb56925","examples/ray-traced-triangle/main.rs":"b4435d6df8c9ee69e30a0ea7f8392bea5925fe32b58cb999b6f0fb957af953d1","examples/ray-traced-triangle/shader.wgsl":"90a66ffa57a642fd806a5b3c96347448158cd562d8951b5afd067bfd0b3d1e98","src/auxil/dxgi/conv.rs":"3ca856c93916c0be714924ff077aa1f366be5557cc164210154d252e7e49fb77","src/auxil/dxgi/exception.rs":"0eafc977390cf76b6ab92697a60de05b8fd61fbad5f3d3ea0649d6e93777e1bc","src/auxil/dxgi/factory.rs":"81e479e550a09127384d8080e43d5f5bae8e8dda6082fe41a87bea8f872eb0f1","src/auxil/dxgi/mod.rs":"e6c5cc3b73bb97742135d6f35308c42f0822304764978fb8dabb0e848863352a","src/auxil/dxgi/name.rs":"ff942da0da1a497ee4d2be21604f7ba9fae963588105b3d1f63aae1a0c536e82","src/auxil/dxgi/result.rs":"e7a9dfb48d8ef8cbe58b28b1ace5caf7818ee50505ba3220bb0509e66ae469b7","src/auxil/dxgi/time.rs":"b6911800be3873cbe277b2534b3839c6f005f3d9a09341aace4752e207d584a2","src/auxil/mod.rs":"87f8ebf431175d008e0400c569c5a76c419a7400fa61649687b23de97a4a3705","src/auxil/renderdoc.rs":"94898f747476e269b2910d42e769f2bbb6be0cb466ad92f9d381f55799d2756e","src/dx12/adapter.rs":"82330954cbc52e7a6aedc0f317d658d52f4be83882a08fdf91022f002d274edf","src/dx12/command.rs":"3d3c61140d1003252d887ee0f4870bd9cfbedccfd5f4ae8b949ab0151648823b","src/dx12/conv.rs":"a320fca45fd1990762ff10dad1d8bbb29833d9b693b0887bf2898021a737612c","src/dx12/dcomp.rs":"053068fbd393a4b8610c7070a72e5887f0fe09b61d35d2be806083d800b48b08","src/dx12/descriptor.rs":"ccd4feb6bd3e0a0ffc26142f8a81fca26180d0f50146b6eb2f670cbc89ea7064","src/dx12/device.rs":"6b559e95a819269a5bdb84563604242bf532822ae1dee40a1d22133ae92108e2","src/dx12/instance.rs":"994484b3909e2507b8ed450fc0a5ac69a3f21a0c66a6e7183d76ae4bf1a2c8ad","src/dx12/mod.rs":"44fdc829d9ede4820211d40706e63beec2f1cb556e9642cd7dd0661156aa56dc","src/dx12/pipeline_desc.rs":"631c72108f6735debe9f7059e22574ad50b11aca4dc0ca804e6d89b1373d19ff","src/dx12/sampler.rs":"d18d243efe4fa667dbab5b75b5b91d47de94d441976d9f44a46a2b49ba38473d","src/dx12/shader_compilation.rs":"a0ce9ca4b9143715ffc4b45a56a18182d54b118161553173c1a546cc6041560c","src/dx12/suballocation.rs":"3abbde338015079fe366c5b76bd155b2c41b31ea5b44b7a67ad42f2c43545239","src/dx12/types.rs":"3fc7619fc09303eb3c936d4ded6889f94ce9e8b9aa62742ce900baa1b1e1cca7","src/dx12/view.rs":"79b3f7331d9795e60f9b53023cbf0df46c3a05b1e8bd5c7bcca8acdd235b124f","src/dynamic/adapter.rs":"e93f7d082a3950c9e8ccff8a631d251c7598b4b25dda9fe6347dadfa3ba07829","src/dynamic/command.rs":"90cb8c795af62a39a4488659dc5d482e8738a494a975d123bcd764cdaecebfc6","src/dynamic/device.rs":"d521f90344bfe54538888a8649500c748d656b1b7668acf4f56af6e17461e83a","src/dynamic/instance.rs":"7b515c201e1ca24f24439544dbfa1d19ea1412a4f89bd803e009aed13b021e55","src/dynamic/mod.rs":"2577d3aef6441f5b42b427d80ba3cf7fee2a97b2fc12335a1fba383e8a79a9b2","src/dynamic/queue.rs":"d76abb4797e90253386d24584f186dbe1909e772560156b2e891fa043cfefbdc","src/dynamic/surface.rs":"4328c2fe86931f50aa00ac3d6982d0879b774eebf7a507903d1b1898c891fb4d","src/gles/adapter.rs":"9a39f0a589d28144c715319b6a4907c671a2ede69bf34a21483852238a78f917","src/gles/command.rs":"fec58151673a12aff619eaa495d34e39da0af428dac26f4087bb990cd1019261","src/gles/conv.rs":"1013e1e48bc6b5b0df60a026251b97653528b112600eb3f8d4e905dbf791d897","src/gles/device.rs":"c2be98d957f8a13689bb23b16300f9deb6c1d7def016ef414c0592aa2d2ab40d","src/gles/egl.rs":"1d8c984ca3c7d70f2f2cd284e6585a549d7f1e33c5bc86bf35de98485f61c06f","src/gles/emscripten.rs":"316d2bb6f2a4bb126dbe68a223f7393399080d116b61c39504454acdf4f9cfaf","src/gles/fence.rs":"a2e7b6abd2fd4a438de90b9f58452c64cd8bba83999c90fd6390e3db128b9c6c","src/gles/mod.rs":"21b3402a396c91d3168e3c42ec9e43cf33991f362a7d59638c23e1e1a34602f2","src/gles/queue.rs":"949c132a5e7ebc65b5da1b007f90503fd9d080021b3335c884b556b120c150ab","src/gles/shaders/clear.frag":"9133ed8ed97d3641fbb6b5f5ea894a3554c629ccc1b80a5fc9221d7293aa1954","src/gles/shaders/clear.vert":"a543768725f4121ff2e9e1fb5b00644931e9d6f2f946c0ef01968afb5a135abd","src/gles/shaders/srgb_present.frag":"dd9a43c339a2fa4ccf7f6a1854c6f400cabf271a7d5e9230768e9f39d47f3ff5","src/gles/shaders/srgb_present.vert":"6e85d489403d80b81cc94790730bb53b309dfc5eeede8f1ea3412a660f31d357","src/gles/web.rs":"cb5940bf7b2381811675011b640040274f407a7d1908d0f82c813d6a9d3b00f7","src/gles/wgl.rs":"d98495ae9866e71fecdbd5fadd6be4e87f4d61c6981f44368893129a128cc659","src/lib.rs":"7455991893b42874d09444d54931da781cae27f71e780f21911158037305cd6d","src/metal/adapter.rs":"bda1ac5c6bb4dc5edcc123a02105159c45904f2649dcbd5fcd3e14645a734a32","src/metal/command.rs":"1eef7886ee0423de4015ff91a14c95db72c51d552c3fd90112c6143e23cb387c","src/metal/conv.rs":"85e8168be334ba24d109575a0a7e91b2ad3459403173e99e5cdd5d977cc5c18f","src/metal/device.rs":"801d02d1291827219340fb2b2e4603ffb1f14a5a91cd8ba8f285c580aab255ea","src/metal/layer_observer.rs":"8370a6e443d01739b951b8538ee719a03b69fc0cbac92c748db418fbcc8837b5","src/metal/mod.rs":"733b8c584f18dbc445b0603d9664d4dc1410efe95c7cd0ce54908e7621605605","src/metal/surface.rs":"b002e2665704c220bb06f352a6e840290ac07d6aa4bbd236f7532fedae0d0e5f","src/metal/time.rs":"c32d69f30e846dfcc0e39e01097fb80df63b2bebb6586143bb62494999850246","src/noop/buffer.rs":"b5edc5e2c52287dfbd4f619e36097ac233041eb9ab287b889b6ee477d740fa09","src/noop/command.rs":"2b6c04bb4cd7b7a1f40433266767698e01ed725a9cec42a6c16834c3a369cc67","src/noop/mod.rs":"45fb2426cc02f7ffe71d217055cc0569e0829a386fbe1ddfba3df5dc7a3df7af","src/validation_canary.rs":"2e8f84e5f85671b3e55ddd410476171c762e34cbea315b37921cbb6ff18bfb4f","src/vulkan/adapter.rs":"ad944471311577f1de081f21b1107cde35658c4fab10f6d33d730882eaca8f09","src/vulkan/command.rs":"d38772cdbaa63eb87a84b0b31ffec0848c70ffc8f26b2743ec7f9e51ed39db36","src/vulkan/conv.rs":"237289e3a9403a194482559d9f361c7758e35c61bc916283c2fa7d79081b15fc","src/vulkan/device.rs":"4b849b404e5277909ee81b0e317facacdc18cc066e0177c62be52cb152544ee1","src/vulkan/drm.rs":"45f7bf1dd854688a65261e4674d80f90c997b193a162fd2ae658acf4e2003552","src/vulkan/instance.rs":"3afdd04742c4b2588be421d644de70eba47901cb165de4c0528bc425fed6652a","src/vulkan/mod.rs":"452ea5f33ad883ce6d085e7c3ccc96e9ebef8cc83f0b29d459e2bd9c8943f78b","src/vulkan/sampler.rs":"f65729d6df5cce681b7756b3e48074017f0c7f42da69ca55e26cc723cd14ad59","src/vulkan/semaphore_list.rs":"6e548d810d75daf5ed31b6e520ece32c8ef97e4b66926c17f0d4317f355802e5","src/vulkan/swapchain/mod.rs":"1baef6ef36d005fe03b27ea15a91d1c5c7114eb7f777a50e67ca5f448a53291b","src/vulkan/swapchain/native.rs":"fc05951f4bf49074fab510f9706689ac74edf4478bae049af054e8735f247a56"},"package":null} +\ No newline at end of file diff --git a/third_party/rust/wgpu-hal/Cargo.toml b/third_party/rust/wgpu-hal/Cargo.toml @@ -13,7 +13,7 @@ edition = "2021" rust-version = "1.82.0" name = "wgpu-hal" -version = "27.0.0" +version = "28.0.0" authors = ["gfx-rs developers"] build = "build.rs" autolib = false @@ -136,7 +136,6 @@ vulkan = [ "dep:arrayvec", "dep:ash", "dep:bytemuck", - "dep:gpu-alloc", "dep:gpu-descriptor", "dep:hashbrown", "dep:libc", @@ -147,6 +146,7 @@ vulkan = [ "dep:profiling", "dep:smallvec", "dep:windows", + "gpu-allocator/vulkan", "windows/Win32", ] @@ -205,7 +205,7 @@ version = "0.4.21" optional = true [dependencies.naga] -version = "27.0.0" +version = "28.0.0" path = "../naga" [dependencies.ordered-float] @@ -231,7 +231,7 @@ version = "2.0.12" default-features = false [dependencies.wgpu-types] -version = "27.0.0" +version = "28.0.0" path = "../wgpu-types" default-features = false @@ -242,8 +242,11 @@ default-features = false [dev-dependencies.glam] version = "0.30.7" +[dev-dependencies.log] +version = "0.4.21" + [dev-dependencies.naga] -version = "27.0.0" +version = "28.0.0" path = "../naga" features = [ "wgsl-in", @@ -278,7 +281,7 @@ features = [ optional = true default-features = false -[target.'cfg(all(windows, not(target_arch = "aarch64")))'.dependencies.mach-dxcompiler-rs] +[target.'cfg(all(windows, not(target_arch = "aarch64"), target_env = "msvc"))'.dependencies.mach-dxcompiler-rs] version = "0.1.4" optional = true default-features = false @@ -322,9 +325,11 @@ features = [ version = "0.38" optional = true -[target.'cfg(not(target_arch = "wasm32"))'.dependencies.gpu-alloc] -version = "0.6" +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.gpu-allocator] +version = "0.28" +features = ["hashbrown"] optional = true +default-features = false [target.'cfg(not(target_arch = "wasm32"))'.dependencies.gpu-descriptor] version = "0.3.2" @@ -382,7 +387,7 @@ version = "0.2" optional = true [target.'cfg(target_vendor = "apple")'.dependencies.metal] -version = "0.32" +version = "0.33" optional = true [target.'cfg(target_vendor = "apple")'.dependencies.objc] @@ -403,15 +408,6 @@ default-features = false version = "0.6" optional = true -[target."cfg(windows)".dependencies.gpu-allocator] -version = "0.28" -features = [ - "hashbrown", - "d3d12", -] -optional = true -default-features = false - [target."cfg(windows)".dependencies.once_cell] version = "1.21" optional = true diff --git a/third_party/rust/wgpu-hal/build.rs b/third_party/rust/wgpu-hal/build.rs @@ -23,8 +23,9 @@ fn main() { ) }, metal: { all(target_vendor = "apple", feature = "metal") }, vulkan: { all(not(target_arch = "wasm32"), feature = "vulkan") }, + any_backend: { any(dx12, metal, vulkan, gles) }, // ⚠️ Keep in sync with target.cfg() definition in Cargo.toml and cfg_alias in `wgpu` crate ⚠️ - static_dxc: { all(target_os = "windows", feature = "static-dxc", not(target_arch = "aarch64")) }, + static_dxc: { all(target_os = "windows", feature = "static-dxc", not(target_arch = "aarch64"), target_env = "msvc") }, supports_64bit_atomics: { target_has_atomic = "64" }, supports_ptr_atomics: { target_has_atomic = "ptr" } } diff --git a/third_party/rust/wgpu-hal/examples/halmark/main.rs b/third_party/rust/wgpu-hal/examples/halmark/main.rs @@ -99,6 +99,7 @@ impl<A: hal::Api> Example<A> { memory_budget_thresholds: wgpu_types::MemoryBudgetThresholds::default(), // Can't rely on having DXC available, so use FXC instead backend_options: wgpu_types::BackendOptions::default(), + telemetry: None, }; let instance = unsafe { A::Instance::init(&instance_desc)? }; let surface = { @@ -242,7 +243,7 @@ impl<A: hal::Api> Example<A> { label: None, flags: hal::PipelineLayoutFlags::empty(), bind_group_layouts: &[&global_group_layout, &local_group_layout], - push_constant_ranges: &[], + immediate_size: 0, }; let pipeline_layout = unsafe { device @@ -718,7 +719,7 @@ impl<A: hal::Api> Example<A> { }, depth_slice: None, resolve_target: None, - ops: hal::AttachmentOps::STORE, + ops: hal::AttachmentOps::STORE | hal::AttachmentOps::LOAD_CLEAR, clear_value: wgpu_types::Color { r: 0.1, g: 0.2, diff --git a/third_party/rust/wgpu-hal/examples/raw-gles.rs b/third_party/rust/wgpu-hal/examples/raw-gles.rs @@ -325,7 +325,7 @@ fn fill_screen(exposed: &hal::ExposedAdapter<hal::api::Gles>, width: u32, height }, depth_slice: None, resolve_target: None, - ops: hal::AttachmentOps::STORE, + ops: hal::AttachmentOps::STORE | hal::AttachmentOps::LOAD_CLEAR, clear_value: wgpu_types::Color::BLUE, })], depth_stencil_attachment: None, diff --git a/third_party/rust/wgpu-hal/examples/ray-traced-triangle/main.rs b/third_party/rust/wgpu-hal/examples/ray-traced-triangle/main.rs @@ -246,6 +246,7 @@ impl<A: hal::Api> Example<A> { }, ..Default::default() }, + telemetry: None, }; let instance = unsafe { A::Instance::init(&instance_desc)? }; let surface = { @@ -387,7 +388,7 @@ impl<A: hal::Api> Example<A> { label: None, flags: hal::PipelineLayoutFlags::empty(), bind_group_layouts: &[&bgl], - push_constant_ranges: &[], + immediate_size: 0, }; let pipeline_layout = unsafe { device diff --git a/third_party/rust/wgpu-hal/examples/ray-traced-triangle/shader.wgsl b/third_party/rust/wgpu-hal/examples/ray-traced-triangle/shader.wgsl @@ -1,3 +1,5 @@ +enable wgpu_ray_query; + struct Uniforms { view_inv: mat4x4<f32>, proj_inv: mat4x4<f32>, diff --git a/third_party/rust/wgpu-hal/src/auxil/dxgi/exception.rs b/third_party/rust/wgpu-hal/src/auxil/dxgi/exception.rs @@ -31,8 +31,10 @@ const MESSAGE_PREFIXES: &[(&str, log::Level)] = &[ ("CORRUPTION", log::Level::Error), ("ERROR", log::Level::Error), ("WARNING", log::Level::Warn), - ("INFO", log::Level::Info), - ("MESSAGE", log::Level::Debug), + // We intentionally suppress "INFO" messages down to debug + // so that users are not innundated with info messages from the runtime. + ("INFO", log::Level::Debug), + ("MESSAGE", log::Level::Trace), ]; unsafe extern "system" fn output_debug_string_handler( diff --git a/third_party/rust/wgpu-hal/src/auxil/mod.rs b/third_party/rust/wgpu-hal/src/auxil/mod.rs @@ -126,3 +126,21 @@ impl crate::TextureCopy { self.size = self.size.min(&max_src_size).min(&max_dst_size); } } + +/// Clamp the limits in `limits` to honor any HAL-imposed maximums. +/// +/// Limits that do not have a HAL-defined maximum are left unchanged. +#[cfg_attr(not(any_backend), allow(dead_code))] +pub(crate) fn apply_hal_limits(mut limits: wgt::Limits) -> wgt::Limits { + // The Metal backend wants to have its own consistent view of the limits, so + // it may duplicate some of these limits. + limits.max_bind_groups = limits.max_bind_groups.min(crate::MAX_BIND_GROUPS as u32); + limits.max_storage_buffer_binding_size &= !(wgt::STORAGE_BINDING_SIZE_ALIGNMENT - 1); + limits.max_vertex_buffers = limits + .max_vertex_buffers + .min(crate::MAX_VERTEX_BUFFERS as u32); + limits.max_color_attachments = limits + .max_color_attachments + .min(crate::MAX_COLOR_ATTACHMENTS as u32); + limits +} diff --git a/third_party/rust/wgpu-hal/src/dx12/adapter.rs b/third_party/rust/wgpu-hal/src/dx12/adapter.rs @@ -23,7 +23,7 @@ use crate::{ self, dxgi::{factory::DxgiAdapter, result::HResult}, }, - dx12::{dcomp::DCompLib, shader_compilation, SurfaceTarget}, + dx12::{dcomp::DCompLib, shader_compilation, FeatureLevel, ShaderModel, SurfaceTarget}, }; impl Drop for super::Adapter { @@ -58,6 +58,7 @@ impl super::Adapter { &self.raw } + #[allow(clippy::too_many_arguments)] pub(super) fn expose( adapter: DxgiAdapter, library: &Arc<D3D12Lib>, @@ -66,19 +67,39 @@ impl super::Adapter { memory_budget_thresholds: wgt::MemoryBudgetThresholds, compiler_container: Arc<shader_compilation::CompilerContainer>, backend_options: wgt::Dx12BackendOptions, + telemetry: Option<crate::Telemetry>, ) -> Option<crate::ExposedAdapter<super::Api>> { + let desc = unsafe { adapter.GetDesc2() }.unwrap(); + let driver_version = + unsafe { adapter.CheckInterfaceSupport(&Dxgi::IDXGIDevice::IID) }.unwrap() as u64; + let driver_version = [ + (driver_version >> 48) as u16, + (driver_version >> 32) as u16, + (driver_version >> 16) as u16, + driver_version as u16, + ]; + // Create the device so that we can get the capabilities. - let device = { + let res = { profiling::scope!("ID3D12Device::create_device"); - library - .create_device(&adapter, Direct3D::D3D_FEATURE_LEVEL_11_0) - .ok()?? + library.create_device(&adapter, Direct3D::D3D_FEATURE_LEVEL_11_0) }; + if let Some(telemetry) = telemetry { + if let Err(err) = res { + (telemetry.d3d12_expose_adapter)( + &desc, + driver_version, + crate::D3D12ExposeAdapterResult::CreateDeviceError(err), + ); + } + } + let device = res.ok()?; profiling::scope!("feature queries"); // Detect the highest supported feature level. let d3d_feature_level = [ + Direct3D::D3D_FEATURE_LEVEL_12_2, Direct3D::D3D_FEATURE_LEVEL_12_1, Direct3D::D3D_FEATURE_LEVEL_12_0, Direct3D::D3D_FEATURE_LEVEL_11_1, @@ -97,11 +118,14 @@ impl super::Adapter { ) } .unwrap(); - let max_feature_level = device_levels.MaxSupportedFeatureLevel; - - // We have found a possible adapter. - // Acquire the device information. - let desc = unsafe { adapter.GetDesc2() }.unwrap(); + let max_feature_level = match device_levels.MaxSupportedFeatureLevel { + Direct3D::D3D_FEATURE_LEVEL_11_0 => FeatureLevel::_11_0, + Direct3D::D3D_FEATURE_LEVEL_11_1 => FeatureLevel::_11_1, + Direct3D::D3D_FEATURE_LEVEL_12_0 => FeatureLevel::_12_0, + Direct3D::D3D_FEATURE_LEVEL_12_1 => FeatureLevel::_12_1, + Direct3D::D3D_FEATURE_LEVEL_12_2 => FeatureLevel::_12_2, + _ => unreachable!(), + }; let device_name = auxil::dxgi::conv::map_adapter_name(desc.Description); @@ -116,13 +140,14 @@ impl super::Adapter { } .unwrap(); - let driver_version = unsafe { adapter.CheckInterfaceSupport(&Dxgi::IDXGIDevice::IID) } - .ok() - .map(|i| { - const MASK: i64 = 0xFFFF; - (i >> 48, (i >> 32) & MASK, (i >> 16) & MASK, i & MASK) - }) - .unwrap_or((0, 0, 0, 0)); + let mut features1 = Direct3D12::D3D12_FEATURE_DATA_D3D12_OPTIONS1::default(); + let hr = unsafe { + device.CheckFeatureSupport( + Direct3D12::D3D12_FEATURE_D3D12_OPTIONS1, + <*mut _>::cast(&mut features1), + size_of_val(&features1) as u32, + ) + }; let mut workarounds = super::Workarounds::default(); @@ -132,7 +157,7 @@ impl super::Adapter { // use a version that starts with 10.x.x.x. Versions that ship from Nuget use 1.0.x.x. // // As far as we know, this is only an issue on the Nuget versions. - if is_warp && driver_version >= (1, 0, 13, 0) && driver_version.0 < 10 { + if is_warp && driver_version >= [1, 0, 13, 0] && driver_version[0] < 10 { workarounds.avoid_shader_debug_info = true; } @@ -153,9 +178,11 @@ impl super::Adapter { device_pci_bus_id: get_adapter_pci_info(desc.VendorId, desc.DeviceId), driver: format!( "{}.{}.{}.{}", - driver_version.0, driver_version.1, driver_version.2, driver_version.3 + driver_version[0], driver_version[1], driver_version[2], driver_version[3] ), driver_info: String::new(), + subgroup_min_size: features1.WaveLaneCountMin, + subgroup_max_size: features1.WaveLaneCountMax, transient_saves_memory: false, }; @@ -170,6 +197,13 @@ impl super::Adapter { .unwrap(); if options.ResourceBindingTier.0 < Direct3D12::D3D12_RESOURCE_BINDING_TIER_2.0 { + if let Some(telemetry) = telemetry { + (telemetry.d3d12_expose_adapter)( + &desc, + driver_version, + crate::D3D12ExposeAdapterResult::ResourceBindingTier2Requirement, + ); + } // We require Tier 2 or higher for the ability to make samplers bindless in all cases. return None; } @@ -263,62 +297,86 @@ impl super::Adapter { } }; + let mut shader_models_after_5_1 = [ + Direct3D12::D3D_SHADER_MODEL_6_9, + Direct3D12::D3D_SHADER_MODEL_6_8, + Direct3D12::D3D_SHADER_MODEL_6_7, + Direct3D12::D3D_SHADER_MODEL_6_6, + Direct3D12::D3D_SHADER_MODEL_6_5, + Direct3D12::D3D_SHADER_MODEL_6_4, + Direct3D12::D3D_SHADER_MODEL_6_3, + Direct3D12::D3D_SHADER_MODEL_6_2, + Direct3D12::D3D_SHADER_MODEL_6_1, + Direct3D12::D3D_SHADER_MODEL_6_0, + ] + .iter(); + let max_device_shader_model = loop { + if let Some(&sm) = shader_models_after_5_1.next() { + let mut sm = Direct3D12::D3D12_FEATURE_DATA_SHADER_MODEL { + HighestShaderModel: sm, + }; + if unsafe { + device.CheckFeatureSupport( + Direct3D12::D3D12_FEATURE_SHADER_MODEL, + <*mut _>::cast(&mut sm), + size_of_val(&sm) as u32, + ) + } + .is_ok() + { + break match sm.HighestShaderModel { + Direct3D12::D3D_SHADER_MODEL_6_0 => ShaderModel::_6_0, + Direct3D12::D3D_SHADER_MODEL_6_1 => ShaderModel::_6_1, + Direct3D12::D3D_SHADER_MODEL_6_2 => ShaderModel::_6_2, + Direct3D12::D3D_SHADER_MODEL_6_3 => ShaderModel::_6_3, + Direct3D12::D3D_SHADER_MODEL_6_4 => ShaderModel::_6_4, + Direct3D12::D3D_SHADER_MODEL_6_5 => ShaderModel::_6_5, + Direct3D12::D3D_SHADER_MODEL_6_6 => ShaderModel::_6_6, + Direct3D12::D3D_SHADER_MODEL_6_7 => ShaderModel::_6_7, + Direct3D12::D3D_SHADER_MODEL_6_8 => ShaderModel::_6_8, + Direct3D12::D3D_SHADER_MODEL_6_9 => ShaderModel::_6_9, + _ => unreachable!(), + }; + } + } else { + break ShaderModel::_5_1; + } + }; + let shader_model = if let Some(max_shader_model) = compiler_container.max_shader_model() { - let max_shader_model = match max_shader_model { - wgt::DxcShaderModel::V6_0 => Direct3D12::D3D_SHADER_MODEL_6_0, - wgt::DxcShaderModel::V6_1 => Direct3D12::D3D_SHADER_MODEL_6_1, - wgt::DxcShaderModel::V6_2 => Direct3D12::D3D_SHADER_MODEL_6_2, - wgt::DxcShaderModel::V6_3 => Direct3D12::D3D_SHADER_MODEL_6_3, - wgt::DxcShaderModel::V6_4 => Direct3D12::D3D_SHADER_MODEL_6_4, - wgt::DxcShaderModel::V6_5 => Direct3D12::D3D_SHADER_MODEL_6_5, - wgt::DxcShaderModel::V6_6 => Direct3D12::D3D_SHADER_MODEL_6_6, - wgt::DxcShaderModel::V6_7 => Direct3D12::D3D_SHADER_MODEL_6_7, + let max_dxc_shader_model = match max_shader_model { + wgt::DxcShaderModel::V6_0 => ShaderModel::_6_0, + wgt::DxcShaderModel::V6_1 => ShaderModel::_6_1, + wgt::DxcShaderModel::V6_2 => ShaderModel::_6_2, + wgt::DxcShaderModel::V6_3 => ShaderModel::_6_3, + wgt::DxcShaderModel::V6_4 => ShaderModel::_6_4, + wgt::DxcShaderModel::V6_5 => ShaderModel::_6_5, + wgt::DxcShaderModel::V6_6 => ShaderModel::_6_6, + wgt::DxcShaderModel::V6_7 => ShaderModel::_6_7, }; - let mut versions = [ - Direct3D12::D3D_SHADER_MODEL_6_7, - Direct3D12::D3D_SHADER_MODEL_6_6, - Direct3D12::D3D_SHADER_MODEL_6_5, - Direct3D12::D3D_SHADER_MODEL_6_4, - Direct3D12::D3D_SHADER_MODEL_6_3, - Direct3D12::D3D_SHADER_MODEL_6_2, - Direct3D12::D3D_SHADER_MODEL_6_1, - Direct3D12::D3D_SHADER_MODEL_6_0, - ] - .iter() - .filter(|shader_model| shader_model.0 <= max_shader_model.0); - - let highest_shader_model = loop { - if let Some(&sm) = versions.next() { - let mut sm = Direct3D12::D3D12_FEATURE_DATA_SHADER_MODEL { - HighestShaderModel: sm, - }; - if unsafe { - device.CheckFeatureSupport( - Direct3D12::D3D12_FEATURE_SHADER_MODEL, - <*mut _>::cast(&mut sm), - size_of_val(&sm) as u32, - ) - } - .is_ok() - { - break sm.HighestShaderModel; + let shader_model = max_device_shader_model.min(max_dxc_shader_model); + + match shader_model { + ShaderModel::_5_1 => { + if let Some(telemetry) = telemetry { + (telemetry.d3d12_expose_adapter)( + &desc, + driver_version, + crate::D3D12ExposeAdapterResult::ShaderModel6Requirement, + ); } - } else { - break Direct3D12::D3D_SHADER_MODEL_5_1; + // don't expose this adapter if it doesn't support DXIL + return None; } - }; - - match highest_shader_model { - Direct3D12::D3D_SHADER_MODEL_5_1 => return None, // don't expose this adapter if it doesn't support DXIL - Direct3D12::D3D_SHADER_MODEL_6_0 => naga::back::hlsl::ShaderModel::V6_0, - Direct3D12::D3D_SHADER_MODEL_6_1 => naga::back::hlsl::ShaderModel::V6_1, - Direct3D12::D3D_SHADER_MODEL_6_2 => naga::back::hlsl::ShaderModel::V6_2, - Direct3D12::D3D_SHADER_MODEL_6_3 => naga::back::hlsl::ShaderModel::V6_3, - Direct3D12::D3D_SHADER_MODEL_6_4 => naga::back::hlsl::ShaderModel::V6_4, - Direct3D12::D3D_SHADER_MODEL_6_5 => naga::back::hlsl::ShaderModel::V6_5, - Direct3D12::D3D_SHADER_MODEL_6_6 => naga::back::hlsl::ShaderModel::V6_6, - Direct3D12::D3D_SHADER_MODEL_6_7 => naga::back::hlsl::ShaderModel::V6_7, + ShaderModel::_6_0 => naga::back::hlsl::ShaderModel::V6_0, + ShaderModel::_6_1 => naga::back::hlsl::ShaderModel::V6_1, + ShaderModel::_6_2 => naga::back::hlsl::ShaderModel::V6_2, + ShaderModel::_6_3 => naga::back::hlsl::ShaderModel::V6_3, + ShaderModel::_6_4 => naga::back::hlsl::ShaderModel::V6_4, + ShaderModel::_6_5 => naga::back::hlsl::ShaderModel::V6_5, + ShaderModel::_6_6 => naga::back::hlsl::ShaderModel::V6_6, + ShaderModel::_6_7 => naga::back::hlsl::ShaderModel::V6_7, _ => unreachable!(), } } else { @@ -351,7 +409,7 @@ impl super::Adapter { let (full_heap_count, uav_count) = match options.ResourceBindingTier { Direct3D12::D3D12_RESOURCE_BINDING_TIER_1 => { let uav_count = match max_feature_level { - Direct3D::D3D_FEATURE_LEVEL_11_0 => 8, + FeatureLevel::_11_0 => 8, _ => 64, }; @@ -364,12 +422,12 @@ impl super::Adapter { Direct3D12::D3D12_MAX_SHADER_VISIBLE_DESCRIPTOR_HEAP_SIZE_TIER_2, 64, ), - Direct3D12::D3D12_RESOURCE_BINDING_TIER_3 => ( + tier if tier.0 >= Direct3D12::D3D12_RESOURCE_BINDING_TIER_3.0 => ( tier3_practical_descriptor_limit, tier3_practical_descriptor_limit, ), other => { - log::warn!("Unknown resource binding tier {other:?}"); + log::debug!("Got zero or negative value for resource binding tier {other:?}"); ( Direct3D12::D3D12_MAX_SHADER_VISIBLE_DESCRIPTOR_HEAP_SIZE_TIER_1, 8, @@ -395,7 +453,7 @@ impl super::Adapter { | wgt::Features::TEXTURE_COMPRESSION_BC_SLICED_3D | wgt::Features::CLEAR_TEXTURE | wgt::Features::TEXTURE_FORMAT_16BIT_NORM - | wgt::Features::PUSH_CONSTANTS + | wgt::Features::IMMEDIATES | wgt::Features::SHADER_PRIMITIVE_INDEX | wgt::Features::RG11B10UFLOAT_RENDERABLE | wgt::Features::DUAL_SOURCE_BLENDING @@ -411,7 +469,7 @@ impl super::Adapter { // write the results there, and issue a bunch of copy commands. //| wgt::Features::PIPELINE_STATISTICS_QUERY - if max_feature_level.0 >= Direct3D::D3D_FEATURE_LEVEL_11_1.0 { + if max_feature_level >= FeatureLevel::_11_1 { features |= wgt::Features::VERTEX_WRITABLE_STORAGE; } @@ -483,15 +541,6 @@ impl super::Adapter { }; features.set(wgt::Features::TEXTURE_FORMAT_P010, p010_format_supported); - let mut features1 = Direct3D12::D3D12_FEATURE_DATA_D3D12_OPTIONS1::default(); - let hr = unsafe { - device.CheckFeatureSupport( - Direct3D12::D3D12_FEATURE_D3D12_OPTIONS1, - <*mut _>::cast(&mut features1), - size_of_val(&features1) as u32, - ) - }; - features.set( wgt::Features::SHADER_INT64, shader_model >= naga::back::hlsl::ShaderModel::V6_0 @@ -517,13 +566,6 @@ impl super::Adapter { ); features.set( - wgt::Features::TEXTURE_INT64_ATOMIC, - shader_model >= naga::back::hlsl::ShaderModel::V6_6 - && hr.is_ok() - && features1.Int64ShaderOps.as_bool(), - ); - - features.set( wgt::Features::SUBGROUP, shader_model >= naga::back::hlsl::ShaderModel::V6_0 && hr.is_ok() @@ -550,23 +592,62 @@ impl super::Adapter { supports_ray_tracing, ); - let atomic_int64_on_typed_resource_supported = { + // Check for Int64 atomic support on buffers. This is very convoluted, but is based on a conservative reading + // of https://microsoft.github.io/DirectX-Specs/d3d/HLSL_SM_6_6_Int64_and_Float_Atomics.html#integer-64-bit-capabilities. + let atomic_int64_buffers; + let atomic_int64_textures; + { let mut features9 = Direct3D12::D3D12_FEATURE_DATA_D3D12_OPTIONS9::default(); - unsafe { + let hr9 = unsafe { device.CheckFeatureSupport( Direct3D12::D3D12_FEATURE_D3D12_OPTIONS9, <*mut _>::cast(&mut features9), size_of_val(&features9) as u32, ) } - .is_ok() - && features9.AtomicInt64OnGroupSharedSupported.as_bool() + .is_ok(); + + let mut features11 = Direct3D12::D3D12_FEATURE_DATA_D3D12_OPTIONS11::default(); + let hr11 = unsafe { + device.CheckFeatureSupport( + Direct3D12::D3D12_FEATURE_D3D12_OPTIONS11, + <*mut _>::cast(&mut features11), + size_of_val(&features11) as u32, + ) + } + .is_ok(); + + atomic_int64_buffers = hr9 && hr11 && hr.is_ok() + // Int64 atomics show up in SM6.6. + && shader_model >= naga::back::hlsl::ShaderModel::V6_6 + // They require Int64 to be available in the shader at all. + && features1.Int64ShaderOps.as_bool() + // As our RWByteAddressBuffers can exist on both descriptor heaps and + // as root descriptors, we need to ensure that both cases are supported. + // base SM6.6 only guarantees Int64 atomics on resources in root descriptors. + && features11.AtomicInt64OnDescriptorHeapResourceSupported.as_bool() + // Our Int64 atomic caps currently require groupshared. This + // prevents Intel or Qcomm from using Int64 currently. + // https://github.com/gfx-rs/wgpu/issues/8666 + && features9.AtomicInt64OnGroupSharedSupported.as_bool(); + + atomic_int64_textures = hr9 && hr11 && hr.is_ok() + // Int64 atomics show up in SM6.6. + && shader_model >= naga::back::hlsl::ShaderModel::V6_6 + // They require Int64 to be available in the shader at all. + && features1.Int64ShaderOps.as_bool() + // Textures are typed resources, so we need this flag. && features9.AtomicInt64OnTypedResourceSupported.as_bool() + // As textures can only exist in descriptor heaps, we require this. + // However, all architectures that support atomics on typed resources + // support this as well, so this is somewhat redundant. + && features11.AtomicInt64OnDescriptorHeapResourceSupported.as_bool(); }; features.set( wgt::Features::SHADER_INT64_ATOMIC_ALL_OPS | wgt::Features::SHADER_INT64_ATOMIC_MIN_MAX, - atomic_int64_on_typed_resource_supported, + atomic_int64_buffers, ); + features.set(wgt::Features::TEXTURE_INT64_ATOMIC, atomic_int64_textures); let mesh_shader_supported = { let mut features7 = Direct3D12::D3D12_FEATURE_DATA_D3D12_OPTIONS7::default(); unsafe { @@ -646,6 +727,17 @@ impl super::Adapter { // See https://microsoft.github.io/DirectX-Specs/d3d/ViewInstancing.html#maximum-viewinstancecount let max_multiview_view_count = if view_instancing { 4 } else { 0 }; + if let Some(telemetry) = telemetry { + (telemetry.d3d12_expose_adapter)( + &desc, + driver_version, + crate::D3D12ExposeAdapterResult::Success( + max_feature_level, + max_device_shader_model, + ), + ); + } + Some(crate::ExposedAdapter { adapter: super::Adapter { raw: adapter, @@ -661,7 +753,7 @@ impl super::Adapter { info, features, capabilities: crate::Capabilities { - limits: wgt::Limits { + limits: auxil::apply_hal_limits(wgt::Limits { max_texture_dimension_1d: Direct3D12::D3D12_REQ_TEXTURE1D_U_DIMENSION, max_texture_dimension_2d: Direct3D12::D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION .min(Direct3D12::D3D12_REQ_TEXTURECUBE_DIMENSION), @@ -690,17 +782,14 @@ impl super::Adapter { max_uniform_buffer_binding_size: Direct3D12::D3D12_REQ_CONSTANT_BUFFER_ELEMENT_COUNT * 16, max_storage_buffer_binding_size: auxil::MAX_I32_BINDING_SIZE, - max_vertex_buffers: Direct3D12::D3D12_VS_INPUT_REGISTER_COUNT - .min(crate::MAX_VERTEX_BUFFERS as u32), + max_vertex_buffers: Direct3D12::D3D12_VS_INPUT_REGISTER_COUNT, max_vertex_attributes: Direct3D12::D3D12_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT, max_vertex_buffer_array_stride: Direct3D12::D3D12_SO_BUFFER_MAX_STRIDE_IN_BYTES, - min_subgroup_size: 4, // Not using `features1.WaveLaneCountMin` as it is unreliable - max_subgroup_size: 128, - // The push constants are part of the root signature which + // The immediates are part of the root signature which // has a limit of 64 DWORDS (256 bytes), but other resources // also share the root signature: // - // - push constants consume a `DWORD` for each `4 bytes` of data + // - immediates consume a `DWORD` for each `4 bytes` of data // - If a bind group has buffers it will consume a `DWORD` // for the descriptor table // - If a bind group has samplers it will consume a `DWORD` @@ -716,7 +805,7 @@ impl super::Adapter { // constants needs to be set to a reasonable number instead. // // Source: https://learn.microsoft.com/en-us/windows/win32/direct3d12/root-signature-limits#memory-limits-and-costs - max_push_constant_size: 128, + max_immediate_size: 128, min_uniform_buffer_offset_alignment: Direct3D12::D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT, min_storage_buffer_offset_alignment: 4, @@ -739,18 +828,42 @@ impl super::Adapter { max_non_sampler_bindings: 1_000_000, // Source: https://microsoft.github.io/DirectX-Specs/d3d/MeshShader.html#dispatchmesh-api - max_task_workgroup_total_count: 2u32.pow(22), + max_task_mesh_workgroup_total_count: if mesh_shader_supported { + 2u32.pow(22) + } else { + 0 + }, // Technically it says "64k" but I highly doubt they want 65536 for compute and exactly 64,000 for task workgroups - max_task_workgroups_per_dimension: - Direct3D12::D3D12_CS_DISPATCH_MAX_THREAD_GROUPS_PER_DIMENSION, - // Multiview not supported by WGPU yet + max_task_mesh_workgroups_per_dimension: if mesh_shader_supported { + Direct3D12::D3D12_CS_DISPATCH_MAX_THREAD_GROUPS_PER_DIMENSION + } else { + 0 + }, + // Assume this inherits from compute shaders + max_task_invocations_per_workgroup: if mesh_shader_supported { + Direct3D12::D3D12_CS_4_X_THREAD_GROUP_MAX_THREADS_PER_GROUP + } else { + 0 + }, + max_task_invocations_per_dimension: if mesh_shader_supported { + Direct3D12::D3D12_CS_THREAD_GROUP_MAX_Z + } else { + 0 + }, + // Source: https://microsoft.github.io/DirectX-Specs/d3d/MeshShader.html#amplification-shader-and-mesh-shader + max_mesh_invocations_per_workgroup: if mesh_shader_supported { 128 } else { 0 }, + max_mesh_invocations_per_dimension: if mesh_shader_supported { 128 } else { 0 }, + + max_task_payload_size: if mesh_shader_supported { 16384 } else { 0 }, + max_mesh_output_vertices: if mesh_shader_supported { 256 } else { 0 }, + max_mesh_output_primitives: if mesh_shader_supported { 256 } else { 0 }, + // Source: https://microsoft.github.io/DirectX-Specs/d3d/MeshShader.html#sv_rendertargetarrayindex-limitations-based-on-queryable-capability + max_mesh_output_layers: if mesh_shader_supported { 8 } else { 0 }, max_mesh_multiview_view_count: if mesh_shader_supported { max_multiview_view_count } else { 0 }, - // This seems to be right, and I can't find anything to suggest it would be less than the 2048 provided here - max_mesh_output_layers: Direct3D12::D3D12_REQ_TEXTURE2D_ARRAY_AXIS_DIMENSION, max_blas_primitive_count: if supports_ray_tracing { 1 << 29 // 2^29 @@ -774,7 +887,7 @@ impl super::Adapter { }, max_multiview_view_count, - }, + }), alignments: crate::Alignments { buffer_copy_offset: wgt::BufferSize::new( Direct3D12::D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT as u64, diff --git a/third_party/rust/wgpu-hal/src/dx12/command.rs b/third_party/rust/wgpu-hal/src/dx12/command.rs @@ -964,7 +964,7 @@ impl crate::CommandEncoder for super::CommandEncoder { self.pass.resolves.clear(); for (rtv, cat) in color_views.iter().zip(desc.color_attachments.iter()) { if let Some(cat) = cat.as_ref() { - if !cat.ops.contains(crate::AttachmentOps::LOAD) { + if cat.ops.contains(crate::AttachmentOps::LOAD_CLEAR) { let value = [ cat.clear_value.r as f32, cat.clear_value.g as f32, @@ -989,12 +989,12 @@ impl crate::CommandEncoder for super::CommandEncoder { if let Some(ref ds) = desc.depth_stencil_attachment { let mut flags = Direct3D12::D3D12_CLEAR_FLAGS::default(); let aspects = ds.target.view.aspects; - if !ds.depth_ops.contains(crate::AttachmentOps::LOAD) + if ds.depth_ops.contains(crate::AttachmentOps::LOAD_CLEAR) && aspects.contains(crate::FormatAspects::DEPTH) { flags |= Direct3D12::D3D12_CLEAR_FLAG_DEPTH; } - if !ds.stencil_ops.contains(crate::AttachmentOps::LOAD) + if ds.stencil_ops.contains(crate::AttachmentOps::LOAD_CLEAR) && aspects.contains(crate::FormatAspects::STENCIL) { flags |= Direct3D12::D3D12_CLEAR_FLAG_STENCIL; @@ -1192,10 +1192,9 @@ impl crate::CommandEncoder for super::CommandEncoder { self.reset_signature(&layout.shared); }; } - unsafe fn set_push_constants( + unsafe fn set_immediates( &mut self, layout: &super::PipelineLayout, - _stages: wgt::ShaderStages, offset_bytes: u32, data: &[u32], ) { diff --git a/third_party/rust/wgpu-hal/src/dx12/device.rs b/third_party/rust/wgpu-hal/src/dx12/device.rs @@ -362,7 +362,7 @@ impl super::Device { (source, entry_point) }; - log::info!( + log::debug!( "Naga generated shader for {entry_point:?} at {naga_stage:?}:\n{source}" ); @@ -867,7 +867,7 @@ impl crate::Device for super::Device { use naga::back::hlsl; // Pipeline layouts are implemented as RootSignature for D3D12. // - // Push Constants are implemented as root constants. + // Immediates are implemented as root constants. // // Each bind group layout will be one table entry of the root signature. // We have the additional restriction that SRV/CBV/UAV and samplers need to be @@ -908,20 +908,12 @@ impl crate::Device for super::Device { let mut bind_srv = hlsl::BindTarget::default(); let mut bind_uav = hlsl::BindTarget::default(); let mut parameters = Vec::new(); - let mut push_constants_target = None; + let mut immediates_target = None; let mut root_constant_info = None; - let mut pc_start = u32::MAX; - let mut pc_end = u32::MIN; - - for pc in desc.push_constant_ranges.iter() { - pc_start = pc_start.min(pc.range.start); - pc_end = pc_end.max(pc.range.end); - } - - if pc_start != u32::MAX && pc_end != u32::MIN { + if desc.immediate_size != 0 { let parameter_index = parameters.len(); - let size = (pc_end - pc_start) / 4; + let size = desc.immediate_size / 4; parameters.push(Direct3D12::D3D12_ROOT_PARAMETER { ParameterType: Direct3D12::D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS, Anonymous: Direct3D12::D3D12_ROOT_PARAMETER_0 { @@ -937,9 +929,9 @@ impl crate::Device for super::Device { bind_cbv.register += 1; root_constant_info = Some(super::RootConstantInfo { root_index: parameter_index as u32, - range: (pc_start / 4)..(pc_end / 4), + range: 0..size, }); - push_constants_target = Some(binding); + immediates_target = Some(binding); bind_cbv.space += 1; } @@ -1333,7 +1325,14 @@ impl crate::Device for super::Device { ShaderVisibility: Direct3D12::D3D12_SHADER_VISIBILITY_ALL, // really needed for VS and CS only, }); let binding = bind_cbv; - bind_cbv.register += 1; + // This is the last time we use this, but lets increment + // it so if we add more later, the value behaves correctly. + + // This is an allow as it doesn't trigger on 1.82, hal's MSRV. + #[allow(unused_assignments)] + { + bind_cbv.register += 1; + } (Some(parameter_index as u32), Some(binding)) } else { (None, None) @@ -1481,7 +1480,7 @@ impl crate::Device for super::Device { binding_map, fake_missing_bindings: false, special_constants_binding, - push_constants_target, + immediates_target, dynamic_storage_buffer_offsets_targets, zero_initialize_workgroup_memory: true, restrict_indexing: true, @@ -1953,8 +1952,7 @@ impl crate::Device for super::Device { }; let flags = Direct3D12::D3D12_PIPELINE_STATE_FLAG_NONE; - let mut view_instancing = - core::pin::pin!(ArrayVec::<Direct3D12::D3D12_VIEW_INSTANCE_LOCATION, 32>::new()); + let mut view_instancing = ArrayVec::<Direct3D12::D3D12_VIEW_INSTANCE_LOCATION, 32>::new(); if let Some(mask) = desc.multiview_mask { let mask = mask.get(); // This array is just what _could_ be rendered to. We actually apply the mask at @@ -1969,6 +1967,9 @@ impl crate::Device for super::Device { } } + // Borrow view instancing slice, so we can be sure that it won't be moved while we have pointers into this buffer. + let view_instancing_slice = view_instancing.as_slice(); + let mut stream_desc = RenderPipelineStateStreamDesc { // Shared by vertex and mesh pipelines root_signature: desc.layout.shared.signature.as_ref(), @@ -1987,10 +1988,10 @@ impl crate::Device for super::Device { node_mask: 0, cached_pso, flags, - view_instancing: if !view_instancing.is_empty() { + view_instancing: if !view_instancing_slice.is_empty() { Some(Direct3D12::D3D12_VIEW_INSTANCING_DESC { - ViewInstanceCount: view_instancing.len() as u32, - pViewInstanceLocations: view_instancing.as_ptr(), + ViewInstanceCount: view_instancing_slice.len() as u32, + pViewInstanceLocations: view_instancing_slice.as_ptr(), // This lets us hide/mask certain values later, at renderpass creation time. Flags: Direct3D12::D3D12_VIEW_INSTANCING_FLAG_ENABLE_VIEW_INSTANCE_MASKING, }) diff --git a/third_party/rust/wgpu-hal/src/dx12/instance.rs b/third_party/rust/wgpu-hal/src/dx12/instance.rs @@ -112,6 +112,7 @@ impl crate::Instance for super::Instance { memory_budget_thresholds: desc.memory_budget_thresholds, compiler_container: Arc::new(compiler_container), options: desc.backend_options.dx12.clone(), + telemetry: desc.telemetry, }) } @@ -164,6 +165,7 @@ impl crate::Instance for super::Instance { self.memory_budget_thresholds, self.compiler_container.clone(), self.options.clone(), + self.telemetry, ) }) .collect() diff --git a/third_party/rust/wgpu-hal/src/dx12/mod.rs b/third_party/rust/wgpu-hal/src/dx12/mod.rs @@ -54,7 +54,7 @@ the limit is merely 2048 unique samplers in existence, which is much more reason ## Resource binding -See ['Device::create_pipeline_layout`] documentation for the structure +See [`crate::Device::create_pipeline_layout`] documentation for the structure of the root signature corresponding to WebGPU pipeline layout. Binding groups is mostly straightforward, with one big caveat: @@ -149,6 +149,13 @@ struct D3D12Lib { lib: DynLib, } +#[derive(Clone, Copy)] +pub enum CreateDeviceError { + GetProcAddress, + D3D12CreateDevice(windows_core::HRESULT), + RetDeviceIsNull, +} + impl D3D12Lib { fn new() -> Result<Self, libloading::Error> { unsafe { DynLib::new("d3d12.dll").map(|lib| Self { lib }) } @@ -158,7 +165,7 @@ impl D3D12Lib { &self, adapter: &DxgiAdapter, feature_level: Direct3D::D3D_FEATURE_LEVEL, - ) -> Result<Option<Direct3D12::ID3D12Device>, crate::DeviceError> { + ) -> Result<Direct3D12::ID3D12Device, CreateDeviceError> { // Calls windows::Win32::Graphics::Direct3D12::D3D12CreateDevice on d3d12.dll type Fun = extern "system" fn( padapter: *mut ffi::c_void, @@ -167,7 +174,8 @@ impl D3D12Lib { ppdevice: *mut *mut ffi::c_void, ) -> windows_core::HRESULT; let func: libloading::Symbol<Fun> = - unsafe { self.lib.get(c"D3D12CreateDevice".to_bytes()) }?; + unsafe { self.lib.get(c"D3D12CreateDevice".to_bytes()) } + .map_err(|_| CreateDeviceError::GetProcAddress)?; let mut result__: Option<Direct3D12::ID3D12Device> = None; @@ -177,20 +185,13 @@ impl D3D12Lib { // TODO: Generic? &Direct3D12::ID3D12Device::IID, <*mut _>::cast(&mut result__), - ) - .ok(); + ); - if let Err(ref err) = res { - match err.code() { - Dxgi::DXGI_ERROR_UNSUPPORTED => return Ok(None), - Dxgi::DXGI_ERROR_DRIVER_INTERNAL_ERROR => return Err(crate::DeviceError::Lost), - _ => {} - } + if res.is_err() { + return Err(CreateDeviceError::D3D12CreateDevice(res)); } - res.into_device_result("Device creation")?; - - result__.ok_or(crate::DeviceError::Unexpected).map(Some) + result__.ok_or(CreateDeviceError::RetDeviceIsNull) } fn serialize_root_signature( @@ -474,6 +475,7 @@ pub struct Instance { memory_budget_thresholds: wgt::MemoryBudgetThresholds, compiler_container: Arc<shader_compilation::CompilerContainer>, options: wgt::Dx12BackendOptions, + telemetry: Option<crate::Telemetry>, } impl Instance { @@ -1616,3 +1618,27 @@ pub enum ShaderModuleSource { DxilPassthrough(DxilPassthroughShader), HlslPassthrough(HlslPassthroughShader), } + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum FeatureLevel { + _11_0, + _11_1, + _12_0, + _12_1, + _12_2, +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum ShaderModel { + _5_1, + _6_0, + _6_1, + _6_2, + _6_3, + _6_4, + _6_5, + _6_6, + _6_7, + _6_8, + _6_9, +} diff --git a/third_party/rust/wgpu-hal/src/dx12/suballocation.rs b/third_party/rust/wgpu-hal/src/dx12/suballocation.rs @@ -7,6 +7,7 @@ use windows::Win32::Graphics::{Direct3D12, Dxgi}; use crate::{ auxil::dxgi::{name::ObjectExt as _, result::HResult as _}, dx12::conv, + AllocationSizes, }; #[derive(Debug)] @@ -74,36 +75,14 @@ impl Allocator { memory_hints: &wgt::MemoryHints, memory_budget_thresholds: wgt::MemoryBudgetThresholds, ) -> Result<Self, crate::DeviceError> { - // TODO: the allocator's configuration should take hardware capability into - // account. - const MB: u64 = 1024 * 1024; - let (device_memblock_size, host_memblock_size) = match memory_hints { - wgt::MemoryHints::Performance => (256 * MB, 64 * MB), - wgt::MemoryHints::MemoryUsage => (8 * MB, 4 * MB), - wgt::MemoryHints::Manual { - suballocated_device_memory_block_size, - } => { - // TODO: Would it be useful to expose the host size in memory hints - // instead of always using half of the device size? - let device_size = suballocated_device_memory_block_size.start; - let host_size = device_size / 2; - (device_size, host_size) - } - }; - - // gpu_allocator clamps the sizes between 4MiB and 256MiB, but we clamp them ourselves since we use - // the sizes when detecting high memory pressure and there is no way to query the values otherwise. - - let device_memblock_size = device_memblock_size.clamp(4 * MB, 256 * MB); - let host_memblock_size = host_memblock_size.clamp(4 * MB, 256 * MB); - - let allocation_sizes = - gpu_allocator::AllocationSizes::new(device_memblock_size, host_memblock_size); + let allocation_sizes = AllocationSizes::from_memory_hints(memory_hints); + let device_memblock_size = allocation_sizes.min_device_memblock_size; + let host_memblock_size = allocation_sizes.min_host_memblock_size; let allocator_desc = gpu_allocator::d3d12::AllocatorCreateDesc { device: gpu_allocator::d3d12::ID3D12DeviceVersion::Device(raw.clone()), debug_settings: Default::default(), - allocation_sizes, + allocation_sizes: allocation_sizes.into(), }; let allocator = gpu_allocator::d3d12::Allocator::new(&allocator_desc).inspect_err(|e| { @@ -622,37 +601,3 @@ impl<'a> DeviceAllocationContext<'a> { Ok(allocation_info) } } - -impl From<gpu_allocator::AllocationError> for crate::DeviceError { - fn from(result: gpu_allocator::AllocationError) -> Self { - match result { - gpu_allocator::AllocationError::OutOfMemory => Self::OutOfMemory, - gpu_allocator::AllocationError::FailedToMap(e) => { - log::error!("DX12 gpu-allocator: Failed to map: {e}"); - Self::Lost - } - gpu_allocator::AllocationError::NoCompatibleMemoryTypeFound => { - log::error!("DX12 gpu-allocator: No Compatible Memory Type Found"); - Self::Lost - } - gpu_allocator::AllocationError::InvalidAllocationCreateDesc => { - log::error!("DX12 gpu-allocator: Invalid Allocation Creation Description"); - Self::Lost - } - gpu_allocator::AllocationError::InvalidAllocatorCreateDesc(e) => { - log::error!("DX12 gpu-allocator: Invalid Allocator Creation Description: {e}"); - Self::Lost - } - - gpu_allocator::AllocationError::Internal(e) => { - log::error!("DX12 gpu-allocator: Internal Error: {e}"); - Self::Lost - } - gpu_allocator::AllocationError::BarrierLayoutNeedsDevice10 - | gpu_allocator::AllocationError::CastableFormatsRequiresEnhancedBarriers - | gpu_allocator::AllocationError::CastableFormatsRequiresAtLeastDevice12 => { - unreachable!() - } - } - } -} diff --git a/third_party/rust/wgpu-hal/src/dynamic/command.rs b/third_party/rust/wgpu-hal/src/dynamic/command.rs @@ -66,10 +66,9 @@ pub trait DynCommandEncoder: DynResource + core::fmt::Debug { dynamic_offsets: &[wgt::DynamicOffset], ); - unsafe fn set_push_constants( + unsafe fn set_immediates( &mut self, layout: &dyn DynPipelineLayout, - stages: wgt::ShaderStages, offset_bytes: u32, data: &[u32], ); @@ -329,15 +328,14 @@ impl<C: CommandEncoder + DynResource> DynCommandEncoder for C { unsafe { C::set_bind_group(self, layout, index, group, dynamic_offsets) }; } - unsafe fn set_push_constants( + unsafe fn set_immediates( &mut self, layout: &dyn DynPipelineLayout, - stages: wgt::ShaderStages, offset_bytes: u32, data: &[u32], ) { let layout = layout.expect_downcast_ref(); - unsafe { C::set_push_constants(self, layout, stages, offset_bytes, data) }; + unsafe { C::set_immediates(self, layout, offset_bytes, data) }; } unsafe fn insert_debug_marker(&mut self, label: &str) { diff --git a/third_party/rust/wgpu-hal/src/dynamic/device.rs b/third_party/rust/wgpu-hal/src/dynamic/device.rs @@ -295,7 +295,7 @@ impl<D: Device + DynResource> DynDevice for D { let desc = PipelineLayoutDescriptor { label: desc.label, bind_group_layouts: &bind_group_layouts, - push_constant_ranges: desc.push_constant_ranges, + immediate_size: desc.immediate_size, flags: desc.flags, }; diff --git a/third_party/rust/wgpu-hal/src/gles/adapter.rs b/third_party/rust/wgpu-hal/src/gles/adapter.rs @@ -190,6 +190,8 @@ impl super::Adapter { device_pci_bus_id: String::new(), driver_info: version, backend: wgt::Backend::Gl, + subgroup_min_size: wgt::MINIMUM_SUBGROUP_MIN_SIZE, + subgroup_max_size: wgt::MAXIMUM_SUBGROUP_MAX_SIZE, transient_saves_memory: false, } } @@ -309,10 +311,11 @@ impl super::Adapter { es_supported || full_supported }; - let supports_storage = - supported((3, 1), (4, 3)) || extensions.contains("GL_ARB_shader_storage_buffer_object"); - let supports_compute = - supported((3, 1), (4, 3)) || extensions.contains("GL_ARB_compute_shader"); + // Naga won't let you emit storage buffers at versions below this, so + // we currently can't support GL_ARB_shader_storage_buffer_object. + let supports_storage = supported((3, 1), (4, 3)); + // Same with compute shaders and GL_ARB_compute_shader + let supports_compute = supported((3, 1), (4, 3)); let supports_work_group_params = supports_compute; // ANGLE provides renderer strings like: "ANGLE (Apple, Apple M1 Pro, OpenGL 4.1)" @@ -327,7 +330,7 @@ impl super::Adapter { // Windows doesn't recognize `GL_MAX_VERTEX_ATTRIB_STRIDE`. let new = (unsafe { gl.get_parameter_i32(glow::MAX_COMPUTE_SHADER_STORAGE_BLOCKS) } as u32); - log::warn!("Max vertex shader storage blocks is zero, but GL_ARB_shader_storage_buffer_object is specified. Assuming the compute value {new}"); + log::debug!("Max vertex shader storage blocks is zero, but GL_ARB_shader_storage_buffer_object is specified. Assuming the compute value {new}"); new } else { value @@ -366,7 +369,7 @@ impl super::Adapter { vertex_shader_storage_blocks == 0 && vertex_shader_storage_textures != 0; if vertex_ssbo_false_zero { // We only care about fragment here as the 0 is a lie. - log::warn!("Max vertex shader SSBO == 0 and SSTO != 0. Interpreting as false zero."); + log::debug!("Max vertex shader SSBO == 0 and SSTO != 0. Interpreting as false zero."); } let max_storage_buffers_per_shader_stage = if vertex_shader_storage_blocks == 0 { @@ -443,7 +446,7 @@ impl super::Adapter { let mut features = wgt::Features::empty() | wgt::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES | wgt::Features::CLEAR_TEXTURE - | wgt::Features::PUSH_CONSTANTS + | wgt::Features::IMMEDIATES | wgt::Features::DEPTH32FLOAT_STENCIL8; features.set( wgt::Features::ADDRESS_MODE_CLAMP_TO_BORDER | wgt::Features::ADDRESS_MODE_CLAMP_TO_ZERO, @@ -677,15 +680,14 @@ impl super::Adapter { let max_color_attachments = unsafe { gl.get_parameter_i32(glow::MAX_COLOR_ATTACHMENTS) - .min(gl.get_parameter_i32(glow::MAX_DRAW_BUFFERS)) - .min(crate::MAX_COLOR_ATTACHMENTS as i32) as u32 + .min(gl.get_parameter_i32(glow::MAX_DRAW_BUFFERS)) as u32 }; // 16 bytes per sample is the maximum size of a color attachment. let max_color_attachment_bytes_per_sample = max_color_attachments * wgt::TextureFormat::MAX_TARGET_PIXEL_BYTE_COST; - let limits = wgt::Limits { + let limits = crate::auxil::apply_hal_limits(wgt::Limits { max_texture_dimension_1d: max_texture_size, max_texture_dimension_2d: max_texture_size, max_texture_dimension_3d: max_texture_3d_size, @@ -717,8 +719,7 @@ impl super::Adapter { (unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_ATTRIB_BINDINGS) } as u32) } else { 16 // should this be different? - } - .min(crate::MAX_VERTEX_BUFFERS as u32), + }, max_vertex_attributes: (unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_ATTRIBS) } as u32) .min(super::MAX_VERTEX_ATTRIBUTES as u32), @@ -736,13 +737,13 @@ impl super::Adapter { // This should be at least 2048, but the driver for AMD Radeon HD 5870 on // Windows doesn't recognize `GL_MAX_VERTEX_ATTRIB_STRIDE`. - log::warn!("Max vertex attribute stride is 0. Assuming it is 2048"); + log::debug!("Max vertex attribute stride is 0. Assuming it is the OpenGL minimum spec 2048"); 2048 } else { value } } else { - log::warn!("Max vertex attribute stride unknown. Assuming it is 2048"); + log::debug!("Max vertex attribute stride unknown. Assuming it is the OpenGL minimum spec 2048"); 2048 } } else { @@ -751,9 +752,7 @@ impl super::Adapter { } else { !0 }, - min_subgroup_size: 0, - max_subgroup_size: 0, - max_push_constant_size: super::MAX_PUSH_CONSTANTS as u32 * 4, + max_immediate_size: super::MAX_IMMEDIATES as u32 * 4, min_uniform_buffer_offset_alignment, min_storage_buffer_offset_alignment, max_inter_stage_shader_components: { @@ -803,10 +802,17 @@ impl super::Adapter { max_buffer_size: i32::MAX as u64, max_non_sampler_bindings: u32::MAX, - max_task_workgroup_total_count: 0, - max_task_workgroups_per_dimension: 0, - max_mesh_multiview_view_count: 0, + max_task_mesh_workgroup_total_count: 0, + max_task_mesh_workgroups_per_dimension: 0, + max_task_invocations_per_workgroup: 0, + max_task_invocations_per_dimension: 0, + max_mesh_invocations_per_workgroup: 0, + max_mesh_invocations_per_dimension: 0, + max_task_payload_size: 0, + max_mesh_output_vertices: 0, + max_mesh_output_primitives: 0, max_mesh_output_layers: 0, + max_mesh_multiview_view_count: 0, max_blas_primitive_count: 0, max_blas_geometry_count: 0, @@ -814,7 +820,7 @@ impl super::Adapter { max_acceleration_structures_per_shader_stage: 0, max_multiview_view_count: 0, - }; + }); let mut workarounds = super::Workarounds::empty(); @@ -832,7 +838,7 @@ impl super::Adapter { && r.split(&[' ', '(', ')'][..]) .any(|substr| substr.len() == 3 && substr.chars().nth(2) == Some('l')) { - log::warn!( + log::debug!( "Detected skylake derivative running on mesa i915. Clears to srgb textures will \ use manual shader clears." ); @@ -955,7 +961,7 @@ impl super::Adapter { let linked_ok = unsafe { gl.get_program_link_status(program) }; let msg = unsafe { gl.get_program_info_log(program) }; if !msg.is_empty() { - log::warn!("Shader link error: {msg}"); + log::error!("Shader link error: {msg}"); } if !linked_ok { return None; diff --git a/third_party/rust/wgpu-hal/src/gles/command.rs b/third_party/rust/wgpu-hal/src/gles/command.rs @@ -33,9 +33,9 @@ pub(super) struct State { dirty_vbuf_mask: usize, active_first_instance: u32, first_instance_location: Option<glow::UniformLocation>, - push_constant_descs: ArrayVec<super::PushConstantDesc, { super::MAX_PUSH_CONSTANT_COMMANDS }>, - // The current state of the push constant data block. - current_push_constant_data: [u32; super::MAX_PUSH_CONSTANTS], + immediates_descs: ArrayVec<super::ImmediateDesc, { super::MAX_IMMEDIATES_COMMANDS }>, + // The current state of the immediate data block. + current_immediates_data: [u32; super::MAX_IMMEDIATES], end_of_pass_timestamp: Option<glow::Query>, clip_distance_count: u32, } @@ -63,8 +63,8 @@ impl Default for State { dirty_vbuf_mask: Default::default(), active_first_instance: Default::default(), first_instance_location: Default::default(), - push_constant_descs: Default::default(), - current_push_constant_data: [0; super::MAX_PUSH_CONSTANTS], + immediates_descs: Default::default(), + current_immediates_data: [0; super::MAX_IMMEDIATES], end_of_pass_timestamp: Default::default(), clip_distance_count: Default::default(), } @@ -85,7 +85,7 @@ impl super::CommandBuffer { start..self.data_bytes.len() as u32 } - fn add_push_constant_data(&mut self, data: &[u32]) -> Range<u32> { + fn add_immediates_data(&mut self, data: &[u32]) -> Range<u32> { let data_raw = bytemuck::cast_slice(data); let start = self.data_bytes.len(); assert!(start < u32::MAX as usize); @@ -235,8 +235,8 @@ impl super::CommandEncoder { .first_instance_location .clone_from(&inner.first_instance_location); self.state - .push_constant_descs - .clone_from(&inner.push_constant_descs); + .immediates_descs + .clone_from(&inner.immediates_descs); // rebind textures, if needed let mut dirty_textures = 0u32; @@ -567,7 +567,7 @@ impl crate::CommandEncoder for super::CommandEncoder { .resolve_attachments .push((attachment, rat.view.clone())); } - if !cat.ops.contains(crate::AttachmentOps::STORE) { + if cat.ops.contains(crate::AttachmentOps::STORE_DISCARD) { self.state.invalidate_attachments.push(attachment); } } @@ -585,14 +585,16 @@ impl crate::CommandEncoder for super::CommandEncoder { depth_slice: None, }); if aspects.contains(crate::FormatAspects::DEPTH) - && !dsat.depth_ops.contains(crate::AttachmentOps::STORE) + && dsat.depth_ops.contains(crate::AttachmentOps::STORE_DISCARD) { self.state .invalidate_attachments .push(glow::DEPTH_ATTACHMENT); } if aspects.contains(crate::FormatAspects::STENCIL) - && !dsat.stencil_ops.contains(crate::AttachmentOps::STORE) + && dsat + .stencil_ops + .contains(crate::AttachmentOps::STORE_DISCARD) { self.state .invalidate_attachments @@ -628,7 +630,7 @@ impl crate::CommandEncoder for super::CommandEncoder { .filter_map(|at| at.as_ref()) .enumerate() { - if !cat.ops.contains(crate::AttachmentOps::LOAD) { + if cat.ops.contains(crate::AttachmentOps::LOAD_CLEAR) { let c = &cat.clear_value; self.cmd_buffer.commands.push( match cat.target.view.format.sample_type(None, None).unwrap() { @@ -652,8 +654,8 @@ impl crate::CommandEncoder for super::CommandEncoder { } if let Some(ref dsat) = desc.depth_stencil_attachment { - let clear_depth = !dsat.depth_ops.contains(crate::AttachmentOps::LOAD); - let clear_stencil = !dsat.stencil_ops.contains(crate::AttachmentOps::LOAD); + let clear_depth = dsat.depth_ops.contains(crate::AttachmentOps::LOAD_CLEAR); + let clear_stencil = dsat.stencil_ops.contains(crate::AttachmentOps::LOAD_CLEAR); if clear_depth && clear_stencil { self.cmd_buffer.commands.push(C::ClearDepthAndStencil( @@ -787,31 +789,30 @@ impl crate::CommandEncoder for super::CommandEncoder { self.rebind_sampler_states(dirty_textures, dirty_samplers); } - unsafe fn set_push_constants( + unsafe fn set_immediates( &mut self, _layout: &super::PipelineLayout, - _stages: wgt::ShaderStages, offset_bytes: u32, data: &[u32], ) { // 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 + // a vector or matrix in the set_immediates call, as to the user, all of this is // just memory. However OpenGL does not allow partial uniform updates. // - // As such, we locally keep a copy of the current state of the push constant memory + // As such, we locally keep a copy of the current state of the immediate data 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] + self.state.current_immediates_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). + // updating from the same immediate data 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() { + for uniform in self.state.immediates_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; @@ -821,12 +822,12 @@ impl crate::CommandEncoder for super::CommandEncoder { start_words < uniform_end_words || uniform_start_words <= end_words; if needs_updating { - let uniform_data = &self.state.current_push_constant_data + let uniform_data = &self.state.current_immediates_data [uniform_start_words as usize..uniform_end_words as usize]; - let range = self.cmd_buffer.add_push_constant_data(uniform_data); + let range = self.cmd_buffer.add_immediates_data(uniform_data); - self.cmd_buffer.commands.push(C::SetPushConstants { + self.cmd_buffer.commands.push(C::SetImmediates { uniform, offset: range.start, }); diff --git a/third_party/rust/wgpu-hal/src/gles/conv.rs b/third_party/rust/wgpu-hal/src/gles/conv.rs @@ -223,7 +223,7 @@ pub(super) fn describe_vertex_format(vertex_format: wgt::VertexFormat) -> super: Vf::Uint32x4 => (4, glow::UNSIGNED_INT, Vak::Integer), Vf::Sint32x4 => (4, glow::INT, Vak::Integer), Vf::Float32x4 => (4, glow::FLOAT, Vak::Float), - Vf::Unorm10_10_10_2 => (4, glow::UNSIGNED_INT_10_10_10_2, Vak::Float), + Vf::Unorm10_10_10_2 => (4, glow::UNSIGNED_INT_2_10_10_10_REV, Vak::Float), Vf::Unorm8x4Bgra => (glow::BGRA as i32, glow::UNSIGNED_BYTE, Vak::Float), Vf::Float64 | Vf::Float64x2 | Vf::Float64x3 | Vf::Float64x4 => unimplemented!(), }; diff --git a/third_party/rust/wgpu-hal/src/gles/device.rs b/third_party/rust/wgpu-hal/src/gles/device.rs @@ -21,7 +21,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<naga::back::glsl::PushConstantItem>, + immediates_items: &'a mut Vec<naga::back::glsl::ImmediateItem>, multiview_mask: Option<NonZeroU32>, clip_distance_count: &'a mut u32, } @@ -103,7 +103,7 @@ impl CompilationContext<'_> { } } - *self.push_constant_items = reflection_info.push_constant_items; + *self.immediates_items = reflection_info.immediates_items; if naga_stage == naga::ShaderStage::Vertex { *self.clip_distance_count = reflection_info.clip_distance_count; @@ -194,7 +194,7 @@ impl super::Device { let msg = unsafe { gl.get_shader_info_log(raw) }; if compiled_ok { if !msg.is_empty() { - log::warn!("\tCompile: {msg}"); + log::debug!("\tCompile message: {msg}"); } Ok(raw) } else { @@ -375,7 +375,7 @@ impl super::Device { } let mut name_binding_map = NameBindingMap::default(); - let mut push_constant_items = ArrayVec::<_, { crate::MAX_CONCURRENT_SHADER_STAGES }>::new(); + let mut immediates_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::<_, { crate::MAX_CONCURRENT_SHADER_STAGES }>::new(); @@ -384,14 +384,14 @@ impl super::Device { 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() + immediates_items.push(Vec::new()); + immediates_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, + immediates_items: pc_item, multiview_mask, clip_distance_count: &mut clip_distance_count, }; @@ -403,7 +403,7 @@ impl super::Device { // Create empty fragment shader if only vertex shader is present if has_stages == wgt::ShaderStages::VERTEX { let shader_src = format!("#version {glsl_version}\n void main(void) {{}}",); - log::info!("Only vertex shader is present. Creating an empty fragment shader",); + log::debug!("Only vertex shader is present. Creating an empty fragment shader",); let shader = unsafe { Self::compile_shader( gl, @@ -432,7 +432,7 @@ impl super::Device { return Err(crate::PipelineError::Linkage(has_stages, msg)); } if !msg.is_empty() { - log::warn!("\tLink: {msg}"); + log::debug!("\tLink message: {msg}"); } if !private_caps.contains(PrivateCapabilities::SHADER_BINDING_LAYOUT) { @@ -463,7 +463,7 @@ impl super::Device { let mut uniforms = ArrayVec::new(); - for (stage_idx, stage_items) in push_constant_items.into_iter().enumerate() { + for (stage_idx, stage_items) in immediates_items.into_iter().enumerate() { for item in stage_items { let naga_module = &shaders[stage_idx].1.module.source.module; let type_inner = &naga_module.types[item.ty].inner; @@ -471,7 +471,7 @@ impl super::Device { let location = unsafe { gl.get_uniform_location(program, &item.access_path) }; log::trace!( - "push constant item: name={}, ty={:?}, offset={}, location={:?}", + "immediate data item: name={}, ty={:?}, offset={}, location={:?}", item.access_path, type_inner, item.offset, @@ -479,7 +479,7 @@ impl super::Device { ); if let Some(location) = location { - uniforms.push(super::PushConstantDesc { + uniforms.push(super::ImmediateDesc { location, offset: item.offset, size_bytes: type_inner.size(naga_module.to_ctx()), @@ -500,7 +500,7 @@ impl super::Device { program, sampler_map, first_instance_location, - push_constant_descs: uniforms, + immediates_descs: uniforms, clip_distance_count, })) } diff --git a/third_party/rust/wgpu-hal/src/gles/egl.rs b/third_party/rust/wgpu-hal/src/gles/egl.rs @@ -7,7 +7,7 @@ use hashbrown::HashMap; use parking_lot::{MappedMutexGuard, Mutex, MutexGuard, RwLock}; /// The amount of time to wait while trying to obtain a lock to the adapter context -const CONTEXT_LOCK_TIMEOUT_SECS: u64 = 1; +const CONTEXT_LOCK_TIMEOUT_SECS: u64 = 6; const EGL_CONTEXT_FLAGS_KHR: i32 = 0x30FC; const EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR: i32 = 0x0001; @@ -88,8 +88,11 @@ unsafe extern "system" fn egl_debug_proc( let log_severity = match message_type { EGL_DEBUG_MSG_CRITICAL_KHR | EGL_DEBUG_MSG_ERROR_KHR => log::Level::Error, EGL_DEBUG_MSG_WARN_KHR => log::Level::Warn, - EGL_DEBUG_MSG_INFO_KHR => log::Level::Info, - _ => log::Level::Debug, + // We intentionally suppress info messages down to debug + // so that users are not innundated with info messages from + // the runtime. + EGL_DEBUG_MSG_INFO_KHR => log::Level::Debug, + _ => log::Level::Trace, }; let command = unsafe { ffi::CStr::from_ptr(command_raw) }.to_string_lossy(); let message = if message_raw.is_null() { @@ -263,7 +266,7 @@ fn choose_config( if tier_max == 1 { //Note: this has been confirmed to malfunction on Intel+NV laptops, // but also on Angle. - log::warn!("EGL says it can present to the window but not natively",); + log::info!("EGL says it can present to the window but not natively",); } // Android emulator can't natively present either. let tier_threshold = @@ -275,7 +278,7 @@ fn choose_config( return Ok((config, tier_max >= tier_threshold)); } Ok(None) => { - log::warn!("No config found!"); + log::debug!("No config found!"); } Err(e) => { log::error!("error in choose_first_config: {e:?}"); @@ -545,7 +548,7 @@ impl Inner { log::debug!("\tEGL surface: +srgb khr"); SrgbFrameBufferKind::Khr } else { - log::warn!("\tEGL surface: -srgb"); + log::debug!("\tEGL surface: -srgb"); SrgbFrameBufferKind::None }; @@ -902,7 +905,7 @@ impl crate::Instance for Instance { let (display, display_owner, wsi_kind) = if let (Some(library), Some(egl)) = (wayland_library, egl1_5) { - log::info!("Using Wayland platform"); + log::debug!("Using Wayland platform"); let display_attributes = [khronos_egl::ATTRIB_NONE]; let display = unsafe { egl.get_platform_display( @@ -914,7 +917,7 @@ impl crate::Instance for Instance { .map_err(instance_err("failed to get Wayland display"))?; (display, Some(Rc::new(library)), WindowKind::Wayland) } else if let (Some(display_owner), Some(egl)) = (x11_display_library, egl1_5) { - log::info!("Using X11 platform"); + log::debug!("Using X11 platform"); let display_attributes = [khronos_egl::ATTRIB_NONE]; let display = unsafe { egl.get_platform_display( @@ -926,7 +929,7 @@ impl crate::Instance for Instance { .map_err(instance_err("failed to get x11 display"))?; (display, Some(Rc::new(display_owner)), WindowKind::X11) } else if let (Some(display_owner), Some(egl)) = (angle_x11_display_library, egl1_5) { - log::info!("Using Angle platform with X11"); + log::debug!("Using Angle platform with X11"); let display_attributes = [ EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE as khronos_egl::Attrib, EGL_PLATFORM_X11_KHR as khronos_egl::Attrib, @@ -944,7 +947,7 @@ impl crate::Instance for Instance { .map_err(instance_err("failed to get Angle display"))?; (display, Some(Rc::new(display_owner)), WindowKind::AngleX11) } else if client_ext_str.contains("EGL_MESA_platform_surfaceless") { - log::warn!("No windowing system present. Using surfaceless platform"); + log::debug!("No windowing system present. Using surfaceless platform"); #[allow(clippy::unnecessary_literal_unwrap)] // This is only a literal on Emscripten let egl = egl1_5.expect("Failed to get EGL 1.5 for surfaceless"); let display = unsafe { @@ -958,7 +961,7 @@ impl crate::Instance for Instance { (display, None, WindowKind::Unknown) } else { - log::warn!("EGL_MESA_platform_surfaceless not available. Using default platform"); + log::debug!("EGL_MESA_platform_surfaceless not available. Using default platform"); let display = unsafe { egl.get_display(khronos_egl::DEFAULT_DISPLAY) } .ok_or_else(|| crate::InstanceError::new("Failed to get default display".into()))?; (display, None, WindowKind::Unknown) diff --git a/third_party/rust/wgpu-hal/src/gles/mod.rs b/third_party/rust/wgpu-hal/src/gles/mod.rs @@ -136,9 +136,9 @@ const MAX_TEXTURE_SLOTS: usize = 16; 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; +const MAX_IMMEDIATES: usize = 64; +// We have to account for each immediate data may need to be set for every shader. +const MAX_IMMEDIATES_COMMANDS: usize = MAX_IMMEDIATES * crate::MAX_CONCURRENT_SHADER_STAGES; impl crate::Api for Api { const VARIANT: wgt::Backend = wgt::Backend::Gl; @@ -253,17 +253,12 @@ bitflags::bitflags! { type BindTarget = u32; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Default, Clone, Copy)] enum VertexAttribKind { + #[default] Float, // glVertexAttribPointer Integer, // glVertexAttribIPointer - //Double, // glVertexAttribLPointer -} - -impl Default for VertexAttribKind { - fn default() -> Self { - Self::Float - } + //Double, // glVertexAttribLPointer } #[derive(Clone, Debug)] @@ -653,7 +648,7 @@ struct VertexBufferDesc { } #[derive(Clone, Debug)] -struct PushConstantDesc { +struct ImmediateDesc { location: glow::UniformLocation, ty: naga::TypeInner, offset: u32, @@ -661,9 +656,9 @@ struct PushConstantDesc { } #[cfg(send_sync)] -unsafe impl Sync for PushConstantDesc {} +unsafe impl Sync for ImmediateDesc {} #[cfg(send_sync)] -unsafe impl Send for PushConstantDesc {} +unsafe impl Send for ImmediateDesc {} /// For each texture in the pipeline layout, store the index of the only /// sampler (in this layout) that the texture is used with. @@ -674,7 +669,7 @@ struct PipelineInner { program: glow::Program, sampler_map: SamplerBindMap, first_instance_location: Option<glow::UniformLocation>, - push_constant_descs: ArrayVec<PushConstantDesc, MAX_PUSH_CONSTANT_COMMANDS>, + immediates_descs: ArrayVec<ImmediateDesc, MAX_IMMEDIATES_COMMANDS>, clip_distance_count: u32, } @@ -1008,8 +1003,8 @@ enum Command { InsertDebugMarker(Range<u32>), PushDebugGroup(Range<u32>), PopDebugGroup, - SetPushConstants { - uniform: PushConstantDesc, + SetImmediates { + uniform: ImmediateDesc, /// Offset from the start of the `data_bytes` offset: u32, }, @@ -1083,7 +1078,7 @@ fn gl_debug_message_callback(source: u32, gltype: u32, id: u32, severity: u32, m let log_severity = match severity { glow::DEBUG_SEVERITY_HIGH => log::Level::Error, glow::DEBUG_SEVERITY_MEDIUM => log::Level::Warn, - glow::DEBUG_SEVERITY_LOW => log::Level::Info, + glow::DEBUG_SEVERITY_LOW => log::Level::Debug, glow::DEBUG_SEVERITY_NOTIFICATION => log::Level::Trace, _ => unreachable!(), }; diff --git a/third_party/rust/wgpu-hal/src/gles/queue.rs b/third_party/rust/wgpu-hal/src/gles/queue.rs @@ -1637,7 +1637,7 @@ impl super::Queue { } }; } - C::SetPushConstants { + C::SetImmediates { ref uniform, offset, } => { diff --git a/third_party/rust/wgpu-hal/src/gles/wgl.rs b/third_party/rust/wgpu-hal/src/gles/wgl.rs @@ -281,6 +281,21 @@ fn create_global_window_class() -> Result<CString, crate::InstanceError> { let name = format!("wgpu Device Class {:x}\0", class_addr as usize); let name = CString::from_vec_with_nul(name.into_bytes()).unwrap(); + // The window class may already be registered if we are a dynamic library that got + // unloaded & loaded back into the same process. If so, just skip creation. + let already_exists = unsafe { + let mut wc = mem::zeroed::<WindowsAndMessaging::WNDCLASSEXA>(); + WindowsAndMessaging::GetClassInfoExA( + Some(instance.into()), + PCSTR(name.as_ptr().cast()), + &mut wc, + ) + .is_ok() + }; + if already_exists { + return Ok(name); + } + // Use a wrapper function for compatibility with `windows-rs`. unsafe extern "system" fn wnd_proc( window: Foundation::HWND, diff --git a/third_party/rust/wgpu-hal/src/lib.rs b/third_party/rust/wgpu-hal/src/lib.rs @@ -381,6 +381,107 @@ pub enum DeviceError { Unexpected, } +#[cfg(any(dx12, vulkan))] +impl From<gpu_allocator::AllocationError> for DeviceError { + fn from(result: gpu_allocator::AllocationError) -> Self { + match result { + gpu_allocator::AllocationError::OutOfMemory => Self::OutOfMemory, + gpu_allocator::AllocationError::FailedToMap(e) => { + log::error!("gpu-allocator: Failed to map: {e}"); + Self::Lost + } + gpu_allocator::AllocationError::NoCompatibleMemoryTypeFound => { + log::error!("gpu-allocator: No Compatible Memory Type Found"); + Self::Lost + } + gpu_allocator::AllocationError::InvalidAllocationCreateDesc => { + log::error!("gpu-allocator: Invalid Allocation Creation Description"); + Self::Lost + } + gpu_allocator::AllocationError::InvalidAllocatorCreateDesc(e) => { + log::error!("gpu-allocator: Invalid Allocator Creation Description: {e}"); + Self::Lost + } + + gpu_allocator::AllocationError::Internal(e) => { + log::error!("gpu-allocator: Internal Error: {e}"); + Self::Lost + } + gpu_allocator::AllocationError::BarrierLayoutNeedsDevice10 + | gpu_allocator::AllocationError::CastableFormatsRequiresEnhancedBarriers + | gpu_allocator::AllocationError::CastableFormatsRequiresAtLeastDevice12 => { + unreachable!() + } + } + } +} + +// A copy of gpu_allocator::AllocationSizes, allowing to read the configured value for +// the dx12 backend, we should instead add getters to gpu_allocator::AllocationSizes +// and remove this type. +// https://github.com/Traverse-Research/gpu-allocator/issues/295 +#[cfg_attr(not(any(dx12, vulkan)), expect(dead_code))] +pub(crate) struct AllocationSizes { + pub(crate) min_device_memblock_size: u64, + pub(crate) max_device_memblock_size: u64, + pub(crate) min_host_memblock_size: u64, + pub(crate) max_host_memblock_size: u64, +} + +impl AllocationSizes { + #[allow(dead_code)] // may be unused on some platforms + pub(crate) fn from_memory_hints(memory_hints: &wgt::MemoryHints) -> Self { + // TODO: the allocator's configuration should take hardware capability into + // account. + const MB: u64 = 1024 * 1024; + + match memory_hints { + wgt::MemoryHints::Performance => Self { + min_device_memblock_size: 128 * MB, + max_device_memblock_size: 256 * MB, + min_host_memblock_size: 64 * MB, + max_host_memblock_size: 128 * MB, + }, + wgt::MemoryHints::MemoryUsage => Self { + min_device_memblock_size: 8 * MB, + max_device_memblock_size: 64 * MB, + min_host_memblock_size: 4 * MB, + max_host_memblock_size: 32 * MB, + }, + wgt::MemoryHints::Manual { + suballocated_device_memory_block_size, + } => { + // TODO: https://github.com/gfx-rs/wgpu/issues/8625 + // Would it be useful to expose the host size in memory hints + // instead of always using half of the device size? + let device_size = suballocated_device_memory_block_size; + let host_size = device_size.start / 2..device_size.end / 2; + + // gpu_allocator clamps the sizes between 4MiB and 256MiB, but we clamp them ourselves since we use + // the sizes when detecting high memory pressure and there is no way to query the values otherwise. + Self { + min_device_memblock_size: device_size.start.clamp(4 * MB, 256 * MB), + max_device_memblock_size: device_size.end.clamp(4 * MB, 256 * MB), + min_host_memblock_size: host_size.start.clamp(4 * MB, 256 * MB), + max_host_memblock_size: host_size.end.clamp(4 * MB, 256 * MB), + } + } + } + } +} + +#[cfg(any(dx12, vulkan))] +impl From<AllocationSizes> for gpu_allocator::AllocationSizes { + fn from(value: AllocationSizes) -> gpu_allocator::AllocationSizes { + gpu_allocator::AllocationSizes::new( + value.min_device_memblock_size, + value.min_host_memblock_size, + ) + .with_max_device_memblock_size(value.max_device_memblock_size) + .with_max_host_memblock_size(value.max_host_memblock_size) + } +} + #[allow(dead_code)] // may be unused on some platforms #[cold] fn hal_usage_error<T: fmt::Display>(txt: T) -> ! { @@ -1366,18 +1467,17 @@ pub trait CommandEncoder: WasmNotSendSync + fmt::Debug { dynamic_offsets: &[wgt::DynamicOffset], ); - /// Sets a range in push constant data. + /// Sets a range in immediate 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( + /// - The range of immediates written must be valid for the pipeline layout at draw time. + unsafe fn set_immediates( &mut self, layout: &<Self::A as Api>::PipelineLayout, - stages: wgt::ShaderStages, offset_bytes: u32, data: &[u32], ); @@ -1414,7 +1514,7 @@ pub trait CommandEncoder: WasmNotSendSync + fmt::Debug { /// This clears any bindings established by the following calls: /// /// - [`set_bind_group`](CommandEncoder::set_bind_group) - /// - [`set_push_constants`](CommandEncoder::set_push_constants) + /// - [`set_immediates`](CommandEncoder::set_immediates) /// - [`begin_query`](CommandEncoder::begin_query) /// - [`set_render_pipeline`](CommandEncoder::set_render_pipeline) /// - [`set_index_buffer`](CommandEncoder::set_index_buffer) @@ -1536,7 +1636,7 @@ pub trait CommandEncoder: WasmNotSendSync + fmt::Debug { /// This clears any bindings established by the following calls: /// /// - [`set_bind_group`](CommandEncoder::set_bind_group) - /// - [`set_push_constants`](CommandEncoder::set_push_constants) + /// - [`set_immediates`](CommandEncoder::set_immediates) /// - [`begin_query`](CommandEncoder::begin_query) /// - [`set_compute_pipeline`](CommandEncoder::set_compute_pipeline) /// @@ -1615,9 +1715,9 @@ bitflags!( #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct PipelineLayoutFlags: u32 { /// D3D12: Add support for `first_vertex` and `first_instance` builtins - /// via push constants for direct execution. + /// via immediates for direct execution. const FIRST_VERTEX_INSTANCE = 1 << 0; - /// D3D12: Add support for `num_workgroups` builtins via push constants + /// D3D12: Add support for `num_workgroups` builtins via immediates /// for direct execution. const NUM_WORK_GROUPS = 1 << 1; /// D3D12: Add support for the builtins that the other flags enable for @@ -1751,13 +1851,22 @@ bitflags!( } ); -//TODO: it's not intuitive for the backends to consider `LOAD` being optional. - bitflags!( + /// Attachment load and store operations. + /// + /// There must be at least one flag from the LOAD group and one from the STORE group set. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct AttachmentOps: u8 { + /// Load the existing contents of the attachment. const LOAD = 1 << 0; - const STORE = 1 << 1; + /// Clear the attachment to a specified value. + const LOAD_CLEAR = 1 << 1; + /// The contents of the attachment are undefined. + const LOAD_DONT_CARE = 1 << 2; + /// Store the contents of the attachment. + const STORE = 1 << 3; + /// The contents of the attachment are undefined after the pass. + const STORE_DISCARD = 1 << 4; } ); @@ -1767,6 +1876,7 @@ pub struct InstanceDescriptor<'a> { pub flags: wgt::InstanceFlags, pub memory_budget_thresholds: wgt::MemoryBudgetThresholds, pub backend_options: wgt::BackendOptions, + pub telemetry: Option<Telemetry>, } #[derive(Clone, Debug)] @@ -1974,7 +2084,7 @@ pub struct PipelineLayoutDescriptor<'a, B: DynBindGroupLayout + ?Sized> { pub label: Label<'a>, pub flags: PipelineLayoutFlags, pub bind_group_layouts: &'a [&'a B], - pub push_constant_ranges: &'a [wgt::PushConstantRange], + pub immediate_size: u32, } /// A region of a buffer made visible to shaders via a [`BindGroup`]. @@ -2692,3 +2802,22 @@ pub struct TlasInstance { pub mask: u8, pub blas_address: u64, } + +#[cfg(dx12)] +pub enum D3D12ExposeAdapterResult { + CreateDeviceError(dx12::CreateDeviceError), + ResourceBindingTier2Requirement, + ShaderModel6Requirement, + Success(dx12::FeatureLevel, dx12::ShaderModel), +} + +/// Pluggable telemetry, mainly to be used by Firefox. +#[derive(Debug, Clone, Copy)] +pub struct Telemetry { + #[cfg(dx12)] + pub d3d12_expose_adapter: fn( + desc: &windows::Win32::Graphics::Dxgi::DXGI_ADAPTER_DESC2, + driver_version: [u16; 4], + result: D3D12ExposeAdapterResult, + ), +} diff --git a/third_party/rust/wgpu-hal/src/metal/adapter.rs b/third_party/rust/wgpu-hal/src/metal/adapter.rs @@ -50,11 +50,10 @@ impl crate::Adapter for super::Adapter { let queue = self .shared .device - .lock() .new_command_queue_with_max_command_buffer_count(MAX_COMMAND_BUFFERS); // Acquiring the meaning of timestamp ticks is hard with Metal! - // The only thing there is is a method correlating cpu & gpu timestamps (`device.sample_timestamps`). + // The only thing there is a method correlating cpu & gpu timestamps (`device.sample_timestamps`). // Users are supposed to call this method twice and calculate the difference, // see "Converting GPU Timestamps into CPU Time": // https://developer.apple.com/documentation/metal/gpu_counters_and_counter_sample_buffers/converting_gpu_timestamps_into_cpu_time @@ -72,7 +71,7 @@ impl crate::Adapter for super::Adapter { // Based on: // * https://github.com/gfx-rs/wgpu/pull/2528 // * https://github.com/gpuweb/gpuweb/issues/1325#issuecomment-761041326 - let timestamp_period = if self.shared.device.lock().name().starts_with("Intel") { + let timestamp_period = if self.shared.device.name().starts_with("Intel") { 83.333 } else { // Known for Apple Silicon (at least M1 & M2, iPad Pro 2018) and AMD GPUs. @@ -121,7 +120,7 @@ impl crate::Adapter for super::Adapter { Tfc::empty() }; let is_not_apple1x = super::PrivateCapabilities::supports_any( - self.shared.device.lock().as_ref(), + self.shared.device.as_ref(), &[ MTLFeatureSet::iOS_GPUFamily2_v1, MTLFeatureSet::macOS_GPUFamily1_v1, @@ -262,12 +261,16 @@ impl crate::Adapter for super::Adapter { all_caps | Tfc::DEPTH_STENCIL_ATTACHMENT | msaa_count | msaa_resolve_apple3x_if } Tf::Depth16Unorm => { - let mut flags = - Tfc::DEPTH_STENCIL_ATTACHMENT | msaa_count | msaa_resolve_apple3x_if; if pc.format_depth16unorm { - flags |= Tfc::SAMPLED_LINEAR + let mut flags = + Tfc::DEPTH_STENCIL_ATTACHMENT | msaa_count | msaa_resolve_apple3x_if; + if pc.format_depth16unorm_filter { + flags |= Tfc::SAMPLED_LINEAR; + } + flags + } else { + return Tfc::empty(); } - flags } Tf::Depth32Float | Tf::Depth32FloatStencil8 => { let mut flags = @@ -523,46 +526,16 @@ impl super::PrivateCapabilities { } pub fn new(device: &metal::Device) -> Self { - #[repr(C)] - #[derive(Clone, Copy, Debug)] - #[allow(clippy::upper_case_acronyms)] - struct NSOperatingSystemVersion { - major: usize, - minor: usize, - patch: usize, - } - - impl NSOperatingSystemVersion { - fn at_least( - &self, - mac_version: (usize, usize), - ios_version: (usize, usize), - is_mac: bool, - ) -> bool { - if is_mac { - self.major > mac_version.0 - || (self.major == mac_version.0 && self.minor >= mac_version.1) - } else { - self.major > ios_version.0 - || (self.major == ios_version.0 && self.minor >= ios_version.1) - } - } - } - let version: NSOperatingSystemVersion = unsafe { let process_info: *mut objc::runtime::Object = msg_send![class!(NSProcessInfo), processInfo]; msg_send![process_info, operatingSystemVersion] }; - let os_is_mac = device.supports_feature_set(MTLFeatureSet::macOS_GPUFamily1_v1); - // Metal was first introduced in OS X 10.11 and iOS 8. The current version number of visionOS is 1.0.0. Additionally, - // on the Simulator, Apple only provides the Apple2 GPU capability, and the Apple2+ GPU capability covers the capabilities of Apple2. - // Therefore, the following conditions can be used to determine if it is visionOS. - // https://developer.apple.com/documentation/metal/developing_metal_apps_that_run_in_simulator - let os_is_xr = version.major < 8 && device.supports_family(MTLGPUFamily::Apple2); - let family_check = os_is_xr || version.at_least((10, 15), (13, 0), os_is_mac); - + let os_type = super::OsType::new(version, device); + let family_check = version.at_least((10, 15), (13, 0), (13, 0), (1, 0), os_type); + let metal3 = family_check && device.supports_family(MTLGPUFamily::Metal3); + let metal4 = family_check && device.supports_family(MTLGPUFamily::Metal4); let mut sample_count_mask = crate::TextureFormatCapabilities::MULTISAMPLE_X4; // 1 and 4 samples are supported on all devices if device.supports_texture_sample_count(2) { sample_count_mask |= crate::TextureFormatCapabilities::MULTISAMPLE_X2; @@ -574,9 +547,15 @@ impl super::PrivateCapabilities { sample_count_mask |= crate::TextureFormatCapabilities::MULTISAMPLE_X16; } - let rw_texture_tier = if version.at_least((10, 13), (11, 0), os_is_mac) { + let rw_texture_tier = if version.at_least((10, 13), (11, 0), (11, 0), (1, 0), os_type) { device.read_write_texture_support() - } else if version.at_least((10, 12), OS_NOT_SUPPORT, os_is_mac) { + } else if version.at_least( + (10, 12), + OS_NOT_SUPPORT, + OS_NOT_SUPPORT, + OS_NOT_SUPPORT, + os_type, + ) { if Self::supports_any(device, &[MTLFeatureSet::macOS_ReadWriteTextureTier2]) { MTLReadWriteTextureTier::Tier2 } else { @@ -587,7 +566,7 @@ impl super::PrivateCapabilities { }; let mut timestamp_query_support = TimestampQuerySupport::empty(); - if version.at_least((11, 0), (14, 0), os_is_mac) + if version.at_least((11, 0), (14, 0), (14, 0), (1, 0), os_type) && device.supports_counter_sampling(MTLCounterSamplingPoint::AtStageBoundary) { // If we don't support at stage boundary, don't support anything else. @@ -605,46 +584,52 @@ impl super::PrivateCapabilities { // `TimestampQuerySupport::INSIDE_WGPU_PASSES` emerges from the other flags. } - let argument_buffers = device.argument_buffers_support(); + let argument_buffers = version + .at_least((10, 13), (11, 0), (11, 0), (1, 0), os_type) + .then(|| device.argument_buffers_support()); let is_virtual = device.name().to_lowercase().contains("virtual"); + let mesh_shaders = family_check + && (device.supports_family(MTLGPUFamily::Metal3) + || device.supports_family(MTLGPUFamily::Apple7) + || device.supports_family(MTLGPUFamily::Mac2)) + // Mesh shaders don't work on virtual devices even if they should be supported. CI thing + && !is_virtual; + + let msl_version = if version.at_least((14, 0), (17, 0), (17, 0), (1, 0), os_type) { + MTLLanguageVersion::V3_1 + } else if version.at_least((13, 0), (16, 0), (16, 0), (1, 0), os_type) { + MTLLanguageVersion::V3_0 + } else if version.at_least((12, 0), (15, 0), (15, 0), (1, 0), os_type) { + MTLLanguageVersion::V2_4 + } else if version.at_least((11, 0), (14, 0), (14, 0), (1, 0), os_type) { + MTLLanguageVersion::V2_3 + } else if version.at_least((10, 15), (13, 0), (13, 0), (1, 0), os_type) { + MTLLanguageVersion::V2_2 + } else if version.at_least((10, 14), (12, 0), (12, 0), (1, 0), os_type) { + MTLLanguageVersion::V2_1 + } else if version.at_least((10, 13), (11, 0), (11, 0), (1, 0), os_type) { + MTLLanguageVersion::V2_0 + } else if version.at_least((10, 12), (10, 0), (10, 0), (1, 0), os_type) { + MTLLanguageVersion::V1_2 + } else if version.at_least((10, 11), (9, 0), (9, 0), (1, 0), os_type) { + MTLLanguageVersion::V1_1 + } else { + MTLLanguageVersion::V1_0 + }; + Self { - family_check, - msl_version: if os_is_xr || version.at_least((14, 0), (17, 0), os_is_mac) { - MTLLanguageVersion::V3_1 - } else if version.at_least((13, 0), (16, 0), os_is_mac) { - MTLLanguageVersion::V3_0 - } else if version.at_least((12, 0), (15, 0), os_is_mac) { - MTLLanguageVersion::V2_4 - } else if version.at_least((11, 0), (14, 0), os_is_mac) { - MTLLanguageVersion::V2_3 - } else if version.at_least((10, 15), (13, 0), os_is_mac) { - MTLLanguageVersion::V2_2 - } else if version.at_least((10, 14), (12, 0), os_is_mac) { - MTLLanguageVersion::V2_1 - } else if version.at_least((10, 13), (11, 0), os_is_mac) { - MTLLanguageVersion::V2_0 - } else if version.at_least((10, 12), (10, 0), os_is_mac) { - MTLLanguageVersion::V1_2 - } else if version.at_least((10, 11), (9, 0), os_is_mac) { - MTLLanguageVersion::V1_1 - } else { - MTLLanguageVersion::V1_0 - }, + msl_version, // macOS 10.11 doesn't support read-write resources - fragment_rw_storage: version.at_least((10, 12), (8, 0), os_is_mac), + fragment_rw_storage: version.at_least((10, 12), (8, 0), (8, 0), (1, 0), os_type), read_write_texture_tier: rw_texture_tier, - msaa_desktop: os_is_mac, - msaa_apple3: if family_check { - device.supports_family(MTLGPUFamily::Apple3) - } else { - device.supports_feature_set(MTLFeatureSet::iOS_GPUFamily3_v4) - }, + msaa_desktop: os_type == super::OsType::Macos, + msaa_apple3: (family_check && device.supports_family(MTLGPUFamily::Apple3)) + || device.supports_feature_set(MTLFeatureSet::iOS_GPUFamily3_v4), msaa_apple7: family_check && device.supports_family(MTLGPUFamily::Apple7), resource_heaps: Self::supports_any(device, RESOURCE_HEAP_SUPPORT), argument_buffers, - shared_textures: !os_is_mac, mutable_comparison_samplers: Self::supports_any( device, MUTABLE_COMPARISON_SAMPLER_SUPPORT, @@ -656,22 +641,26 @@ impl super::PrivateCapabilities { BASE_VERTEX_FIRST_INSTANCE_SUPPORT, ), dual_source_blending: Self::supports_any(device, DUAL_SOURCE_BLEND_SUPPORT), - low_power: !os_is_mac || device.is_low_power(), - headless: os_is_mac && device.is_headless(), + low_power: os_type != super::OsType::Macos || device.is_low_power(), + headless: os_type == super::OsType::Macos && device.is_headless(), layered_rendering: Self::supports_any(device, LAYERED_RENDERING_SUPPORT), function_specialization: Self::supports_any(device, FUNCTION_SPECIALIZATION_SUPPORT), depth_clip_mode: Self::supports_any(device, DEPTH_CLIP_MODE), texture_cube_array: Self::supports_any(device, TEXTURE_CUBE_ARRAY_SUPPORT), - supports_float_filtering: os_is_mac - || (version.at_least((11, 0), (14, 0), os_is_mac) + supports_float_filtering: os_type == super::OsType::Macos + || (version.at_least((11, 0), (14, 0), (16, 0), (1, 0), os_type) && device.supports_32bit_float_filtering()), - format_depth24_stencil8: os_is_mac && device.d24_s8_supported(), - format_depth32_stencil8_filter: os_is_mac, - format_depth32_stencil8_none: !os_is_mac, - format_min_srgb_channels: if os_is_mac { 4 } else { 1 }, - format_b5: !os_is_mac, - format_bc: os_is_mac, - format_eac_etc: !os_is_mac + format_depth24_stencil8: os_type == super::OsType::Macos && device.d24_s8_supported(), + format_depth32_stencil8_filter: os_type == super::OsType::Macos, + format_depth32_stencil8_none: os_type != super::OsType::Macos, + format_min_srgb_channels: if os_type == super::OsType::Macos { + 4 + } else { + 1 + }, + format_b5: os_type != super::OsType::Macos, + format_bc: os_type == super::OsType::Macos, + format_eac_etc: os_type != super::OsType::Macos // M1 in macOS supports EAC/ETC2 || (family_check && device.supports_family(MTLGPUFamily::Apple7)), // A8(Apple2) and later always support ASTC pixel formats @@ -683,9 +672,9 @@ impl super::PrivateCapabilities { format_astc_3d: family_check && device.supports_family(MTLGPUFamily::Apple3), format_any8_unorm_srgb_all: Self::supports_any(device, ANY8_UNORM_SRGB_ALL), format_any8_unorm_srgb_no_write: !Self::supports_any(device, ANY8_UNORM_SRGB_ALL) - && !os_is_mac, + && os_type != super::OsType::Macos, format_any8_snorm_all: Self::supports_any(device, ANY8_SNORM_RESOLVE), - format_r16_norm_all: os_is_mac, + format_r16_norm_all: os_type == super::OsType::Macos, // No devices support r32's all capabilities format_r32_all: false, // All devices support r32's write capability @@ -693,8 +682,8 @@ impl super::PrivateCapabilities { // iOS support r32float's write capability, macOS support r32float's all capabilities format_r32float_no_write_no_filter: false, // Only iOS doesn't support r32float's filter capability - format_r32float_no_filter: !os_is_mac, - format_r32float_all: os_is_mac, + format_r32float_no_filter: os_type != super::OsType::Macos, + format_r32float_all: os_type == super::OsType::Macos, format_rgba8_srgb_all: Self::supports_any(device, RGBA8_SRGB), format_rgba8_srgb_no_write: !Self::supports_any(device, RGBA8_SRGB), format_rgb10a2_unorm_all: Self::supports_any(device, RGB10A2UNORM_ALL), @@ -703,16 +692,17 @@ impl super::PrivateCapabilities { format_rg11b10_all: Self::supports_any(device, RG11B10FLOAT_ALL), format_rg11b10_no_write: !Self::supports_any(device, RG11B10FLOAT_ALL), format_rgb9e5_all: Self::supports_any(device, RGB9E5FLOAT_ALL), - format_rgb9e5_no_write: !Self::supports_any(device, RGB9E5FLOAT_ALL) && !os_is_mac, - format_rgb9e5_filter_only: os_is_mac, + format_rgb9e5_no_write: !Self::supports_any(device, RGB9E5FLOAT_ALL) + && os_type != super::OsType::Macos, + format_rgb9e5_filter_only: os_type == super::OsType::Macos, format_rg32_color: true, format_rg32_color_write: true, // Only macOS support rg32float's all capabilities - format_rg32float_all: os_is_mac, + format_rg32float_all: os_type == super::OsType::Macos, // All devices support rg32float's color + blend capabilities format_rg32float_color_blend: true, // Only iOS doesn't support rg32float's filter - format_rg32float_no_filter: !os_is_mac, + format_rg32float_no_filter: os_type != super::OsType::Macos, format_rgba32int_color: true, // All devices support rgba32uint and rgba32sint's color + write capabilities format_rgba32int_color_write: true, @@ -720,21 +710,21 @@ impl super::PrivateCapabilities { // All devices support rgba32float's color + write capabilities format_rgba32float_color_write: true, // Only macOS support rgba32float's all capabilities - format_rgba32float_all: os_is_mac, - format_depth16unorm: Self::supports_any( - device, - &[ - MTLFeatureSet::iOS_GPUFamily3_v3, - MTLFeatureSet::macOS_GPUFamily1_v2, - ], - ), - format_depth32float_filter: os_is_mac, - format_depth32float_none: !os_is_mac, + format_rgba32float_all: os_type == super::OsType::Macos, + // https://developer.apple.com/documentation/metal/mtlpixelformat/depth16unorm + format_depth16unorm: version.at_least((10, 12), (13, 0), (13, 0), (1, 0), os_type), + // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf#page=12 + format_depth16unorm_filter: family_check + && (metal3 + || device.supports_family(MTLGPUFamily::Apple3) + || device.supports_family(MTLGPUFamily::Mac2)), + format_depth32float_filter: os_type == super::OsType::Macos, + format_depth32float_none: os_type != super::OsType::Macos, format_bgr10a2_all: Self::supports_any(device, BGR10A2_ALL), format_bgr10a2_no_write: !Self::supports_any(device, BGR10A2_ALL), max_buffers_per_stage: 31, max_vertex_buffers: 31.min(crate::MAX_VERTEX_BUFFERS as u32), - max_textures_per_stage: if os_is_mac + max_textures_per_stage: if os_type == super::OsType::Macos || (family_check && device.supports_family(MTLGPUFamily::Apple6)) { 128 @@ -744,13 +734,14 @@ impl super::PrivateCapabilities { 31 }, max_samplers_per_stage: 16, - max_binding_array_elements: if argument_buffers == MTLArgumentBuffersTier::Tier2 { + max_binding_array_elements: if argument_buffers == Some(MTLArgumentBuffersTier::Tier2) { 1_000_000 } else if family_check && device.supports_family(MTLGPUFamily::Apple4) { 96 } else { 31 }, + // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf#page=7 max_sampler_binding_array_elements: if family_check && device.supports_family(MTLGPUFamily::Apple9) { @@ -759,18 +750,21 @@ impl super::PrivateCapabilities { && (device.supports_family(MTLGPUFamily::Apple7) || device.supports_family(MTLGPUFamily::Mac2)) { - 1000 + 1024 } else if family_check && device.supports_family(MTLGPUFamily::Apple6) { 128 } else { 16 }, - buffer_alignment: if os_is_mac || os_is_xr { 256 } else { 64 }, - max_buffer_size: if version.at_least((10, 14), (12, 0), os_is_mac) { - // maxBufferLength available on macOS 10.14+ and iOS 12.0+ + buffer_alignment: if matches!(os_type, super::OsType::Macos | super::OsType::VisionOs) { + 256 + } else { + 64 + }, + max_buffer_size: if version.at_least((10, 14), (12, 0), (12, 0), (1, 0), os_type) { let buffer_size: NSInteger = unsafe { msg_send![device.as_ref(), maxBufferLength] }; buffer_size as _ - } else if os_is_mac { + } else if os_type == super::OsType::Macos { 1 << 30 // 1GB on macOS 10.11 and up } else { 1 << 28 // 256MB on iOS 8.0+ @@ -789,13 +783,15 @@ impl super::PrivateCapabilities { }, max_texture_3d_size: 2048, max_texture_layers: 2048, - max_fragment_input_components: if os_is_mac + max_fragment_input_components: if os_type == super::OsType::Macos || device.supports_feature_set(MTLFeatureSet::iOS_GPUFamily4_v1) { 124 } else { 60 }, + // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf#page=7 + // 8 is supported on everything on that list max_color_render_targets: if Self::supports_any( device, &[ @@ -808,10 +804,12 @@ impl super::PrivateCapabilities { } else { 4 }, - // Per https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf + // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf#page=7 max_color_attachment_bytes_per_sample: if family_check - && device.supports_family(MTLGPUFamily::Apple4) + && device.supports_family(MTLGPUFamily::Apple7) { + 128 + } else if family_check && device.supports_family(MTLGPUFamily::Apple4) { 64 } else { 32 @@ -823,6 +821,9 @@ impl super::PrivateCapabilities { } else { 60 }, + // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf#page=6 + // These are older checks but still hold true; no entry in this table supports + // more than 1024 threads. max_threads_per_group: if Self::supports_any( device, &[ @@ -834,6 +835,9 @@ impl super::PrivateCapabilities { } else { 512 }, + // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf#page=6 + // These are older checks but still hold true; no entry in this table supports + // more than 32kb. max_total_threadgroup_memory: if Self::supports_any( device, &[ @@ -854,13 +858,39 @@ impl super::PrivateCapabilities { MTLFeatureSet::tvOS_GPUFamily1_v2, ], ), + // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf#page=4 supports_binary_archives: family_check - && (device.supports_family(MTLGPUFamily::Apple3) - || device.supports_family(MTLGPUFamily::Mac1)), - supports_capture_manager: version.at_least((10, 13), (11, 0), os_is_mac), - can_set_maximum_drawables_count: version.at_least((10, 14), (11, 2), os_is_mac), - can_set_display_sync: version.at_least((10, 13), OS_NOT_SUPPORT, os_is_mac), - can_set_next_drawable_timeout: version.at_least((10, 13), (11, 0), os_is_mac), + && (metal3 + || device.supports_family(MTLGPUFamily::Apple3) + || device.supports_family(MTLGPUFamily::Mac2)), + // https://developer.apple.com/documentation/metal/mtlcapturemanager + supports_capture_manager: version.at_least((10, 13), (11, 0), (11, 0), (1, 0), os_type), + // https://developer.apple.com/documentation/quartzcore/cametallayer/maximumdrawablecount + can_set_maximum_drawables_count: version.at_least( + (10, 14), + (11, 2), + (11, 2), + (1, 0), + os_type, + ), + // https://developer.apple.com/documentation/quartzcore/cametallayer/displaysyncenabled + can_set_display_sync: version.at_least( + (10, 13), + OS_NOT_SUPPORT, + OS_NOT_SUPPORT, + OS_NOT_SUPPORT, + os_type, + ), + // https://developer.apple.com/documentation/quartzcore/cametallayer/allowsnextdrawabletimeout + can_set_next_drawable_timeout: version.at_least( + (10, 13), + (11, 0), + (11, 0), + (1, 0), + os_type, + ), + // This is just trusted blindly since docs referencing supports_any have been removed + // but we don't want to remove feature support. supports_arrays_of_textures: Self::supports_any( device, &[ @@ -869,64 +899,94 @@ impl super::PrivateCapabilities { MTLFeatureSet::macOS_GPUFamily1_v3, ], ), + // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf#page=3 supports_arrays_of_textures_write: family_check - && (device.supports_family(MTLGPUFamily::Apple6) - || device.supports_family(MTLGPUFamily::Mac1) - || device.supports_family(MTLGPUFamily::MacCatalyst1)), - supports_mutability: version.at_least((10, 13), (11, 0), os_is_mac), - //Depth clipping is supported on all macOS GPU families and iOS family 4 and later - supports_depth_clip_control: os_is_mac + && (metal3 + || device.supports_family(MTLGPUFamily::Apple6) + || device.supports_family(MTLGPUFamily::Mac2)), + // https://developer.apple.com/documentation/metal/mtlpipelinebufferdescriptor/mutability + supports_mutability: version.at_least((10, 13), (11, 0), (11, 0), (1, 0), os_type), + // Depth clipping is supported on all macOS GPU families and iOS family 4 and later + supports_depth_clip_control: os_type == super::OsType::Macos || device.supports_feature_set(MTLFeatureSet::iOS_GPUFamily4_v1), - supports_preserve_invariance: version.at_least((11, 0), (13, 0), os_is_mac), - // Metal 2.2 on mac, 2.3 on iOS. - supports_shader_primitive_index: version.at_least((10, 15), (14, 0), os_is_mac), - has_unified_memory: if version.at_least((10, 15), (13, 0), os_is_mac) { + // https://developer.apple.com/documentation/metal/mtlcompileoptions/preserveinvariance + supports_preserve_invariance: version.at_least( + (11, 0), + (14, 0), + (14, 0), + (1, 0), + os_type, + ), + // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf#page=4 + supports_shader_primitive_index: family_check + && (metal3 + || device.supports_family(MTLGPUFamily::Apple7) + || device.supports_family(MTLGPUFamily::Mac2)), + // https://developer.apple.com/documentation/metal/mtldevice/hasunifiedmemory + has_unified_memory: if version.at_least((10, 15), (13, 0), (13, 0), (1, 0), os_type) { Some(device.has_unified_memory()) } else { None }, timestamp_query_support, + // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf#page=4 supports_simd_scoped_operations: family_check - && (device.supports_family(MTLGPUFamily::Metal3) + && (metal3 || device.supports_family(MTLGPUFamily::Mac2) || device.supports_family(MTLGPUFamily::Apple7)), - // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf#page=5 - int64: family_check - && (device.supports_family(MTLGPUFamily::Apple3) - || device.supports_family(MTLGPUFamily::Metal3)), - // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf#page=6 - int64_atomics: family_check - && ((device.supports_family(MTLGPUFamily::Apple8) - && device.supports_family(MTLGPUFamily::Mac2)) - || device.supports_family(MTLGPUFamily::Apple9)), - // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf#page=6 + // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf#page=4 + int64: family_check && (metal3 || device.supports_family(MTLGPUFamily::Apple3)), + // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf#page=4 + // There is also a footnote that says + // "Some GPU devices in the Apple8 family support 64-bit atomic minimum and maximum..." + int64_atomics_min_max: family_check + && (device.supports_family(MTLGPUFamily::Apple9) + || (device.supports_family(MTLGPUFamily::Apple8) + && device.supports_family(MTLGPUFamily::Mac2))), + int64_atomics: family_check && device.supports_family(MTLGPUFamily::Apple9), + // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf#page=4 float_atomics: family_check - && (device.supports_family(MTLGPUFamily::Apple7) - || device.supports_family(MTLGPUFamily::Mac2)), - supports_shared_event: version.at_least((10, 14), (12, 0), os_is_mac), - mesh_shaders: family_check - && (device.supports_family(MTLGPUFamily::Metal3) + && (metal3 || device.supports_family(MTLGPUFamily::Apple7) - || device.supports_family(MTLGPUFamily::Mac2)) - // Mesh shaders don't work on virtual devices even if they should be supported. - && !is_virtual, + || device.supports_family(MTLGPUFamily::Mac2)), + // https://developer.apple.com/documentation/metal/mtlsharedevent + supports_shared_event: version.at_least((10, 14), (12, 0), (12, 0), (1, 0), os_type), + // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf#page=5 (footnote) + // Supported on some Metal4, Apple7, Mac2, and some other platforms can be queried with device.supportsShaderBarycentricCoordinates(). + shader_barycentrics: metal4 + || (family_check + && (device.supports_family(MTLGPUFamily::Apple7) + || device.supports_family(MTLGPUFamily::Mac2))) + || (version.at_least((10, 15), (14, 0), (16, 0), (1, 0), os_type) + && device.supports_shader_barycentric_coordinates()), + // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf#page=3 + supports_memoryless_storage: metal4 + || (family_check && device.supports_family(MTLGPUFamily::Apple2)), supported_vertex_amplification_factor: { let mut factor = 1; // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf#page=8 // The table specifies either none, 2, 8, or unsupported, implying it is a relatively small power of 2 // The bitmask only uses 32 bits, so it can't be higher even if the device for some reason claims to support that. - while factor < 32 && device.supports_vertex_amplification_count(factor * 2) { + while factor < 32 + // See https://developer.apple.com/documentation/metal/mtldevice/supportsvertexamplificationcount(_:) + && version.at_least( + // "10.15.4" so we're taking the conservative route. + (10, 16), + (13, 0), + (16, 0), + (1, 0), + os_type, + ) + && device.supports_vertex_amplification_count(factor * 2) + { factor *= 2 } factor as u32 }, - shader_barycentrics: device.supports_shader_barycentric_coordinates(), - // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf#page=3 - supports_memoryless_storage: if family_check { - device.supports_family(MTLGPUFamily::Apple2) - } else { - version.at_least((11, 0), (10, 0), os_is_mac) - }, + // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf#page=4 + mesh_shaders, + max_mesh_task_workgroup_count: if mesh_shaders { 1024 } else { 0 }, + max_task_payload_size: if mesh_shaders { 16384 - 32 } else { 0 }, } } @@ -945,7 +1005,7 @@ impl super::PrivateCapabilities { | F::MAPPABLE_PRIMARY_BUFFERS | F::VERTEX_WRITABLE_STORAGE | F::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES - | F::PUSH_CONSTANTS + | F::IMMEDIATES | F::POLYGON_MODE_LINE | F::CLEAR_TEXTURE | F::TEXTURE_FORMAT_16BIT_NORM @@ -991,7 +1051,20 @@ impl super::PrivateCapabilities { | F::PARTIALLY_BOUND_BINDING_ARRAY, self.msl_version >= MTLLanguageVersion::V3_0 && self.supports_arrays_of_textures - && self.argument_buffers as u64 >= MTLArgumentBuffersTier::Tier2 as u64, + && self + .argument_buffers + .unwrap_or(MTLArgumentBuffersTier::Tier1) as u64 + >= MTLArgumentBuffersTier::Tier2 as u64, + ); + features.set( + F::STORAGE_RESOURCE_BINDING_ARRAY, + self.msl_version >= MTLLanguageVersion::V3_0 + && self.supports_arrays_of_textures + && self.supports_arrays_of_textures_write + && self + .argument_buffers + .unwrap_or(MTLArgumentBuffersTier::Tier1) as u64 + >= MTLArgumentBuffersTier::Tier2 as u64, ); features.set( F::SHADER_INT64, @@ -999,7 +1072,7 @@ impl super::PrivateCapabilities { ); features.set( F::SHADER_INT64_ATOMIC_MIN_MAX, - self.int64_atomics && self.msl_version >= MTLLanguageVersion::V2_4, + self.int64_atomics_min_max && self.msl_version >= MTLLanguageVersion::V2_4, ); features.set( F::TEXTURE_INT64_ATOMIC, @@ -1067,74 +1140,99 @@ impl super::PrivateCapabilities { downlevel .flags .set(wgt::DownlevelFlags::ANISOTROPIC_FILTERING, true); + let base = wgt::Limits::default(); - crate::Capabilities { - limits: wgt::Limits { - max_texture_dimension_1d: self.max_texture_size as u32, - max_texture_dimension_2d: self.max_texture_size as u32, - max_texture_dimension_3d: self.max_texture_3d_size as u32, - max_texture_array_layers: self.max_texture_layers as u32, - max_bind_groups: 8, - max_bindings_per_bind_group: 65535, - max_dynamic_uniform_buffers_per_pipeline_layout: base - .max_dynamic_uniform_buffers_per_pipeline_layout, - max_dynamic_storage_buffers_per_pipeline_layout: base - .max_dynamic_storage_buffers_per_pipeline_layout, - max_sampled_textures_per_shader_stage: self.max_textures_per_stage, - max_samplers_per_shader_stage: self.max_samplers_per_stage, - max_storage_buffers_per_shader_stage: self.max_buffers_per_stage, - max_storage_textures_per_shader_stage: self.max_textures_per_stage, - max_uniform_buffers_per_shader_stage: self.max_buffers_per_stage, - max_binding_array_elements_per_shader_stage: self.max_binding_array_elements, - max_binding_array_sampler_elements_per_shader_stage: self - .max_sampler_binding_array_elements, - max_uniform_buffer_binding_size: self.max_buffer_size.min(!0u32 as u64) as u32, - max_storage_buffer_binding_size: self.max_buffer_size.min(!0u32 as u64) as u32, - max_vertex_buffers: self.max_vertex_buffers, - max_vertex_attributes: 31, - max_vertex_buffer_array_stride: base.max_vertex_buffer_array_stride, - min_subgroup_size: 4, - max_subgroup_size: 64, - max_push_constant_size: 0x1000, - min_uniform_buffer_offset_alignment: self.buffer_alignment as u32, - min_storage_buffer_offset_alignment: self.buffer_alignment as u32, - max_inter_stage_shader_components: self.max_varying_components, - max_color_attachments: (self.max_color_render_targets as u32) - .min(crate::MAX_COLOR_ATTACHMENTS as u32), - max_color_attachment_bytes_per_sample: self.max_color_attachment_bytes_per_sample - as u32, - max_compute_workgroup_storage_size: self.max_total_threadgroup_memory, - max_compute_invocations_per_workgroup: self.max_threads_per_group, - max_compute_workgroup_size_x: self.max_threads_per_group, - max_compute_workgroup_size_y: self.max_threads_per_group, - max_compute_workgroup_size_z: self.max_threads_per_group, - max_compute_workgroups_per_dimension: 0xFFFF, - max_buffer_size: self.max_buffer_size, - max_non_sampler_bindings: u32::MAX, - - // See https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf, Maximum threadgroups per mesh shader grid - max_task_workgroup_total_count: 1024, - max_task_workgroups_per_dimension: 1024, - max_mesh_multiview_view_count: 0, - max_mesh_output_layers: self.max_texture_layers as u32, - - max_blas_primitive_count: 0, // When added: 2^28 from https://developer.apple.com/documentation/metal/mtlaccelerationstructureusage/extendedlimits - max_blas_geometry_count: 0, // When added: 2^24 - max_tlas_instance_count: 0, // When added: 2^24 - // Unsure what this will be when added: acceleration structures count as a buffer so - // it may be worth using argument buffers for this all acceleration structures, then - // there will be no limit. - // From 2.17.7 in https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf - // > [Acceleration structures] are opaque objects that can be bound directly using - // buffer binding points or via argument buffers - max_acceleration_structures_per_shader_stage: 0, - - max_multiview_view_count: if self.supported_vertex_amplification_factor > 1 { - self.supported_vertex_amplification_factor - } else { - 0 - }, + // Be careful adjusting limits here. The `AdapterShared` stores the + // original `PrivateCapabilities`, so code could accidentally use + // the wrong value. + + let limits = wgt::Limits { + max_texture_dimension_1d: self.max_texture_size as u32, + max_texture_dimension_2d: self.max_texture_size as u32, + max_texture_dimension_3d: self.max_texture_3d_size as u32, + max_texture_array_layers: self.max_texture_layers as u32, + max_bind_groups: 8, + max_bindings_per_bind_group: 65535, + max_dynamic_uniform_buffers_per_pipeline_layout: base + .max_dynamic_uniform_buffers_per_pipeline_layout, + max_dynamic_storage_buffers_per_pipeline_layout: base + .max_dynamic_storage_buffers_per_pipeline_layout, + max_sampled_textures_per_shader_stage: self.max_textures_per_stage, + max_samplers_per_shader_stage: self.max_samplers_per_stage, + max_storage_buffers_per_shader_stage: self.max_buffers_per_stage, + max_storage_textures_per_shader_stage: self.max_textures_per_stage, + max_uniform_buffers_per_shader_stage: self.max_buffers_per_stage, + max_binding_array_elements_per_shader_stage: self.max_binding_array_elements, + max_binding_array_sampler_elements_per_shader_stage: self + .max_sampler_binding_array_elements, + // Note: any adjustment here will not be reflected in the stored `PrivateCapabilities`. + max_uniform_buffer_binding_size: self.max_buffer_size.min(!0u32 as u64) as u32, + max_storage_buffer_binding_size: self.max_buffer_size.min(!0u32 as u64) as u32 + & !(wgt::STORAGE_BINDING_SIZE_ALIGNMENT - 1), + max_vertex_buffers: self.max_vertex_buffers, + max_vertex_attributes: 31, + max_vertex_buffer_array_stride: base.max_vertex_buffer_array_stride, + max_immediate_size: 0x1000, + min_uniform_buffer_offset_alignment: self.buffer_alignment as u32, + min_storage_buffer_offset_alignment: self.buffer_alignment as u32, + max_inter_stage_shader_components: self.max_varying_components, + max_color_attachments: self.max_color_render_targets as u32, + max_color_attachment_bytes_per_sample: self.max_color_attachment_bytes_per_sample + as u32, + max_compute_workgroup_storage_size: self.max_total_threadgroup_memory, + max_compute_invocations_per_workgroup: self.max_threads_per_group, + max_compute_workgroup_size_x: self.max_threads_per_group, + max_compute_workgroup_size_y: self.max_threads_per_group, + max_compute_workgroup_size_z: self.max_threads_per_group, + max_compute_workgroups_per_dimension: 0xFFFF, + max_buffer_size: self.max_buffer_size, + max_non_sampler_bindings: u32::MAX, + + max_blas_primitive_count: 0, // When added: 2^28 from https://developer.apple.com/documentation/metal/mtlaccelerationstructureusage/extendedlimits + max_blas_geometry_count: 0, // When added: 2^24 + max_tlas_instance_count: 0, // When added: 2^24 + // Unsure what this will be when added: acceleration structures count as a buffer so + // it may be worth using argument buffers for this all acceleration structures, then + // there will be no limit. + // From 2.17.7 in https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf + // > [Acceleration structures] are opaque objects that can be bound directly using + // buffer binding points or via argument buffers + max_acceleration_structures_per_shader_stage: 0, + + max_multiview_view_count: if self.supported_vertex_amplification_factor > 1 { + self.supported_vertex_amplification_factor + } else { + 0 }, + + // Should be not too large + max_task_mesh_workgroup_total_count: self.max_mesh_task_workgroup_count, + max_task_mesh_workgroups_per_dimension: self.max_mesh_task_workgroup_count, + max_task_invocations_per_workgroup: if self.mesh_shaders { 1024 } else { 0 }, + max_task_invocations_per_dimension: if self.mesh_shaders { 1024 } else { 0 }, + max_mesh_invocations_per_workgroup: if self.mesh_shaders { 1024 } else { 0 }, + max_mesh_invocations_per_dimension: if self.mesh_shaders { 1024 } else { 0 }, + // Using certain variables or debuggers can reduce the size by 32 bytes + max_task_payload_size: self.max_task_payload_size, + max_mesh_output_vertices: 256, + max_mesh_output_primitives: 256, + max_mesh_output_layers: self.max_texture_layers as u32, + max_mesh_multiview_view_count: 0, + }; + + // Since a bunch of the limits are duplicated between `Limits` and + // `PrivateCapabilities`, reducing the limits at this point could make + // things inconsistent and lead to confusion. Make sure that doesn't + // happen. + debug_assert!( + crate::auxil::apply_hal_limits(limits.clone()) == limits, + "Limits were modified by apply_hal_limits\nOriginal:\n{:#?}\nModified:\n{:#?}", + limits, + crate::auxil::apply_hal_limits(limits.clone()) + ); + + crate::Capabilities { + limits, alignments: crate::Alignments { buffer_copy_offset: wgt::BufferSize::new(self.buffer_alignment).unwrap(), buffer_copy_pitch: wgt::BufferSize::new(4).unwrap(), @@ -1330,3 +1428,52 @@ impl super::PrivateDisabilities { } } } + +impl super::OsType { + fn new(version: NSOperatingSystemVersion, device: &metal::Device) -> Self { + // Metal was first introduced in OS X 10.11 and iOS 8. The current version number of visionOS is 1.0.0. Additionally, + // on the Simulator, Apple only provides the Apple2 GPU capability, and the Apple2+ GPU capability covers the capabilities of Apple2. + // Therefore, the following conditions can be used to determine if it is visionOS. + // https://developer.apple.com/documentation/metal/developing_metal_apps_that_run_in_simulator + let os_is_vision = version.major < 8 && device.supports_family(MTLGPUFamily::Apple2); + let os_is_mac = device.supports_feature_set(MTLFeatureSet::macOS_GPUFamily1_v1); + let os_is_tvos = device.supports_feature_set(MTLFeatureSet::tvOS_GPUFamily1_v1); + if os_is_vision { + Self::VisionOs + } else if os_is_mac { + Self::Macos + } else if os_is_tvos { + Self::Tvos + } else { + Self::Ios + } + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +#[allow(clippy::upper_case_acronyms)] +struct NSOperatingSystemVersion { + major: usize, + minor: usize, + patch: usize, +} + +impl NSOperatingSystemVersion { + fn at_least( + &self, + mac_version: (usize, usize), + ios_version: (usize, usize), + tvos_version: (usize, usize), + visionos_version: (usize, usize), + os_type: super::OsType, + ) -> bool { + let required = match os_type { + super::OsType::Macos => mac_version, + super::OsType::Ios => ios_version, + super::OsType::Tvos => tvos_version, + super::OsType::VisionOs => visionos_version, + }; + (self.major, self.minor) >= required + } +} diff --git a/third_party/rust/wgpu-hal/src/metal/command.rs b/third_party/rust/wgpu-hal/src/metal/command.rs @@ -7,7 +7,7 @@ use alloc::{ use core::ops::Range; use metal::{ MTLIndexType, MTLLoadAction, MTLPrimitiveType, MTLScissorRect, MTLSize, MTLStoreAction, - MTLViewport, MTLVisibilityResultMode, NSRange, + MTLViewport, MTLVisibilityResultMode, NSRange, NSUInteger, }; use smallvec::SmallVec; @@ -25,12 +25,81 @@ impl Default for super::CommandState { stage_infos: Default::default(), storage_buffer_length_map: Default::default(), vertex_buffer_size_map: Default::default(), - push_constants: Vec::new(), + immediates: Vec::new(), pending_timer_queries: Vec::new(), } } } +/// Helper for passing encoders to `update_bind_group_state`. +/// +/// Combines [`naga::ShaderStage`] and an encoder of the appropriate type for +/// that stage. +enum Encoder<'e> { + Vertex(&'e metal::RenderCommandEncoder), + Fragment(&'e metal::RenderCommandEncoder), + Task(&'e metal::RenderCommandEncoder), + Mesh(&'e metal::RenderCommandEncoder), + Compute(&'e metal::ComputeCommandEncoder), +} + +impl Encoder<'_> { + fn stage(&self) -> naga::ShaderStage { + match self { + Self::Vertex(_) => naga::ShaderStage::Vertex, + Self::Fragment(_) => naga::ShaderStage::Fragment, + Self::Task(_) => naga::ShaderStage::Task, + Self::Mesh(_) => naga::ShaderStage::Mesh, + Self::Compute(_) => naga::ShaderStage::Compute, + } + } + + fn set_buffer( + &self, + index: NSUInteger, + buffer: Option<&metal::BufferRef>, + offset: wgt::BufferAddress, + ) { + match *self { + Self::Vertex(enc) => enc.set_vertex_buffer(index, buffer, offset), + Self::Fragment(enc) => enc.set_fragment_buffer(index, buffer, offset), + Self::Task(enc) => enc.set_object_buffer(index, buffer, offset), + Self::Mesh(enc) => enc.set_mesh_buffer(index, buffer, offset), + Self::Compute(enc) => enc.set_buffer(index, buffer, offset), + } + } + + fn set_bytes(&self, index: NSUInteger, length: u64, bytes: *const core::ffi::c_void) { + match *self { + Self::Vertex(enc) => enc.set_vertex_bytes(index, length, bytes), + Self::Fragment(enc) => enc.set_fragment_bytes(index, length, bytes), + Self::Task(enc) => enc.set_object_bytes(index, length, bytes), + Self::Mesh(enc) => enc.set_mesh_bytes(index, length, bytes), + Self::Compute(enc) => enc.set_bytes(index, length, bytes), + } + } + + fn set_sampler_state(&self, index: NSUInteger, state: Option<&metal::SamplerStateRef>) { + match *self { + Self::Vertex(enc) => enc.set_vertex_sampler_state(index, state), + Self::Fragment(enc) => enc.set_fragment_sampler_state(index, state), + Self::Task(enc) => enc.set_object_sampler_state(index, state), + Self::Mesh(enc) => enc.set_mesh_sampler_state(index, state), + Self::Compute(enc) => enc.set_sampler_state(index, state), + } + } + + fn set_texture(&self, index: NSUInteger, texture: Option<&metal::TextureRef>) { + match *self { + Self::Vertex(enc) => enc.set_vertex_texture(index, texture), + Self::Fragment(enc) => enc.set_fragment_texture(index, texture), + Self::Task(enc) => enc.set_object_texture(index, texture), + Self::Mesh(enc) => enc.set_mesh_texture(index, texture), + Self::Compute(enc) => enc.set_texture(index, texture), + } + } +} + impl super::CommandEncoder { pub fn raw_command_buffer(&self) -> Option<&metal::CommandBuffer> { self.raw_cmd_buf.as_ref() @@ -146,31 +215,29 @@ impl super::CommandEncoder { } /// Updates the bindings for a single shader stage, called in `set_bind_group`. - #[expect(clippy::too_many_arguments)] fn update_bind_group_state( &mut self, - stage: naga::ShaderStage, - render_encoder: Option<&metal::RenderCommandEncoder>, - compute_encoder: Option<&metal::ComputeCommandEncoder>, + encoder: Encoder<'_>, index_base: super::ResourceData<u32>, bg_info: &super::BindGroupLayoutInfo, dynamic_offsets: &[wgt::DynamicOffset], group_index: u32, group: &super::BindGroup, ) { - let resource_indices = match stage { - naga::ShaderStage::Vertex => &bg_info.base_resource_indices.vs, - naga::ShaderStage::Fragment => &bg_info.base_resource_indices.fs, - naga::ShaderStage::Task => &bg_info.base_resource_indices.ts, - naga::ShaderStage::Mesh => &bg_info.base_resource_indices.ms, - naga::ShaderStage::Compute => &bg_info.base_resource_indices.cs, + use naga::ShaderStage as S; + let resource_indices = match encoder.stage() { + S::Vertex => &bg_info.base_resource_indices.vs, + S::Fragment => &bg_info.base_resource_indices.fs, + S::Task => &bg_info.base_resource_indices.ts, + S::Mesh => &bg_info.base_resource_indices.ms, + S::Compute => &bg_info.base_resource_indices.cs, }; - let buffers = match stage { - naga::ShaderStage::Vertex => group.counters.vs.buffers, - naga::ShaderStage::Fragment => group.counters.fs.buffers, - naga::ShaderStage::Task => group.counters.ts.buffers, - naga::ShaderStage::Mesh => group.counters.ms.buffers, - naga::ShaderStage::Compute => group.counters.cs.buffers, + let buffers = match encoder.stage() { + S::Vertex => group.counters.vs.buffers, + S::Fragment => group.counters.fs.buffers, + S::Task => group.counters.ts.buffers, + S::Mesh => group.counters.ms.buffers, + S::Compute => group.counters.cs.buffers, }; let mut changes_sizes_buffer = false; for index in 0..buffers { @@ -179,18 +246,9 @@ impl super::CommandEncoder { if let Some(dyn_index) = buf.dynamic_index { offset += dynamic_offsets[dyn_index as usize] as wgt::BufferAddress; } - let a1 = (resource_indices.buffers + index) as u64; - let a2 = Some(buf.ptr.as_native()); - let a3 = offset; - match stage { - naga::ShaderStage::Vertex => render_encoder.unwrap().set_vertex_buffer(a1, a2, a3), - naga::ShaderStage::Fragment => { - render_encoder.unwrap().set_fragment_buffer(a1, a2, a3) - } - naga::ShaderStage::Task => render_encoder.unwrap().set_object_buffer(a1, a2, a3), - naga::ShaderStage::Mesh => render_encoder.unwrap().set_mesh_buffer(a1, a2, a3), - naga::ShaderStage::Compute => compute_encoder.unwrap().set_buffer(a1, a2, a3), - } + let index = (resource_indices.buffers + index) as u64; + let buffer = Some(buf.ptr.as_native()); + encoder.set_buffer(index, buffer, offset); if let Some(size) = buf.binding_size { let br = naga::ResourceBinding { group: group_index, @@ -203,66 +261,40 @@ impl super::CommandEncoder { if changes_sizes_buffer { if let Some((index, sizes)) = self .state - .make_sizes_buffer_update(stage, &mut self.temp.binding_sizes) + .make_sizes_buffer_update(encoder.stage(), &mut self.temp.binding_sizes) { - let a1 = index as _; - let a2 = (sizes.len() * WORD_SIZE) as u64; - let a3 = sizes.as_ptr().cast(); - match stage { - naga::ShaderStage::Vertex => { - render_encoder.unwrap().set_vertex_bytes(a1, a2, a3) - } - naga::ShaderStage::Fragment => { - render_encoder.unwrap().set_fragment_bytes(a1, a2, a3) - } - naga::ShaderStage::Task => render_encoder.unwrap().set_object_bytes(a1, a2, a3), - naga::ShaderStage::Mesh => render_encoder.unwrap().set_mesh_bytes(a1, a2, a3), - naga::ShaderStage::Compute => compute_encoder.unwrap().set_bytes(a1, a2, a3), - } + let index = index as _; + let length = (sizes.len() * WORD_SIZE) as u64; + let bytes_ptr = sizes.as_ptr().cast(); + encoder.set_bytes(index, length, bytes_ptr); } } - let samplers = match stage { - naga::ShaderStage::Vertex => group.counters.vs.samplers, - naga::ShaderStage::Fragment => group.counters.fs.samplers, - naga::ShaderStage::Task => group.counters.ts.samplers, - naga::ShaderStage::Mesh => group.counters.ms.samplers, - naga::ShaderStage::Compute => group.counters.cs.samplers, + let samplers = match encoder.stage() { + S::Vertex => group.counters.vs.samplers, + S::Fragment => group.counters.fs.samplers, + S::Task => group.counters.ts.samplers, + S::Mesh => group.counters.ms.samplers, + S::Compute => group.counters.cs.samplers, }; for index in 0..samplers { let res = group.samplers[(index_base.samplers + index) as usize]; - let a1 = (resource_indices.samplers + index) as u64; - let a2 = Some(res.as_native()); - match stage { - naga::ShaderStage::Vertex => { - render_encoder.unwrap().set_vertex_sampler_state(a1, a2) - } - naga::ShaderStage::Fragment => { - render_encoder.unwrap().set_fragment_sampler_state(a1, a2) - } - naga::ShaderStage::Task => render_encoder.unwrap().set_object_sampler_state(a1, a2), - naga::ShaderStage::Mesh => render_encoder.unwrap().set_mesh_sampler_state(a1, a2), - naga::ShaderStage::Compute => compute_encoder.unwrap().set_sampler_state(a1, a2), - } + let index = (resource_indices.samplers + index) as u64; + let state = Some(res.as_native()); + encoder.set_sampler_state(index, state); } - let textures = match stage { - naga::ShaderStage::Vertex => group.counters.vs.textures, - naga::ShaderStage::Fragment => group.counters.fs.textures, - naga::ShaderStage::Task => group.counters.ts.textures, - naga::ShaderStage::Mesh => group.counters.ms.textures, - naga::ShaderStage::Compute => group.counters.cs.textures, + let textures = match encoder.stage() { + S::Vertex => group.counters.vs.textures, + S::Fragment => group.counters.fs.textures, + S::Task => group.counters.ts.textures, + S::Mesh => group.counters.ms.textures, + S::Compute => group.counters.cs.textures, }; for index in 0..textures { let res = group.textures[(index_base.textures + index) as usize]; - let a1 = (resource_indices.textures + index) as u64; - let a2 = Some(res.as_native()); - match stage { - naga::ShaderStage::Vertex => render_encoder.unwrap().set_vertex_texture(a1, a2), - naga::ShaderStage::Fragment => render_encoder.unwrap().set_fragment_texture(a1, a2), - naga::ShaderStage::Task => render_encoder.unwrap().set_object_texture(a1, a2), - naga::ShaderStage::Mesh => render_encoder.unwrap().set_mesh_texture(a1, a2), - naga::ShaderStage::Compute => compute_encoder.unwrap().set_texture(a1, a2), - } + let index = (resource_indices.textures + index) as u64; + let texture = Some(res.as_native()); + encoder.set_texture(index, texture); } } } @@ -276,7 +308,7 @@ impl super::CommandState { self.stage_infos.cs.clear(); self.stage_infos.ts.clear(); self.stage_infos.ms.clear(); - self.push_constants.clear(); + self.immediates.clear(); } fn make_sizes_buffer_update<'a>( @@ -670,9 +702,13 @@ impl crate::CommandEncoder for super::CommandEncoder { } let load_action = if at.ops.contains(crate::AttachmentOps::LOAD) { MTLLoadAction::Load - } else { + } else if at.ops.contains(crate::AttachmentOps::LOAD_DONT_CARE) { + MTLLoadAction::DontCare + } else if at.ops.contains(crate::AttachmentOps::LOAD_CLEAR) { at_descriptor.set_clear_color(conv::map_clear_color(&at.clear_value)); MTLLoadAction::Clear + } else { + unreachable!() }; let store_action = conv::map_store_action( at.ops.contains(crate::AttachmentOps::STORE), @@ -690,9 +726,13 @@ impl crate::CommandEncoder for super::CommandEncoder { let load_action = if at.depth_ops.contains(crate::AttachmentOps::LOAD) { MTLLoadAction::Load - } else { + } else if at.depth_ops.contains(crate::AttachmentOps::LOAD_DONT_CARE) { + MTLLoadAction::DontCare + } else if at.depth_ops.contains(crate::AttachmentOps::LOAD_CLEAR) { at_descriptor.set_clear_depth(at.clear_value.0 as f64); MTLLoadAction::Clear + } else { + unreachable!(); }; let store_action = if at.depth_ops.contains(crate::AttachmentOps::STORE) { MTLStoreAction::Store @@ -713,9 +753,16 @@ impl crate::CommandEncoder for super::CommandEncoder { let load_action = if at.stencil_ops.contains(crate::AttachmentOps::LOAD) { MTLLoadAction::Load - } else { + } else if at + .stencil_ops + .contains(crate::AttachmentOps::LOAD_DONT_CARE) + { + MTLLoadAction::DontCare + } else if at.stencil_ops.contains(crate::AttachmentOps::LOAD_CLEAR) { at_descriptor.set_clear_stencil(at.clear_value.1); MTLLoadAction::Clear + } else { + unreachable!() }; let store_action = if at.stencil_ops.contains(crate::AttachmentOps::STORE) { MTLStoreAction::Store @@ -826,9 +873,7 @@ impl crate::CommandEncoder for super::CommandEncoder { let compute_encoder = self.state.compute.clone(); if let Some(encoder) = render_encoder { self.update_bind_group_state( - naga::ShaderStage::Vertex, - Some(&encoder), - None, + Encoder::Vertex(&encoder), // All zeros, as vs comes first super::ResourceData::default(), bg_info, @@ -837,9 +882,7 @@ impl crate::CommandEncoder for super::CommandEncoder { group, ); self.update_bind_group_state( - naga::ShaderStage::Task, - Some(&encoder), - None, + Encoder::Task(&encoder), // All zeros, as ts comes first super::ResourceData::default(), bg_info, @@ -848,9 +891,7 @@ impl crate::CommandEncoder for super::CommandEncoder { group, ); self.update_bind_group_state( - naga::ShaderStage::Mesh, - Some(&encoder), - None, + Encoder::Mesh(&encoder), group.counters.ts.clone(), bg_info, dynamic_offsets, @@ -858,9 +899,7 @@ impl crate::CommandEncoder for super::CommandEncoder { group, ); self.update_bind_group_state( - naga::ShaderStage::Fragment, - Some(&encoder), - None, + Encoder::Fragment(&encoder), super::ResourceData { buffers: group.counters.vs.buffers + group.counters.ts.buffers @@ -884,9 +923,7 @@ impl crate::CommandEncoder for super::CommandEncoder { } if let Some(encoder) = compute_encoder { self.update_bind_group_state( - naga::ShaderStage::Compute, - None, - Some(&encoder), + Encoder::Compute(&encoder), super::ResourceData { buffers: group.counters.vs.buffers + group.counters.ts.buffers @@ -916,56 +953,61 @@ impl crate::CommandEncoder for super::CommandEncoder { } } - unsafe fn set_push_constants( + unsafe fn set_immediates( &mut self, layout: &super::PipelineLayout, - stages: wgt::ShaderStages, 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); + let state_pc = &mut self.state.immediates; + if state_pc.len() < layout.total_immediates as usize { + state_pc.resize(layout.total_immediates as usize, 0); } debug_assert_eq!(offset_bytes as usize % WORD_SIZE, 0); 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( - layout.push_constants_infos.cs.unwrap().buffer_index as _, - (layout.total_push_constants as usize * WORD_SIZE) as _, - state_pc.as_ptr().cast(), - ) - } - if stages.contains(wgt::ShaderStages::VERTEX) { - self.state.render.as_ref().unwrap().set_vertex_bytes( - layout.push_constants_infos.vs.unwrap().buffer_index as _, - (layout.total_push_constants as usize * WORD_SIZE) as _, - state_pc.as_ptr().cast(), - ) - } - if stages.contains(wgt::ShaderStages::FRAGMENT) { - self.state.render.as_ref().unwrap().set_fragment_bytes( - layout.push_constants_infos.fs.unwrap().buffer_index as _, - (layout.total_push_constants as usize * WORD_SIZE) as _, - state_pc.as_ptr().cast(), - ) - } - if stages.contains(wgt::ShaderStages::TASK) { - self.state.render.as_ref().unwrap().set_object_bytes( - layout.push_constants_infos.ts.unwrap().buffer_index as _, - (layout.total_push_constants as usize * WORD_SIZE) as _, + if let Some(ref compute) = self.state.compute { + compute.set_bytes( + layout.immediates_infos.cs.unwrap().buffer_index as _, + (layout.total_immediates as usize * WORD_SIZE) as _, state_pc.as_ptr().cast(), ) } - if stages.contains(wgt::ShaderStages::MESH) { - self.state.render.as_ref().unwrap().set_object_bytes( - layout.push_constants_infos.ms.unwrap().buffer_index as _, - (layout.total_push_constants as usize * WORD_SIZE) as _, - state_pc.as_ptr().cast(), - ) + if let Some(ref render) = self.state.render { + if let Some(vs) = layout.immediates_infos.vs { + render.set_vertex_bytes( + vs.buffer_index as _, + (layout.total_immediates as usize * WORD_SIZE) as _, + state_pc.as_ptr().cast(), + ) + } + if let Some(fs) = layout.immediates_infos.fs { + render.set_fragment_bytes( + fs.buffer_index as _, + (layout.total_immediates as usize * WORD_SIZE) as _, + state_pc.as_ptr().cast(), + ) + } + if let Some(ts) = layout.immediates_infos.ts { + if self.shared.private_caps.mesh_shaders { + render.set_object_bytes( + ts.buffer_index as _, + (layout.total_immediates as usize * WORD_SIZE) as _, + state_pc.as_ptr().cast(), + ) + } + } + if let Some(ms) = layout.immediates_infos.ms { + if self.shared.private_caps.mesh_shaders { + render.set_object_bytes( + ms.buffer_index as _, + (layout.total_immediates as usize * WORD_SIZE) as _, + state_pc.as_ptr().cast(), + ) + } + } } } diff --git a/third_party/rust/wgpu-hal/src/metal/device.rs b/third_party/rust/wgpu-hal/src/metal/device.rs @@ -2,8 +2,6 @@ use alloc::{borrow::ToOwned as _, sync::Arc, vec::Vec}; use core::{ptr::NonNull, sync::atomic}; use std::{thread, time}; -use parking_lot::Mutex; - use super::{conv, PassthroughShader}; use crate::auxil::map_naga_stage; use crate::metal::ShaderModuleSource; @@ -215,10 +213,9 @@ impl super::Device { let library = self .shared .device - .lock() .new_library_with_source(source.as_ref(), &options) .map_err(|err| { - log::warn!("Naga generated shader:\n{source}"); + log::debug!("Naga generated shader:\n{source}"); crate::PipelineError::Linkage(stage_bit, format!("Metal: {err}")) })?; @@ -362,7 +359,7 @@ impl super::Device { super::Buffer { raw, size } } - pub fn raw_device(&self) -> &Mutex<metal::Device> { + pub fn raw_device(&self) -> &metal::Device { &self.shared.device } } @@ -386,7 +383,7 @@ impl crate::Device for super::Device { //TODO: HazardTrackingModeUntracked objc::rc::autoreleasepool(|| { - let raw = self.shared.device.lock().new_buffer(desc.size, options); + let raw = self.shared.device.new_buffer(desc.size, options); if let Some(label) = desc.label { raw.set_label(label); } @@ -468,7 +465,7 @@ impl crate::Device for super::Device { descriptor.set_usage(conv::map_texture_usage(desc.format, desc.usage)); descriptor.set_storage_mode(mtl_storage_mode); - let raw = self.shared.device.lock().new_texture(&descriptor); + let raw = self.shared.device.new_texture(&descriptor); if raw.as_ptr().is_null() { return Err(crate::DeviceError::OutOfMemory); } @@ -620,7 +617,7 @@ impl crate::Device for super::Device { if self.features.contains(wgt::Features::TEXTURE_BINDING_ARRAY) { descriptor.set_support_argument_buffers(true); } - let raw = self.shared.device.lock().new_sampler(&descriptor); + let raw = self.shared.device.new_sampler(&descriptor); self.counters.samplers.add(1); @@ -687,31 +684,15 @@ impl crate::Device for super::Device { }); let mut bind_group_infos = arrayvec::ArrayVec::new(); - // First, place the push constants - let mut total_push_constants = 0; + // First, place the immediates for info in stage_data.iter_mut() { - for pcr in desc.push_constant_ranges { - if pcr.stages.contains(map_naga_stage(info.stage)) { - debug_assert_eq!(pcr.range.end % 4, 0); - info.pc_limit = (pcr.range.end / 4).max(info.pc_limit); - } - } + info.pc_limit = desc.immediate_size; - // round up the limits alignment to 4, so that it matches MTL compiler logic - const LIMIT_MASK: u32 = 3; - //TODO: figure out what and how exactly does the alignment. Clearly, it's not - // straightforward, given that value of 2 stays non-aligned. - if info.pc_limit > LIMIT_MASK { - info.pc_limit = (info.pc_limit + LIMIT_MASK) & !LIMIT_MASK; - } - - // handle the push constant buffer assignment and shader overrides + // handle the immediate data buffer assignment and shader overrides if info.pc_limit != 0 { info.pc_buffer = Some(info.counters.buffers); info.counters.buffers += 1; } - - total_push_constants = total_push_constants.max(info.pc_limit); } // Second, place the described resources @@ -820,8 +801,8 @@ impl crate::Device for super::Device { } } - let push_constants_infos = stage_data.map_ref(|info| { - info.pc_buffer.map(|buffer_index| super::PushConstantsInfo { + let immediates_infos = stage_data.map_ref(|info| { + info.pc_buffer.map(|buffer_index| super::ImmediateDataInfo { count: info.pc_limit, buffer_index, }) @@ -830,7 +811,7 @@ impl crate::Device for super::Device { let total_counters = stage_data.map_ref(|info| info.counters.clone()); let per_stage_map = stage_data.map(|info| naga::back::msl::EntryPointResources { - push_constant_buffer: info + immediates_buffer: info .pc_buffer .map(|buffer_index| buffer_index as naga::back::msl::Slot), sizes_buffer: info @@ -843,9 +824,9 @@ impl crate::Device for super::Device { Ok(super::PipelineLayout { bind_group_infos, - push_constants_infos, + immediates_infos, total_counters, - total_push_constants, + total_immediates: desc.immediate_size, per_stage_map, }) } @@ -891,7 +872,7 @@ impl crate::Device for super::Device { let uses = conv::map_resource_usage(&layout.ty); // Create argument buffer for this array - let buffer = self.shared.device.lock().new_buffer( + let buffer = self.shared.device.new_buffer( 8 * count as u64, MTLResourceOptions::HazardTrackingModeUntracked | MTLResourceOptions::StorageModeShared, @@ -1073,8 +1054,8 @@ impl crate::Device for super::Device { num_workgroups, } => { let options = metal::CompileOptions::new(); - // Obtain the locked device from shared - let device = self.shared.device.lock(); + // Obtain the device from shared + let device = &self.shared.device; let library = device .new_library_with_source(source, &options) .map_err(|e| crate::ShaderError::Compilation(format!("MSL: {e:?}")))?; @@ -1233,7 +1214,7 @@ impl crate::Device for super::Device { } vs_info = Some(super::PipelineStageInfo { - push_constants: desc.layout.push_constants_infos.vs, + immediates: desc.layout.immediates_infos.vs, sizes_slot: desc.layout.per_stage_map.vs.sizes_buffer, sized_bindings: vs.sized_bindings, vertex_buffer_mappings, @@ -1326,7 +1307,7 @@ impl crate::Device for super::Device { ); } ts_info = Some(super::PipelineStageInfo { - push_constants: desc.layout.push_constants_infos.ts, + immediates: desc.layout.immediates_infos.ts, sizes_slot: desc.layout.per_stage_map.ts.sizes_buffer, sized_bindings: ts.sized_bindings, vertex_buffer_mappings: vec![], @@ -1355,7 +1336,7 @@ impl crate::Device for super::Device { ); } ms_info = Some(super::PipelineStageInfo { - push_constants: desc.layout.push_constants_infos.ms, + immediates: desc.layout.immediates_infos.ms, sizes_slot: desc.layout.per_stage_map.ms.sizes_buffer, sized_bindings: ms.sized_bindings, vertex_buffer_mappings: vec![], @@ -1398,7 +1379,7 @@ impl crate::Device for super::Device { } Some(super::PipelineStageInfo { - push_constants: desc.layout.push_constants_infos.fs, + immediates: desc.layout.immediates_infos.fs, sizes_slot: desc.layout.per_stage_map.fs.sizes_buffer, sized_bindings: fs.sized_bindings, vertex_buffer_mappings: vec![], @@ -1459,11 +1440,7 @@ impl crate::Device for super::Device { } let ds_descriptor = create_depth_stencil_desc(ds); - let raw = self - .shared - .device - .lock() - .new_depth_stencil_state(&ds_descriptor); + let raw = self.shared.device.new_depth_stencil_state(&ds_descriptor); Some((raw, ds.bias)) } None => None, @@ -1496,10 +1473,10 @@ impl crate::Device for super::Device { // Create the pipeline from descriptor let raw = match descriptor { MetalGenericRenderPipelineDescriptor::Standard(d) => { - self.shared.device.lock().new_render_pipeline_state(&d) + self.shared.device.new_render_pipeline_state(&d) } MetalGenericRenderPipelineDescriptor::Mesh(d) => { - self.shared.device.lock().new_mesh_render_pipeline_state(&d) + self.shared.device.new_mesh_render_pipeline_state(&d) } } .map_err(|e| { @@ -1585,7 +1562,7 @@ impl crate::Device for super::Device { let cs_info = super::PipelineStageInfo { library: Some(cs.library), - push_constants: desc.layout.push_constants_infos.cs, + immediates: desc.layout.immediates_infos.cs, sizes_slot: desc.layout.per_stage_map.cs.sizes_buffer, sized_bindings: cs.sized_bindings, vertex_buffer_mappings: vec![], @@ -1600,7 +1577,6 @@ impl crate::Device for super::Device { let raw = self .shared .device - .lock() .new_compute_pipeline_state(&descriptor) .map_err(|e| { crate::PipelineError::Linkage( @@ -1637,7 +1613,7 @@ impl crate::Device for super::Device { let size = desc.count as u64 * crate::QUERY_SIZE; let options = MTLResourceOptions::empty(); //TODO: HazardTrackingModeUntracked - let raw_buffer = self.shared.device.lock().new_buffer(size, options); + let raw_buffer = self.shared.device.new_buffer(size, options); if let Some(label) = desc.label { raw_buffer.set_label(label); } @@ -1649,7 +1625,7 @@ impl crate::Device for super::Device { } wgt::QueryType::Timestamp => { let size = desc.count as u64 * crate::QUERY_SIZE; - let device = self.shared.device.lock(); + let device = &self.shared.device; let destination_buffer = device.new_buffer(size, MTLResourceOptions::empty()); let csb_desc = metal::CounterSampleBufferDescriptor::new(); @@ -1695,13 +1671,13 @@ impl crate::Device for super::Device { } unsafe fn destroy_query_set(&self, _set: super::QuerySet) { - self.counters.query_sets.add(1); + self.counters.query_sets.sub(1); } unsafe fn create_fence(&self) -> DeviceResult<super::Fence> { self.counters.fences.add(1); let shared_event = if self.shared.private_caps.supports_shared_event { - Some(self.shared.device.lock().new_shared_event()) + Some(self.shared.device.new_shared_event()) } else { None }; @@ -1765,9 +1741,9 @@ impl crate::Device for super::Device { if !self.shared.private_caps.supports_capture_manager { return false; } - let device = self.shared.device.lock(); + let device = &self.shared.device; let shared_capture_manager = metal::CaptureManager::shared(); - let default_capture_scope = shared_capture_manager.new_capture_scope_with_device(&device); + let default_capture_scope = shared_capture_manager.new_capture_scope_with_device(device); shared_capture_manager.set_default_capture_scope(&default_capture_scope); shared_capture_manager.start_capture_with_scope(&default_capture_scope); default_capture_scope.begin_scope(); diff --git a/third_party/rust/wgpu-hal/src/metal/mod.rs b/third_party/rust/wgpu-hal/src/metal/mod.rs @@ -3,11 +3,11 @@ ## Pipeline Layout -In Metal, push constants, vertex buffers, and resources in the bind groups +In Metal, immediates, vertex buffers, and resources in the bind groups are all placed together in the native resource bindings, which work similarly to D3D11: there are tables of textures, buffers, and samplers. -We put push constants first (if any) in the table, followed by bind group 0 +We put immediates first (if any) in the table, followed by bind group 0 resources, followed by other bind groups. The vertex buffers are bound at the very end of the VS buffer table. @@ -160,6 +160,12 @@ impl crate::Instance for Instance { driver: String::new(), driver_info: String::new(), backend: wgt::Backend::Metal, + // These are hardcoded based on typical values for Metal devices + // + // See <https://github.com/gpuweb/gpuweb/blob/main/proposals/subgroups.md#adapter-info> + // for more information. + subgroup_min_size: 4, + subgroup_max_size: 64, transient_saves_memory: shared.private_caps.supports_memoryless_storage, }, features: shared.private_caps.features(), @@ -196,10 +202,11 @@ bitflags!( } ); +// TODO(https://github.com/gfx-rs/wgpu/issues/8715): Eliminate duplication with +// `wgt::Limits`. Keeping multiple sets of limits creates a risk of confusion. #[allow(dead_code)] #[derive(Clone, Debug)] struct PrivateCapabilities { - family_check: bool, msl_version: MTLLanguageVersion, fragment_rw_storage: bool, read_write_texture_tier: MTLReadWriteTextureTier, @@ -207,8 +214,7 @@ struct PrivateCapabilities { msaa_apple3: bool, msaa_apple7: bool, resource_heaps: bool, - argument_buffers: MTLArgumentBuffersTier, - shared_textures: bool, + argument_buffers: Option<MTLArgumentBuffersTier>, mutable_comparison_samplers: bool, sampler_clamp_to_border: bool, indirect_draw_dispatch: bool, @@ -261,6 +267,7 @@ struct PrivateCapabilities { format_rgba32float_color_write: bool, format_rgba32float_all: bool, format_depth16unorm: bool, + format_depth16unorm_filter: bool, format_depth32float_filter: bool, format_depth32float_none: bool, format_bgr10a2_all: bool, @@ -272,6 +279,11 @@ struct PrivateCapabilities { max_binding_array_elements: ResourceIndex, max_sampler_binding_array_elements: ResourceIndex, buffer_alignment: u64, + + /// Platform-reported maximum buffer size + /// + /// This value is clamped to `u32::MAX` for `wgt::Limits`, so you probably + /// shouldn't be looking at this copy. max_buffer_size: u64, max_texture_size: u64, max_texture_3d_size: u64, @@ -299,10 +311,13 @@ struct PrivateCapabilities { timestamp_query_support: TimestampQuerySupport, supports_simd_scoped_operations: bool, int64: bool, + int64_atomics_min_max: bool, int64_atomics: bool, float_atomics: bool, supports_shared_event: bool, mesh_shaders: bool, + max_mesh_task_workgroup_count: u32, + max_task_payload_size: u32, supported_vertex_amplification_factor: u32, shader_barycentrics: bool, supports_memoryless_storage: bool, @@ -331,7 +346,7 @@ impl Default for Settings { } struct AdapterShared { - device: Mutex<metal::Device>, + device: metal::Device, disabilities: PrivateDisabilities, private_caps: PrivateCapabilities, settings: Settings, @@ -349,7 +364,7 @@ impl AdapterShared { Self { disabilities: PrivateDisabilities::new(&device), private_caps, - device: Mutex::new(device), + device, settings: Settings::default(), presentation_timer: time::PresentationTimer::new(), } @@ -391,9 +406,6 @@ pub struct Surface { render_layer: Mutex<metal::MetalLayer>, swapchain_format: RwLock<Option<wgt::TextureFormat>>, extent: RwLock<wgt::Extent3d>, - // Useful for UI-intensive applications that are sensitive to - // window resizing. - pub present_with_transaction: bool, } unsafe impl Send for Surface {} @@ -403,6 +415,8 @@ unsafe impl Sync for Surface {} pub struct SurfaceTexture { texture: Texture, drawable: metal::MetalDrawable, + // Useful for UI-intensive applications that are sensitive to + // window resizing. present_with_transaction: bool, } @@ -679,7 +693,7 @@ struct BindGroupLayoutInfo { } #[derive(Copy, Clone, Debug, Eq, PartialEq)] -struct PushConstantsInfo { +struct ImmediateDataInfo { count: u32, buffer_index: ResourceIndex, } @@ -687,9 +701,9 @@ struct PushConstantsInfo { #[derive(Debug)] pub struct PipelineLayout { bind_group_infos: ArrayVec<BindGroupLayoutInfo, { crate::MAX_BIND_GROUPS }>, - push_constants_infos: MultiStageData<Option<PushConstantsInfo>>, + immediates_infos: MultiStageData<Option<ImmediateDataInfo>>, total_counters: MultiStageResourceCounters, - total_push_constants: u32, + total_immediates: u32, per_stage_map: MultiStageResources, } @@ -832,7 +846,7 @@ impl crate::DynShaderModule for ShaderModule {} struct PipelineStageInfo { #[allow(dead_code)] library: Option<metal::Library>, - push_constants: Option<PushConstantsInfo>, + immediates: Option<ImmediateDataInfo>, /// The buffer argument table index at which we pass runtime-sized arrays' buffer sizes. /// @@ -856,7 +870,7 @@ struct PipelineStageInfo { impl PipelineStageInfo { fn clear(&mut self) { - self.push_constants = None; + self.immediates = None; self.sizes_slot = None; self.sized_bindings.clear(); self.vertex_buffer_mappings.clear(); @@ -866,7 +880,7 @@ impl PipelineStageInfo { } fn assign_from(&mut self, other: &Self) { - self.push_constants = other.push_constants; + self.immediates = other.immediates; self.sizes_slot = other.sizes_slot; self.sized_bindings.clear(); self.sized_bindings.extend_from_slice(&other.sized_bindings); @@ -993,7 +1007,7 @@ struct CommandState { /// checks and the WGSL `arrayLength` function. /// /// For each stage `S` in `stage_infos`, we consult this to find the sizes - /// of the buffers listed in [`stage_infos.S.sized_bindings`], which we must + /// of the buffers listed in `stage_infos.S.sized_bindings`, which we must /// pass to the entry point. /// /// See `device::CompiledShader::sized_bindings` for more details. @@ -1003,7 +1017,7 @@ struct CommandState { vertex_buffer_size_map: FastHashMap<u64, wgt::BufferSize>, - push_constants: Vec<u32>, + immediates: Vec<u32>, /// Timer query that should be executed when the next pass starts. pending_timer_queries: Vec<(QuerySet, u32)>, @@ -1049,3 +1063,11 @@ impl crate::DynPipelineCache for PipelineCache {} pub struct AccelerationStructure; impl crate::DynAccelerationStructure for AccelerationStructure {} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum OsType { + Macos, + Ios, + Tvos, + VisionOs, +} diff --git a/third_party/rust/wgpu-hal/src/metal/surface.rs b/third_party/rust/wgpu-hal/src/metal/surface.rs @@ -28,7 +28,6 @@ impl super::Surface { render_layer: Mutex::new(layer), swapchain_format: RwLock::new(None), extent: RwLock::new(wgt::Extent3d::default()), - present_with_transaction: false, } } @@ -50,6 +49,10 @@ impl super::Surface { Self::new(layer.to_owned()) } + pub fn render_layer(&self) -> &Mutex<metal::MetalLayer> { + &self.render_layer + } + /// Get or create a new `CAMetalLayer` associated with the given `NSView` /// or `UIView`. /// @@ -160,11 +163,10 @@ impl crate::Surface for super::Surface { _ => (), } - let device_raw = device.shared.device.lock(); - render_layer.set_device(&device_raw); + let device_raw = &device.shared.device; + render_layer.set_device(device_raw); render_layer.set_pixel_format(caps.map_format(config.format)); render_layer.set_framebuffer_only(framebuffer_only); - render_layer.set_presents_with_transaction(self.present_with_transaction); // opt-in to Metal EDR // EDR potentially more power used in display and more bandwidth, memory footprint. let wants_edr = config.format == wgt::TextureFormat::Rgba16Float; @@ -220,7 +222,7 @@ impl crate::Surface for super::Surface { }, }, drawable, - present_with_transaction: self.present_with_transaction, + present_with_transaction: render_layer.presents_with_transaction(), }; Ok(Some(crate::AcquiredSurfaceTexture { diff --git a/third_party/rust/wgpu-hal/src/noop/command.rs b/third_party/rust/wgpu-hal/src/noop/command.rs @@ -162,14 +162,7 @@ impl crate::CommandEncoder for CommandBuffer { dynamic_offsets: &[wgt::DynamicOffset], ) { } - unsafe fn set_push_constants( - &mut self, - layout: &Resource, - stages: wgt::ShaderStages, - offset_bytes: u32, - data: &[u32], - ) { - } + unsafe fn set_immediates(&mut self, layout: &Resource, offset_bytes: u32, data: &[u32]) {} unsafe fn insert_debug_marker(&mut self, label: &str) {} unsafe fn begin_debug_marker(&mut self, group_label: &str) {} diff --git a/third_party/rust/wgpu-hal/src/noop/mod.rs b/third_party/rust/wgpu-hal/src/noop/mod.rs @@ -98,6 +98,7 @@ impl crate::Instance for Context { name: _, flags: _, memory_budget_thresholds: _, + telemetry: _, } = *desc; if enable { Ok(Context) @@ -141,6 +142,8 @@ pub fn adapter_info() -> wgt::AdapterInfo { driver: String::from("wgpu"), driver_info: String::new(), backend: wgt::Backend::Noop, + subgroup_min_size: wgt::MINIMUM_SUBGROUP_MIN_SIZE, + subgroup_max_size: wgt::MAXIMUM_SUBGROUP_MAX_SIZE, transient_saves_memory: false, } } @@ -189,15 +192,20 @@ pub const CAPABILITIES: crate::Capabilities = { max_compute_workgroup_size_y: ALLOC_MAX_U32, max_compute_workgroup_size_z: ALLOC_MAX_U32, max_compute_workgroups_per_dimension: ALLOC_MAX_U32, - min_subgroup_size: 1, - max_subgroup_size: ALLOC_MAX_U32, - max_push_constant_size: ALLOC_MAX_U32, + max_immediate_size: ALLOC_MAX_U32, max_non_sampler_bindings: ALLOC_MAX_U32, - max_task_workgroup_total_count: ALLOC_MAX_U32, - max_task_workgroups_per_dimension: ALLOC_MAX_U32, - max_mesh_multiview_view_count: ALLOC_MAX_U32, + max_task_mesh_workgroup_total_count: ALLOC_MAX_U32, + max_task_mesh_workgroups_per_dimension: ALLOC_MAX_U32, + max_task_invocations_per_workgroup: ALLOC_MAX_U32, + max_task_invocations_per_dimension: ALLOC_MAX_U32, + max_mesh_invocations_per_workgroup: ALLOC_MAX_U32, + max_mesh_invocations_per_dimension: ALLOC_MAX_U32, + max_task_payload_size: ALLOC_MAX_U32, + max_mesh_output_vertices: ALLOC_MAX_U32, + max_mesh_output_primitives: ALLOC_MAX_U32, max_mesh_output_layers: ALLOC_MAX_U32, + max_mesh_multiview_view_count: ALLOC_MAX_U32, max_blas_primitive_count: ALLOC_MAX_U32, max_blas_geometry_count: ALLOC_MAX_U32, diff --git a/third_party/rust/wgpu-hal/src/vulkan/adapter.rs b/third_party/rust/wgpu-hal/src/vulkan/adapter.rs @@ -4,7 +4,7 @@ use core::{ffi::CStr, marker::PhantomData}; use ash::{ext, google, khr, vk}; use parking_lot::Mutex; -use crate::vulkan::semaphore_list::SemaphoreList; +use crate::{vulkan::semaphore_list::SemaphoreList, AllocationSizes}; use super::semaphore_list::SemaphoreListMode; @@ -130,6 +130,11 @@ pub struct PhysicalDeviceFeatures { /// Features provided by `VK_KHR_fragment_shader_barycentric` shader_barycentrics: Option<vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR<'static>>, + + /// Features provided by `VK_KHR_portability_subset`. + /// + /// Strictly speaking this tells us what features we *don't* have compared to core. + portability_subset: Option<vk::PhysicalDevicePortabilitySubsetFeaturesKHR<'static>>, } impl PhysicalDeviceFeatures { @@ -206,6 +211,9 @@ impl PhysicalDeviceFeatures { if let Some(ref mut feature) = self.shader_barycentrics { info = info.push_next(feature); } + if let Some(ref mut feature) = self.portability_subset { + info = info.push_next(feature); + } info } @@ -551,6 +559,17 @@ impl PhysicalDeviceFeatures { } else { None }, + portability_subset: if enabled_extensions.contains(&khr::portability_subset::NAME) { + let multisample_array_needed = + requested_features.intersects(wgt::Features::MULTISAMPLE_ARRAY); + + Some( + vk::PhysicalDevicePortabilitySubsetFeaturesKHR::default() + .multisample_array_image(multisample_array_needed), + ) + } else { + None + }, } } @@ -567,16 +586,14 @@ impl PhysicalDeviceFeatures { instance: &ash::Instance, phd: vk::PhysicalDevice, caps: &PhysicalDeviceProperties, + queue_props: &vk::QueueFamilyProperties, ) -> (wgt::Features, wgt::DownlevelFlags) { use wgt::{DownlevelFlags as Df, Features as F}; let mut features = F::empty() | F::MAPPABLE_PRIMARY_BUFFERS - | F::PUSH_CONSTANTS + | F::IMMEDIATES | F::ADDRESS_MODE_CLAMP_TO_BORDER | F::ADDRESS_MODE_CLAMP_TO_ZERO - | F::TIMESTAMP_QUERY - | F::TIMESTAMP_QUERY_INSIDE_ENCODERS - | F::TIMESTAMP_QUERY_INSIDE_PASSES | F::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES | F::CLEAR_TEXTURE | F::PIPELINE_CACHE @@ -619,6 +636,13 @@ impl PhysicalDeviceFeatures { dl_flags.set(Df::DEPTH_BIAS_CLAMP, self.core.depth_bias_clamp != 0); features.set( + F::TIMESTAMP_QUERY + | F::TIMESTAMP_QUERY_INSIDE_ENCODERS + | F::TIMESTAMP_QUERY_INSIDE_PASSES, + // Vulkan strictly defines this as either 36-64, or zero. + queue_props.timestamp_valid_bits >= 36, + ); + features.set( F::INDIRECT_FIRST_INSTANCE, self.core.draw_indirect_first_instance != 0, ); @@ -927,6 +951,15 @@ impl PhysicalDeviceFeatures { mesh_shader.multiview_mesh_shader != 0, ); } + + // Not supported by default by `VK_KHR_portability_subset`, which we use on apple platforms. + features.set( + F::MULTISAMPLE_ARRAY, + self.portability_subset + .map(|p| p.multisample_array_image == vk::TRUE) + .unwrap_or(true), + ); + (features, dl_flags) } } @@ -1153,7 +1186,7 @@ impl PhysicalDeviceProperties { if self.supports_extension(ext::memory_budget::NAME) { extensions.push(ext::memory_budget::NAME); } else { - log::warn!("VK_EXT_memory_budget is not available.") + log::debug!("VK_EXT_memory_budget is not available.") } // Require `VK_KHR_draw_indirect_count` if the associated feature was requested @@ -1231,19 +1264,40 @@ impl PhysicalDeviceProperties { .min(limits.max_compute_work_group_count[1]) .min(limits.max_compute_work_group_count[2]); let ( - max_task_workgroup_total_count, - max_task_workgroups_per_dimension, - max_mesh_multiview_view_count, - max_mesh_output_layers, - ) = match self.mesh_shader { - Some(m) => ( - m.max_task_work_group_total_count, - m.max_task_work_group_count.into_iter().min().unwrap(), - m.max_mesh_multiview_view_count, - m.max_mesh_output_layers, - ), - None => (0, 0, 0, 0), - }; + mut max_task_mesh_workgroup_total_count, + mut max_task_mesh_workgroups_per_dimension, + mut max_task_invocations_per_workgroup, + mut max_task_invocations_per_dimension, + mut max_mesh_invocations_per_workgroup, + mut max_mesh_invocations_per_dimension, + mut max_task_payload_size, + mut max_mesh_output_vertices, + mut max_mesh_output_primitives, + mut max_mesh_output_layers, + mut max_mesh_multiview_view_count, + ) = Default::default(); + if let Some(m) = self.mesh_shader { + max_task_mesh_workgroup_total_count = m + .max_task_work_group_total_count + .min(m.max_mesh_work_group_total_count); + max_task_mesh_workgroups_per_dimension = m + .max_task_work_group_count + .into_iter() + .chain(m.max_mesh_work_group_count) + .min() + .unwrap(); + max_task_invocations_per_workgroup = m.max_task_work_group_invocations; + max_task_invocations_per_dimension = + m.max_task_work_group_size.into_iter().min().unwrap(); + max_mesh_invocations_per_workgroup = m.max_mesh_work_group_invocations; + max_mesh_invocations_per_dimension = + m.max_mesh_work_group_size.into_iter().min().unwrap(); + max_task_payload_size = m.max_task_payload_size; + max_mesh_output_vertices = m.max_mesh_output_vertices; + max_mesh_output_primitives = m.max_mesh_output_primitives; + max_mesh_output_layers = m.max_mesh_output_layers; + max_mesh_multiview_view_count = m.max_mesh_multiview_view_count; + } // Prevent very large buffers on mesa and most android devices, and in all cases // don't risk confusing JS by exceeding the range of a double. @@ -1300,14 +1354,12 @@ impl PhysicalDeviceProperties { .map(|a| a.max_multiview_view_count.min(32)) .unwrap_or(0); - wgt::Limits { + crate::auxil::apply_hal_limits(wgt::Limits { max_texture_dimension_1d: limits.max_image_dimension1_d, max_texture_dimension_2d: limits.max_image_dimension2_d, max_texture_dimension_3d: limits.max_image_dimension3_d, max_texture_array_layers: limits.max_image_array_layers, - max_bind_groups: limits - .max_bound_descriptor_sets - .min(crate::MAX_BIND_GROUPS as u32), + max_bind_groups: limits.max_bound_descriptor_sets, max_bindings_per_bind_group: wgt::Limits::default().max_bindings_per_bind_group, max_dynamic_uniform_buffers_per_pipeline_layout: limits .max_descriptor_set_uniform_buffers_dynamic, @@ -1326,28 +1378,16 @@ impl PhysicalDeviceProperties { max_storage_buffer_binding_size: limits .max_storage_buffer_range .min(crate::auxil::MAX_I32_BINDING_SIZE), - max_vertex_buffers: limits - .max_vertex_input_bindings - .min(crate::MAX_VERTEX_BUFFERS as u32), + max_vertex_buffers: limits.max_vertex_input_bindings, max_vertex_attributes: limits.max_vertex_input_attributes, max_vertex_buffer_array_stride: limits.max_vertex_input_binding_stride, - min_subgroup_size: self - .subgroup_size_control - .map(|subgroup_size| subgroup_size.min_subgroup_size) - .unwrap_or(0), - max_subgroup_size: self - .subgroup_size_control - .map(|subgroup_size| subgroup_size.max_subgroup_size) - .unwrap_or(0), - max_push_constant_size: limits.max_push_constants_size, + max_immediate_size: limits.max_push_constants_size, min_uniform_buffer_offset_alignment: limits.min_uniform_buffer_offset_alignment as u32, min_storage_buffer_offset_alignment: limits.min_storage_buffer_offset_alignment as u32, max_inter_stage_shader_components: limits .max_vertex_output_components .min(limits.max_fragment_input_components), - max_color_attachments: limits - .max_color_attachments - .min(crate::MAX_COLOR_ATTACHMENTS as u32), + max_color_attachments: limits.max_color_attachments, max_color_attachment_bytes_per_sample, max_compute_workgroup_storage_size: limits.max_compute_shared_memory_size, max_compute_invocations_per_workgroup: limits.max_compute_work_group_invocations, @@ -1358,10 +1398,19 @@ impl PhysicalDeviceProperties { max_buffer_size, max_non_sampler_bindings: u32::MAX, - max_task_workgroup_total_count, - max_task_workgroups_per_dimension, - max_mesh_multiview_view_count, + max_task_mesh_workgroup_total_count, + max_task_mesh_workgroups_per_dimension, + max_task_invocations_per_workgroup, + max_task_invocations_per_dimension, + + max_mesh_invocations_per_workgroup, + max_mesh_invocations_per_dimension, + + max_task_payload_size, + max_mesh_output_vertices, + max_mesh_output_primitives, max_mesh_output_layers, + max_mesh_multiview_view_count, max_blas_primitive_count, max_blas_geometry_count, @@ -1369,7 +1418,7 @@ impl PhysicalDeviceProperties { max_acceleration_structures_per_shader_stage, max_multiview_view_count, - } + }) } /// Return a `wgpu_hal::Alignments` structure describing this adapter. @@ -1698,6 +1747,13 @@ impl super::InstanceShared { features2 = features2.push_next(next); } + if capabilities.supports_extension(khr::portability_subset::NAME) { + let next = features + .portability_subset + .insert(vk::PhysicalDevicePortabilitySubsetFeaturesKHR::default()); + features2 = features2.push_next(next); + } + unsafe { get_device_properties.get_physical_device_features2(phd, &mut features2) }; features2.features } else { @@ -1776,10 +1832,16 @@ impl super::Instance { .to_owned() }, backend: wgt::Backend::Vulkan, + subgroup_min_size: phd_capabilities + .subgroup_size_control + .map(|subgroup_size| subgroup_size.min_subgroup_size) + .unwrap_or(wgt::MINIMUM_SUBGROUP_MIN_SIZE), + subgroup_max_size: phd_capabilities + .subgroup_size_control + .map(|subgroup_size| subgroup_size.max_subgroup_size) + .unwrap_or(wgt::MAXIMUM_SUBGROUP_MAX_SIZE), transient_saves_memory: supports_lazily_allocated, }; - let (available_features, mut downlevel_flags) = - phd_features.to_wgpu(&self.shared.raw, phd, &phd_capabilities); let mut workarounds = super::Workarounds::empty(); { // TODO: only enable for particular devices @@ -1794,15 +1856,6 @@ impl super::Instance { ); }; - if info.driver == "llvmpipe" { - // The `F16_IN_F32` instructions do not normally require native `F16` support, but on - // llvmpipe, they do. - downlevel_flags.set( - wgt::DownlevelFlags::SHADER_F16_IN_F32, - available_features.contains(wgt::Features::SHADER_F16), - ); - } - if let Some(driver) = phd_capabilities.driver { if driver.conformance_version.major == 0 { if driver.driver_id == vk::DriverId::MOLTENVK { @@ -1812,9 +1865,9 @@ impl super::Instance { .flags .contains(wgt::InstanceFlags::ALLOW_UNDERLYING_NONCOMPLIANT_ADAPTER) { - log::warn!("Adapter is not Vulkan compliant: {}", info.name); + log::debug!("Adapter is not Vulkan compliant: {}", info.name); } else { - log::warn!( + log::debug!( "Adapter is not Vulkan compliant, hiding adapter: {}", info.name ); @@ -1825,7 +1878,7 @@ impl super::Instance { if phd_capabilities.device_api_version == vk::API_VERSION_1_0 && !phd_capabilities.supports_extension(khr::storage_buffer_storage_class::NAME) { - log::warn!( + log::debug!( "SPIR-V storage buffer class is not supported, hiding adapter: {}", info.name ); @@ -1834,7 +1887,7 @@ impl super::Instance { if !phd_capabilities.supports_extension(khr::maintenance1::NAME) && phd_capabilities.device_api_version < vk::API_VERSION_1_1 { - log::warn!( + log::debug!( "VK_KHR_maintenance1 is not supported, hiding adapter: {}", info.name ); @@ -1846,12 +1899,37 @@ impl super::Instance { .raw .get_physical_device_queue_family_properties(phd) }; - let queue_flags = queue_families.first()?.queue_flags; + let queue_family_properties = queue_families.first()?; + let queue_flags = queue_family_properties.queue_flags; if !queue_flags.contains(vk::QueueFlags::GRAPHICS) { - log::warn!("The first queue only exposes {queue_flags:?}"); + log::debug!("The first queue only exposes {queue_flags:?}"); return None; } + let (available_features, mut downlevel_flags) = phd_features.to_wgpu( + &self.shared.raw, + phd, + &phd_capabilities, + queue_family_properties, + ); + + if info.driver == "llvmpipe" { + // The `F16_IN_F32` instructions do not normally require native `F16` support, but on + // llvmpipe, they do. + downlevel_flags.set( + wgt::DownlevelFlags::SHADER_F16_IN_F32, + available_features.contains(wgt::Features::SHADER_F16), + ); + } + + let has_robust_buffer_access2 = phd_features + .robustness2 + .as_ref() + .map(|r| r.robust_buffer_access2 == 1) + .unwrap_or_default(); + + let alignments = phd_capabilities.to_hal_alignments(has_robust_buffer_access2); + let private_caps = super::PrivateCapabilities { image_view_usage: phd_capabilities.device_api_version >= vk::API_VERSION_1_1 || phd_capabilities.supports_extension(khr::maintenance2::NAME), @@ -1883,6 +1961,7 @@ impl super::Instance { depth_stencil_required_flags(), ), multi_draw_indirect: phd_features.core.multi_draw_indirect != 0, + max_draw_indirect_count: phd_capabilities.properties.limits.max_draw_indirect_count, non_coherent_map_mask: phd_capabilities.properties.limits.non_coherent_atom_size - 1, can_present: true, //TODO: make configurable @@ -1893,11 +1972,7 @@ impl super::Instance { .image_robustness .is_some_and(|ext| ext.robust_image_access != 0), }, - robust_buffer_access2: phd_features - .robustness2 - .as_ref() - .map(|r| r.robust_buffer_access2 == 1) - .unwrap_or_default(), + robust_buffer_access2: has_robust_buffer_access2, robust_image_access2: phd_features .robustness2 .as_ref() @@ -1922,10 +1997,11 @@ impl super::Instance { .multiview .map(|a| a.max_multiview_instance_index) .unwrap_or(0), + scratch_buffer_alignment: alignments.ray_tracing_scratch_buffer_alignment, }; let capabilities = crate::Capabilities { limits: phd_capabilities.to_wgpu_limits(), - alignments: phd_capabilities.to_hal_alignments(private_caps.robust_buffer_access2), + alignments, downlevel: wgt::DownlevelCapabilities { flags: downlevel_flags, limits: wgt::DownlevelLimits {}, @@ -1985,7 +2061,7 @@ impl super::Adapter { }); if !unsupported_extensions.is_empty() { - log::warn!("Missing extensions: {unsupported_extensions:?}"); + log::debug!("Missing extensions: {unsupported_extensions:?}"); } log::debug!("Supported extensions: {supported_extensions:?}"); @@ -2355,87 +2431,20 @@ impl super::Adapter { signal_semaphores: Mutex::new(SemaphoreList::new(SemaphoreListMode::Signal)), }; - let mem_allocator = { - let limits = self.phd_capabilities.properties.limits; + let allocation_sizes = AllocationSizes::from_memory_hints(memory_hints).into(); - // Note: the parameters here are not set in stone nor where they picked with - // strong confidence. - // `final_free_list_chunk` should be bigger than starting_free_list_chunk if - // we want the behavior of starting with smaller block sizes and using larger - // ones only after we observe that the small ones aren't enough, which I think - // is a good "I don't know what the workload is going to be like" approach. - // - // For reference, `VMA`, and `gpu_allocator` both start with 256 MB blocks - // (then VMA doubles the block size each time it needs a new block). - // At some point it would be good to experiment with real workloads - // - // TODO(#5925): The plan is to switch the Vulkan backend from `gpu_alloc` to - // `gpu_allocator` which has a different (simpler) set of configuration options. - // - // TODO: These parameters should take hardware capabilities into account. - let mb = 1024 * 1024; - let perf_cfg = gpu_alloc::Config { - starting_free_list_chunk: 128 * mb, - final_free_list_chunk: 512 * mb, - minimal_buddy_size: 1, - initial_buddy_dedicated_size: 8 * mb, - dedicated_threshold: 32 * mb, - preferred_dedicated_threshold: mb, - transient_dedicated_threshold: 128 * mb, - }; - let mem_usage_cfg = gpu_alloc::Config { - starting_free_list_chunk: 8 * mb, - final_free_list_chunk: 64 * mb, - minimal_buddy_size: 1, - initial_buddy_dedicated_size: 8 * mb, - dedicated_threshold: 8 * mb, - preferred_dedicated_threshold: mb, - transient_dedicated_threshold: 16 * mb, - }; - let config = match memory_hints { - wgt::MemoryHints::Performance => perf_cfg, - wgt::MemoryHints::MemoryUsage => mem_usage_cfg, - wgt::MemoryHints::Manual { - suballocated_device_memory_block_size, - } => gpu_alloc::Config { - starting_free_list_chunk: suballocated_device_memory_block_size.start, - final_free_list_chunk: suballocated_device_memory_block_size.end, - initial_buddy_dedicated_size: suballocated_device_memory_block_size.start, - ..perf_cfg - }, - }; + let buffer_device_address = enabled_extensions.contains(&khr::buffer_device_address::NAME); + + let mem_allocator = + gpu_allocator::vulkan::Allocator::new(&gpu_allocator::vulkan::AllocatorCreateDesc { + instance: self.instance.raw.clone(), + device: shared.raw.clone(), + physical_device: self.raw, + debug_settings: Default::default(), + buffer_device_address, + allocation_sizes, + })?; - let max_memory_allocation_size = - if let Some(maintenance_3) = self.phd_capabilities.maintenance_3 { - maintenance_3.max_memory_allocation_size - } else { - u64::MAX - }; - let properties = gpu_alloc::DeviceProperties { - max_memory_allocation_count: limits.max_memory_allocation_count, - max_memory_allocation_size, - non_coherent_atom_size: limits.non_coherent_atom_size, - memory_types: memory_types - .iter() - .map(|memory_type| gpu_alloc::MemoryType { - props: gpu_alloc::MemoryPropertyFlags::from_bits_truncate( - memory_type.property_flags.as_raw() as u8, - ), - heap: memory_type.heap_index, - }) - .collect(), - memory_heaps: mem_properties - .memory_heaps_as_slice() - .iter() - .map(|&memory_heap| gpu_alloc::MemoryHeap { - size: memory_heap.size, - }) - .collect(), - buffer_device_address: enabled_extensions - .contains(&khr::buffer_device_address::NAME), - }; - gpu_alloc::GpuAllocator::new(config, properties) - }; let desc_allocator = gpu_descriptor::DescriptorAllocator::new( if let Some(di) = self.phd_capabilities.descriptor_indexing { di.max_update_after_bind_descriptors_in_all_pools @@ -2864,7 +2873,7 @@ fn is_intel_igpu_outdated_for_robustness2( .unwrap_or_default(); if is_outdated { - log::warn!( + log::debug!( "Disabling robustBufferAccess2 and robustImageAccess2: IntegratedGpu Intel Driver is outdated. Found with version 0x{:X}, less than the known good version 0x{:X} (31.0.101.2115)", props.driver_version, DRIVER_VERSION_WORKING diff --git a/third_party/rust/wgpu-hal/src/vulkan/command.rs b/third_party/rust/wgpu-hal/src/vulkan/command.rs @@ -813,10 +813,11 @@ impl crate::CommandEncoder for super::CommandEncoder { }); let color = super::ColorAttachmentKey { base: cat.target.make_attachment_key(cat.ops), - resolve: cat - .resolve_target - .as_ref() - .map(|target| target.make_attachment_key(crate::AttachmentOps::STORE)), + resolve: cat.resolve_target.as_ref().map(|target| { + target.make_attachment_key( + crate::AttachmentOps::LOAD_CLEAR | crate::AttachmentOps::STORE, + ) + }), }; rp_key.colors.push(Some(color)); @@ -939,10 +940,9 @@ impl crate::CommandEncoder for super::CommandEncoder { ) }; } - unsafe fn set_push_constants( + unsafe fn set_immediates( &mut self, layout: &super::PipelineLayout, - stages: wgt::ShaderStages, offset_bytes: u32, data: &[u32], ) { @@ -950,7 +950,7 @@ impl crate::CommandEncoder for super::CommandEncoder { self.device.raw.cmd_push_constants( self.active, layout.raw, - conv::map_shader_stage(stages), + vk::ShaderStageFlags::ALL, offset_bytes, bytemuck::cast_slice(data), ) @@ -1128,15 +1128,35 @@ impl crate::CommandEncoder for super::CommandEncoder { offset: wgt::BufferAddress, draw_count: u32, ) { - unsafe { - self.device.raw.cmd_draw_indirect( - self.active, - buffer.raw, - offset, - draw_count, - size_of::<wgt::DrawIndirectArgs>() as u32, - ) - }; + if draw_count >= 1 + && self.device.private_caps.multi_draw_indirect + && draw_count <= self.device.private_caps.max_draw_indirect_count + { + unsafe { + self.device.raw.cmd_draw_indirect( + self.active, + buffer.raw, + offset, + draw_count, + size_of::<wgt::DrawIndirectArgs>() as u32, + ) + }; + } else { + for i in 0..draw_count { + let indirect_offset = offset + + i as wgt::BufferAddress + * size_of::<wgt::DrawIndirectArgs>() as wgt::BufferAddress; + unsafe { + self.device.raw.cmd_draw_indirect( + self.active, + buffer.raw, + indirect_offset, + 1, + size_of::<wgt::DrawIndirectArgs>() as u32, + ) + }; + } + } } unsafe fn draw_indexed_indirect( &mut self, @@ -1144,7 +1164,10 @@ impl crate::CommandEncoder for super::CommandEncoder { offset: wgt::BufferAddress, draw_count: u32, ) { - if draw_count >= 1 && self.device.private_caps.multi_draw_indirect { + if draw_count >= 1 + && self.device.private_caps.multi_draw_indirect + && draw_count <= self.device.private_caps.max_draw_indirect_count + { unsafe { self.device.raw.cmd_draw_indexed_indirect( self.active, @@ -1155,12 +1178,15 @@ impl crate::CommandEncoder for super::CommandEncoder { ) }; } else { - for _ in 0..draw_count { + for i in 0..draw_count { + let indirect_offset = offset + + i as wgt::BufferAddress + * size_of::<wgt::DrawIndexedIndirectArgs>() as wgt::BufferAddress; unsafe { self.device.raw.cmd_draw_indexed_indirect( self.active, buffer.raw, - offset, + indirect_offset, 1, size_of::<wgt::DrawIndexedIndirectArgs>() as u32, ) diff --git a/third_party/rust/wgpu-hal/src/vulkan/conv.rs b/third_party/rust/wgpu-hal/src/vulkan/conv.rs @@ -452,13 +452,19 @@ pub fn map_attachment_ops( ) -> (vk::AttachmentLoadOp, vk::AttachmentStoreOp) { let load_op = if op.contains(crate::AttachmentOps::LOAD) { vk::AttachmentLoadOp::LOAD - } else { + } else if op.contains(crate::AttachmentOps::LOAD_DONT_CARE) { + vk::AttachmentLoadOp::DONT_CARE + } else if op.contains(crate::AttachmentOps::LOAD_CLEAR) { vk::AttachmentLoadOp::CLEAR + } else { + unreachable!() }; let store_op = if op.contains(crate::AttachmentOps::STORE) { vk::AttachmentStoreOp::STORE - } else { + } else if op.contains(crate::AttachmentOps::STORE_DISCARD) { vk::AttachmentStoreOp::DONT_CARE + } else { + unreachable!() }; (load_op, store_op) } diff --git a/third_party/rust/wgpu-hal/src/vulkan/device.rs b/third_party/rust/wgpu-hal/src/vulkan/device.rs @@ -235,105 +235,17 @@ impl super::DeviceShared { buffer: &'a super::Buffer, ranges: I, ) -> Option<impl 'a + Iterator<Item = vk::MappedMemoryRange<'a>>> { - let block = buffer.block.as_ref()?.lock(); + let allocation = buffer.allocation.as_ref()?.lock(); let mask = self.private_caps.non_coherent_map_mask; Some(ranges.map(move |range| { vk::MappedMemoryRange::default() - .memory(*block.memory()) - .offset((block.offset() + range.start) & !mask) + .memory(allocation.memory()) + .offset((allocation.offset() + range.start) & !mask) .size((range.end - range.start + mask) & !mask) })) } } -impl gpu_alloc::MemoryDevice<vk::DeviceMemory> for super::DeviceShared { - unsafe fn allocate_memory( - &self, - size: u64, - memory_type: u32, - flags: gpu_alloc::AllocationFlags, - ) -> Result<vk::DeviceMemory, gpu_alloc::OutOfMemory> { - let mut info = vk::MemoryAllocateInfo::default() - .allocation_size(size) - .memory_type_index(memory_type); - - let mut info_flags; - - if flags.contains(gpu_alloc::AllocationFlags::DEVICE_ADDRESS) { - info_flags = vk::MemoryAllocateFlagsInfo::default() - .flags(vk::MemoryAllocateFlags::DEVICE_ADDRESS); - info = info.push_next(&mut info_flags); - } - - match unsafe { self.raw.allocate_memory(&info, None) } { - Ok(memory) => { - self.memory_allocations_counter.add(1); - Ok(memory) - } - Err(vk::Result::ERROR_OUT_OF_DEVICE_MEMORY) => { - Err(gpu_alloc::OutOfMemory::OutOfDeviceMemory) - } - Err(vk::Result::ERROR_OUT_OF_HOST_MEMORY) => { - Err(gpu_alloc::OutOfMemory::OutOfHostMemory) - } - // We don't use VK_KHR_external_memory - // VK_ERROR_INVALID_EXTERNAL_HANDLE - // We don't use VK_KHR_buffer_device_address - // VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS_KHR - Err(err) => handle_unexpected(err), - } - } - - unsafe fn deallocate_memory(&self, memory: vk::DeviceMemory) { - self.memory_allocations_counter.sub(1); - - unsafe { self.raw.free_memory(memory, None) }; - } - - unsafe fn map_memory( - &self, - memory: &mut vk::DeviceMemory, - offset: u64, - size: u64, - ) -> Result<ptr::NonNull<u8>, gpu_alloc::DeviceMapError> { - match unsafe { - self.raw - .map_memory(*memory, offset, size, vk::MemoryMapFlags::empty()) - } { - Ok(ptr) => Ok(ptr::NonNull::new(ptr.cast::<u8>()) - .expect("Pointer to memory mapping must not be null")), - Err(vk::Result::ERROR_OUT_OF_DEVICE_MEMORY) => { - Err(gpu_alloc::DeviceMapError::OutOfDeviceMemory) - } - Err(vk::Result::ERROR_OUT_OF_HOST_MEMORY) => { - Err(gpu_alloc::DeviceMapError::OutOfHostMemory) - } - Err(vk::Result::ERROR_MEMORY_MAP_FAILED) => Err(gpu_alloc::DeviceMapError::MapFailed), - Err(err) => handle_unexpected(err), - } - } - - unsafe fn unmap_memory(&self, memory: &mut vk::DeviceMemory) { - unsafe { self.raw.unmap_memory(*memory) }; - } - - unsafe fn invalidate_memory_ranges( - &self, - _ranges: &[gpu_alloc::MappedMemoryRange<'_, vk::DeviceMemory>], - ) -> Result<(), gpu_alloc::OutOfMemory> { - // should never be called - unimplemented!() - } - - unsafe fn flush_memory_ranges( - &self, - _ranges: &[gpu_alloc::MappedMemoryRange<'_, vk::DeviceMemory>], - ) -> Result<(), gpu_alloc::OutOfMemory> { - // should never be called - unimplemented!() - } -} - impl gpu_descriptor::DescriptorDevice<vk::DescriptorSetLayout, vk::DescriptorPool, vk::DescriptorSet> for super::DeviceShared @@ -488,41 +400,27 @@ impl super::Device { /// - If `drop_callback` is [`None`], wgpu-hal will take ownership of `vk_image`. If /// `drop_callback` is [`Some`], `vk_image` must be valid until the callback is called. /// - If the `ImageCreateFlags` does not contain `MUTABLE_FORMAT`, the `view_formats` of `desc` must be empty. - /// - If `external_memory` is [`Some`], wgpu-hal will take ownership of the memory (which is presumed to back - /// `vk_image`). If `external_memory` is [`None`], the memory must be valid until `drop_callback` is called. + /// - If `memory` is not [`super::TextureMemory::External`], wgpu-hal will take ownership of the + /// memory (which is presumed to back `vk_image`). Otherwise, the memory must remain valid until + /// `drop_callback` is called. pub unsafe fn texture_from_raw( &self, vk_image: vk::Image, desc: &crate::TextureDescriptor, drop_callback: Option<crate::DropCallback>, - external_memory: Option<vk::DeviceMemory>, + memory: super::TextureMemory, ) -> super::Texture { - let mut raw_flags = vk::ImageCreateFlags::empty(); - let mut view_formats = vec![]; - for tf in desc.view_formats.iter() { - if *tf == desc.format { - continue; - } - view_formats.push(*tf); - } - if !view_formats.is_empty() { - raw_flags |= - vk::ImageCreateFlags::MUTABLE_FORMAT | vk::ImageCreateFlags::EXTENDED_USAGE; - view_formats.push(desc.format) - } - if desc.format.is_multi_planar_format() { - raw_flags |= vk::ImageCreateFlags::MUTABLE_FORMAT; - } - let identity = self.shared.texture_identity_factory.next(); - let drop_guard = crate::DropGuard::from_option(drop_callback); + if let Some(label) = desc.label { + unsafe { self.shared.set_object_name(vk_image, label) }; + } + super::Texture { raw: vk_image, drop_guard, - external_memory, - block: None, + memory, format: desc.format, copy_size: desc.copy_extent(), identity, @@ -634,7 +532,6 @@ impl super::Device { Ok(ImageWithoutMemory { raw, requirements: req, - copy_size, }) } @@ -695,22 +592,13 @@ impl super::Device { unsafe { self.shared.raw.bind_image_memory(image.raw, memory, 0) } .map_err(super::map_host_device_oom_err)?; - if let Some(label) = desc.label { - unsafe { self.shared.set_object_name(image.raw, label) }; - } - - let identity = self.shared.texture_identity_factory.next(); - - self.counters.textures.add(1); - - Ok(super::Texture { - raw: image.raw, - drop_guard: None, - external_memory: Some(memory), - block: None, - format: desc.format, - copy_size: image.copy_size, - identity, + Ok(unsafe { + self.texture_from_raw( + image.raw, + desc, + None, + super::TextureMemory::Dedicated(memory), + ) }) } @@ -990,59 +878,59 @@ impl crate::Device for super::Device { .create_buffer(&vk_info, None) .map_err(super::map_host_device_oom_and_ioca_err)? }; - let req = unsafe { self.shared.raw.get_buffer_memory_requirements(raw) }; - let mut alloc_usage = if desc - .usage - .intersects(wgt::BufferUses::MAP_READ | wgt::BufferUses::MAP_WRITE) - { - let mut flags = gpu_alloc::UsageFlags::HOST_ACCESS; - //TODO: find a way to use `crate::MemoryFlags::PREFER_COHERENT` - flags.set( - gpu_alloc::UsageFlags::DOWNLOAD, - desc.usage.contains(wgt::BufferUses::MAP_READ), - ); - flags.set( - gpu_alloc::UsageFlags::UPLOAD, - desc.usage.contains(wgt::BufferUses::MAP_WRITE), - ); - flags - } else { - gpu_alloc::UsageFlags::FAST_DEVICE_ACCESS + let mut requirements = unsafe { self.shared.raw.get_buffer_memory_requirements(raw) }; + + let is_cpu_read = desc.usage.contains(wgt::BufferUses::MAP_READ); + let is_cpu_write = desc.usage.contains(wgt::BufferUses::MAP_WRITE); + + let location = match (is_cpu_read, is_cpu_write) { + (true, true) => gpu_allocator::MemoryLocation::CpuToGpu, + (true, false) => gpu_allocator::MemoryLocation::GpuToCpu, + (false, true) => gpu_allocator::MemoryLocation::CpuToGpu, + (false, false) => gpu_allocator::MemoryLocation::GpuOnly, }; - alloc_usage.set( - gpu_alloc::UsageFlags::TRANSIENT, - desc.memory_flags.contains(crate::MemoryFlags::TRANSIENT), - ); - let needs_host_access = alloc_usage.contains(gpu_alloc::UsageFlags::HOST_ACCESS); + let needs_host_access = is_cpu_read || is_cpu_write; - self.error_if_would_oom_on_resource_allocation(needs_host_access, req.size) + self.error_if_would_oom_on_resource_allocation(needs_host_access, requirements.size) .inspect_err(|_| { unsafe { self.shared.raw.destroy_buffer(raw, None) }; })?; - let alignment_mask = req.alignment - 1; + let name = desc.label.unwrap_or("Unlabeled buffer"); - let block = unsafe { - self.mem_allocator.lock().alloc( - &*self.shared, - gpu_alloc::Request { - size: req.size, - align_mask: alignment_mask, - usage: alloc_usage, - memory_types: req.memory_type_bits & self.valid_ash_memory_types, + if desc + .usage + .contains(wgt::BufferUses::ACCELERATION_STRUCTURE_SCRATCH) + { + // There is no way to specify this usage to Vulkan so we must make sure the alignment requirement is large enough. + requirements.alignment = requirements + .alignment + .max(self.shared.private_caps.scratch_buffer_alignment as u64); + } + + let allocation = self + .mem_allocator + .lock() + .allocate(&gpu_allocator::vulkan::AllocationCreateDesc { + name, + requirements: vk::MemoryRequirements { + memory_type_bits: requirements.memory_type_bits & self.valid_ash_memory_types, + ..requirements }, - ) - } - .inspect_err(|_| { - unsafe { self.shared.raw.destroy_buffer(raw, None) }; - })?; + location, + linear: true, // Buffers are always linear + allocation_scheme: gpu_allocator::vulkan::AllocationScheme::GpuAllocatorManaged, + }) + .inspect_err(|_| { + unsafe { self.shared.raw.destroy_buffer(raw, None) }; + })?; unsafe { self.shared .raw - .bind_buffer_memory(raw, *block.memory(), block.offset()) + .bind_buffer_memory(raw, allocation.memory(), allocation.offset()) } .map_err(super::map_host_device_oom_and_ioca_err) .inspect_err(|_| { @@ -1053,23 +941,26 @@ impl crate::Device for super::Device { unsafe { self.shared.set_object_name(raw, label) }; } - self.counters.buffer_memory.add(block.size() as isize); + self.counters.buffer_memory.add(allocation.size() as isize); self.counters.buffers.add(1); Ok(super::Buffer { raw, - block: Some(Mutex::new(super::BufferMemoryBacking::Managed(block))), + allocation: Some(Mutex::new(super::BufferMemoryBacking::Managed(allocation))), }) } unsafe fn destroy_buffer(&self, buffer: super::Buffer) { unsafe { self.shared.raw.destroy_buffer(buffer.raw, None) }; - if let Some(block) = buffer.block { - let block = block.into_inner(); - self.counters.buffer_memory.sub(block.size() as isize); - match block { - super::BufferMemoryBacking::Managed(block) => unsafe { - self.mem_allocator.lock().dealloc(&*self.shared, block) - }, + if let Some(allocation) = buffer.allocation { + let allocation = allocation.into_inner(); + self.counters.buffer_memory.sub(allocation.size() as isize); + match allocation { + super::BufferMemoryBacking::Managed(allocation) => { + let result = self.mem_allocator.lock().free(allocation); + if let Err(err) = result { + log::warn!("Failed to free buffer allocation: {err}"); + } + } super::BufferMemoryBacking::VulkanMemory { memory, .. } => unsafe { self.shared.raw.free_memory(memory, None); }, @@ -1088,15 +979,22 @@ impl crate::Device for super::Device { buffer: &super::Buffer, range: crate::MemoryRange, ) -> Result<crate::BufferMapping, crate::DeviceError> { - if let Some(ref block) = buffer.block { - let size = range.end - range.start; - let mut block = block.lock(); - if let super::BufferMemoryBacking::Managed(ref mut block) = *block { - let ptr = unsafe { block.map(&*self.shared, range.start, size as usize)? }; - let is_coherent = block - .props() - .contains(gpu_alloc::MemoryPropertyFlags::HOST_COHERENT); - Ok(crate::BufferMapping { ptr, is_coherent }) + if let Some(ref allocation) = buffer.allocation { + let mut allocation = allocation.lock(); + if let super::BufferMemoryBacking::Managed(ref mut allocation) = *allocation { + let is_coherent = allocation + .memory_properties() + .contains(vk::MemoryPropertyFlags::HOST_COHERENT); + Ok(crate::BufferMapping { + ptr: unsafe { + allocation + .mapped_ptr() + .unwrap() + .cast() + .offset(range.start as isize) + }, + is_coherent, + }) } else { crate::hal_usage_error("tried to map externally created buffer") } @@ -1104,14 +1002,10 @@ impl crate::Device for super::Device { crate::hal_usage_error("tried to map external buffer") } } + unsafe fn unmap_buffer(&self, buffer: &super::Buffer) { - if let Some(ref block) = buffer.block { - match &mut *block.lock() { - super::BufferMemoryBacking::Managed(block) => unsafe { block.unmap(&*self.shared) }, - super::BufferMemoryBacking::VulkanMemory { .. } => { - crate::hal_usage_error("tried to unmap externally created buffer") - } - }; + if buffer.allocation.is_some() { + // gpu-allocator maps the buffer when allocated and unmap it when free'd } else { crate::hal_usage_error("tried to unmap external buffer") } @@ -1159,62 +1053,65 @@ impl crate::Device for super::Device { unsafe { self.shared.raw.destroy_image(image.raw, None) }; })?; - let block = unsafe { - self.mem_allocator.lock().alloc( - &*self.shared, - gpu_alloc::Request { - size: image.requirements.size, - align_mask: image.requirements.alignment - 1, - usage: gpu_alloc::UsageFlags::FAST_DEVICE_ACCESS, - memory_types: image.requirements.memory_type_bits & self.valid_ash_memory_types, + let name = desc.label.unwrap_or("Unlabeled texture"); + + let allocation = self + .mem_allocator + .lock() + .allocate(&gpu_allocator::vulkan::AllocationCreateDesc { + name, + requirements: vk::MemoryRequirements { + memory_type_bits: image.requirements.memory_type_bits + & self.valid_ash_memory_types, + ..image.requirements }, - ) - } - .inspect_err(|_| { - unsafe { self.shared.raw.destroy_image(image.raw, None) }; - })?; + location: gpu_allocator::MemoryLocation::GpuOnly, + linear: false, + allocation_scheme: gpu_allocator::vulkan::AllocationScheme::GpuAllocatorManaged, + }) + .inspect_err(|_| { + unsafe { self.shared.raw.destroy_image(image.raw, None) }; + })?; - self.counters.texture_memory.add(block.size() as isize); + self.counters.texture_memory.add(allocation.size() as isize); unsafe { self.shared .raw - .bind_image_memory(image.raw, *block.memory(), block.offset()) + .bind_image_memory(image.raw, allocation.memory(), allocation.offset()) } .map_err(super::map_host_device_oom_err) .inspect_err(|_| { unsafe { self.shared.raw.destroy_image(image.raw, None) }; })?; - if let Some(label) = desc.label { - unsafe { self.shared.set_object_name(image.raw, label) }; - } - - let identity = self.shared.texture_identity_factory.next(); - - self.counters.textures.add(1); - - Ok(super::Texture { - raw: image.raw, - drop_guard: None, - external_memory: None, - block: Some(block), - format: desc.format, - copy_size: image.copy_size, - identity, + Ok(unsafe { + self.texture_from_raw( + image.raw, + desc, + None, + super::TextureMemory::Allocation(allocation), + ) }) } + unsafe fn destroy_texture(&self, texture: super::Texture) { if texture.drop_guard.is_none() { unsafe { self.shared.raw.destroy_image(texture.raw, None) }; } - if let Some(memory) = texture.external_memory { - unsafe { self.shared.raw.free_memory(memory, None) }; - } - if let Some(block) = texture.block { - self.counters.texture_memory.sub(block.size() as isize); - unsafe { self.mem_allocator.lock().dealloc(&*self.shared, block) }; + match texture.memory { + super::TextureMemory::Allocation(allocation) => { + self.counters.texture_memory.sub(allocation.size() as isize); + let result = self.mem_allocator.lock().free(allocation); + if let Err(err) = result { + log::warn!("Failed to free texture allocation: {err}"); + } + } + super::TextureMemory::Dedicated(memory) => unsafe { + self.shared.raw.free_memory(memory, None); + }, + super::TextureMemory::External => {} } self.counters.textures.sub(1); @@ -1517,20 +1414,20 @@ impl crate::Device for super::Device { .iter() .map(|bgl| bgl.raw) .collect::<Vec<_>>(); - let vk_push_constant_ranges = desc - .push_constant_ranges - .iter() - .map(|pcr| vk::PushConstantRange { - stage_flags: conv::map_shader_stage(pcr.stages), - offset: pcr.range.start, - size: pcr.range.end - pcr.range.start, + let vk_immediates_ranges: Option<vk::PushConstantRange> = if desc.immediate_size != 0 { + Some(vk::PushConstantRange { + stage_flags: vk::ShaderStageFlags::ALL, + offset: 0, + size: desc.immediate_size, }) - .collect::<Vec<_>>(); + } else { + None + }; let vk_info = vk::PipelineLayoutCreateInfo::default() .flags(vk::PipelineLayoutCreateFlags::empty()) .set_layouts(&vk_set_layouts) - .push_constant_ranges(&vk_push_constant_ranges); + .push_constant_ranges(vk_immediates_ranges.as_slice()); let raw = { profiling::scope!("vkCreatePipelineLayout"); @@ -2582,32 +2479,35 @@ impl crate::Device for super::Device { .raw .create_buffer(&vk_buffer_info, None) .map_err(super::map_host_device_oom_and_ioca_err)?; - let req = self.shared.raw.get_buffer_memory_requirements(raw_buffer); - self.error_if_would_oom_on_resource_allocation(false, req.size) + let requirements = self.shared.raw.get_buffer_memory_requirements(raw_buffer); + + self.error_if_would_oom_on_resource_allocation(false, requirements.size) .inspect_err(|_| { self.shared.raw.destroy_buffer(raw_buffer, None); })?; - let block = self + let name = desc + .label + .unwrap_or("Unlabeled acceleration structure buffer"); + + let allocation = self .mem_allocator .lock() - .alloc( - &*self.shared, - gpu_alloc::Request { - size: req.size, - align_mask: req.alignment - 1, - usage: gpu_alloc::UsageFlags::FAST_DEVICE_ACCESS, - memory_types: req.memory_type_bits & self.valid_ash_memory_types, - }, - ) + .allocate(&gpu_allocator::vulkan::AllocationCreateDesc { + name, + requirements, + location: gpu_allocator::MemoryLocation::GpuOnly, + linear: true, // Buffers are always linear + allocation_scheme: gpu_allocator::vulkan::AllocationScheme::GpuAllocatorManaged, + }) .inspect_err(|_| { self.shared.raw.destroy_buffer(raw_buffer, None); })?; self.shared .raw - .bind_buffer_memory(raw_buffer, *block.memory(), block.offset()) + .bind_buffer_memory(raw_buffer, allocation.memory(), allocation.offset()) .map_err(super::map_host_device_oom_and_ioca_err) .inspect_err(|_| { self.shared.raw.destroy_buffer(raw_buffer, None); @@ -2660,7 +2560,7 @@ impl crate::Device for super::Device { Ok(super::AccelerationStructure { raw: raw_acceleration_structure, buffer: raw_buffer, - block: Mutex::new(block), + allocation, compacted_size_query: pool, }) } @@ -2684,9 +2584,13 @@ impl crate::Device for super::Device { self.shared .raw .destroy_buffer(acceleration_structure.buffer, None); - self.mem_allocator + let result = self + .mem_allocator .lock() - .dealloc(&*self.shared, acceleration_structure.block.into_inner()); + .free(acceleration_structure.allocation); + if let Err(err) = result { + log::warn!("Failed to free buffer acceleration structure: {err}"); + } if let Some(query) = acceleration_structure.compacted_size_query { self.shared.raw.destroy_query_pool(query, None) } @@ -2701,6 +2605,39 @@ impl crate::Device for super::Device { self.counters.as_ref().clone() } + fn generate_allocator_report(&self) -> Option<wgt::AllocatorReport> { + let gpu_allocator::AllocatorReport { + allocations, + blocks, + total_allocated_bytes, + total_capacity_bytes, + } = self.mem_allocator.lock().generate_report(); + + let allocations = allocations + .into_iter() + .map(|alloc| wgt::AllocationReport { + name: alloc.name, + offset: alloc.offset, + size: alloc.size, + }) + .collect(); + + let blocks = blocks + .into_iter() + .map(|block| wgt::MemoryBlockReport { + size: block.size, + allocations: block.allocations.clone(), + }) + .collect(); + + Some(wgt::AllocatorReport { + allocations, + blocks, + total_allocated_bytes, + total_reserved_bytes: total_capacity_bytes, + }) + } + fn tlas_instance_to_bytes(&self, instance: TlasInstance) -> Vec<u8> { const MAX_U24: u32 = (1u32 << 24u32) - 1u32; let temp = RawTlasInstance { @@ -2839,24 +2776,6 @@ impl super::DeviceShared { } } -impl From<gpu_alloc::AllocationError> for crate::DeviceError { - fn from(error: gpu_alloc::AllocationError) -> Self { - use gpu_alloc::AllocationError as Ae; - match error { - Ae::OutOfDeviceMemory | Ae::OutOfHostMemory | Ae::TooManyObjects => Self::OutOfMemory, - Ae::NoCompatibleMemoryTypes => crate::hal_usage_error(error), - } - } -} -impl From<gpu_alloc::MapError> for crate::DeviceError { - fn from(error: gpu_alloc::MapError) -> Self { - use gpu_alloc::MapError as Me; - match error { - Me::OutOfDeviceMemory | Me::OutOfHostMemory | Me::MapFailed => Self::OutOfMemory, - Me::NonHostVisible | Me::AlreadyMapped => crate::hal_usage_error(error), - } - } -} impl From<gpu_descriptor::AllocationError> for crate::DeviceError { fn from(error: gpu_descriptor::AllocationError) -> Self { use gpu_descriptor::AllocationError as Ae; @@ -2879,5 +2798,4 @@ fn handle_unexpected(err: vk::Result) -> ! { struct ImageWithoutMemory { raw: vk::Image, requirements: vk::MemoryRequirements, - copy_size: crate::CopyExtent, } diff --git a/third_party/rust/wgpu-hal/src/vulkan/instance.rs b/third_party/rust/wgpu-hal/src/vulkan/instance.rs @@ -81,8 +81,10 @@ unsafe extern "system" fn debug_utils_messenger_callback( } let level = match message_severity { - vk::DebugUtilsMessageSeverityFlagsEXT::VERBOSE => log::Level::Debug, - vk::DebugUtilsMessageSeverityFlagsEXT::INFO => log::Level::Info, + // We intentionally suppress info messages down to debug + // so that users are not innundated with info messages from the runtime. + vk::DebugUtilsMessageSeverityFlagsEXT::VERBOSE => log::Level::Trace, + vk::DebugUtilsMessageSeverityFlagsEXT::INFO => log::Level::Debug, vk::DebugUtilsMessageSeverityFlagsEXT::WARNING => log::Level::Warn, vk::DebugUtilsMessageSeverityFlagsEXT::ERROR => log::Level::Error, _ => log::Level::Warn, @@ -271,8 +273,7 @@ impl super::Instance { extensions.push(ext::acquire_drm_display::NAME); extensions.push(ext::direct_mode_display::NAME); extensions.push(khr::display::NAME); - // VK_EXT_physical_device_drm -> VK_KHR_get_physical_device_properties2 - extensions.push(ext::physical_device_drm::NAME); + extensions.push(khr::get_physical_device_properties2::NAME); extensions.push(khr::get_display_properties2::NAME); } @@ -298,7 +299,7 @@ impl super::Instance { { true } else { - log::warn!("Unable to find extension: {}", ext.to_string_lossy()); + log::debug!("Unable to find extension: {}", ext.to_string_lossy()); false } }); @@ -334,7 +335,7 @@ impl super::Instance { let debug_utils = if let Some(debug_utils_create_info) = debug_utils_create_info { if extensions.contains(&ext::debug_utils::NAME) { - log::info!("Enabling debug utils"); + log::debug!("Enabling debug utils"); let extension = ext::debug_utils::Instance::new(&entry, &raw_instance); let vk_info = debug_utils_create_info.to_vk_create_info(); @@ -708,7 +709,7 @@ impl super::Instance { }); } } else { - log::warn!( + log::debug!( "InstanceFlags::VALIDATION requested, but unable to find layer: {}", validation_layer_name.to_string_lossy() ); @@ -965,7 +966,7 @@ impl crate::Instance for super::Instance { }) { if version < (21, 2) { // See https://gitlab.freedesktop.org/mesa/mesa/-/issues/4688 - log::warn!( + log::debug!( concat!( "Disabling presentation on '{}' (id {:?}) ", "due to NV Optimus and Intel Mesa < v21.2" diff --git a/third_party/rust/wgpu-hal/src/vulkan/mod.rs b/third_party/rust/wgpu-hal/src/vulkan/mod.rs @@ -319,6 +319,7 @@ struct PrivateCapabilities { can_present: bool, non_coherent_map_mask: wgt::BufferAddress, multi_draw_indirect: bool, + max_draw_indirect_count: u32, /// True if this adapter advertises the [`robustBufferAccess`][vrba] feature. /// @@ -387,6 +388,12 @@ struct PrivateCapabilities { /// if you are drawing more than 128 million instances. We still want to avoid /// undefined behavior in this situation, so we panic if the limit is violated. multiview_instance_index_limit: u32, + + /// BufferUsages::ACCELERATION_STRUCTURE_SCRATCH allows usage as a scratch buffer. + /// Vulkan has no way to specify this as a usage, and it maps to other usages, but + /// these usages do not have as high of an alignment requirement using the buffer as + /// a scratch buffer when building acceleration structures. + scratch_buffer_alignment: u32, } bitflags::bitflags!( @@ -505,8 +512,7 @@ impl Drop for DeviceShared { } pub struct Device { - shared: Arc<DeviceShared>, - mem_allocator: Mutex<gpu_alloc::GpuAllocator<vk::DeviceMemory>>, + mem_allocator: Mutex<gpu_allocator::vulkan::Allocator>, desc_allocator: Mutex<gpu_descriptor::DescriptorAllocator<vk::DescriptorPool, vk::DescriptorSet>>, valid_ash_memory_types: u32, @@ -514,11 +520,13 @@ pub struct Device { #[cfg(feature = "renderdoc")] render_doc: crate::auxil::renderdoc::RenderDoc, counters: Arc<wgt::HalCounters>, + // Struct members are dropped from first to last, put the Device last to ensure that + // all resources that depends on it are destroyed before it like the mem_allocator + shared: Arc<DeviceShared>, } impl Drop for Device { fn drop(&mut self) { - unsafe { self.mem_allocator.lock().cleanup(&*self.shared) }; unsafe { self.desc_allocator.lock().cleanup(&*self.shared) }; } } @@ -619,7 +627,7 @@ impl Drop for Queue { } #[derive(Debug)] enum BufferMemoryBacking { - Managed(gpu_alloc::MemoryBlock<vk::DeviceMemory>), + Managed(gpu_allocator::vulkan::Allocation), VulkanMemory { memory: vk::DeviceMemory, offset: u64, @@ -627,10 +635,10 @@ enum BufferMemoryBacking { }, } impl BufferMemoryBacking { - fn memory(&self) -> &vk::DeviceMemory { + fn memory(&self) -> vk::DeviceMemory { match self { - Self::Managed(m) => m.memory(), - Self::VulkanMemory { memory, .. } => memory, + Self::Managed(m) => unsafe { m.memory() }, + Self::VulkanMemory { memory, .. } => *memory, } } fn offset(&self) -> u64 { @@ -649,7 +657,7 @@ impl BufferMemoryBacking { #[derive(Debug)] pub struct Buffer { raw: vk::Buffer, - block: Option<Mutex<BufferMemoryBacking>>, + allocation: Option<Mutex<BufferMemoryBacking>>, } impl Buffer { /// # Safety @@ -659,7 +667,7 @@ impl Buffer { pub unsafe fn from_raw(vk_buffer: vk::Buffer) -> Self { Self { raw: vk_buffer, - block: None, + allocation: None, } } /// # Safety @@ -674,7 +682,7 @@ impl Buffer { ) -> Self { Self { raw: vk_buffer, - block: Some(Mutex::new(BufferMemoryBacking::VulkanMemory { + allocation: Some(Mutex::new(BufferMemoryBacking::VulkanMemory { memory, offset, size, @@ -689,17 +697,28 @@ impl crate::DynBuffer for Buffer {} pub struct AccelerationStructure { raw: vk::AccelerationStructureKHR, buffer: vk::Buffer, - block: Mutex<gpu_alloc::MemoryBlock<vk::DeviceMemory>>, + allocation: gpu_allocator::vulkan::Allocation, compacted_size_query: Option<vk::QueryPool>, } impl crate::DynAccelerationStructure for AccelerationStructure {} #[derive(Debug)] +pub enum TextureMemory { + // shared memory in GPU allocator (owned by wgpu-hal) + Allocation(gpu_allocator::vulkan::Allocation), + + // dedicated memory (owned by wgpu-hal) + Dedicated(vk::DeviceMemory), + + // memory not owned by wgpu + External, +} + +#[derive(Debug)] pub struct Texture { raw: vk::Image, - external_memory: Option<vk::DeviceMemory>, - block: Option<gpu_alloc::MemoryBlock<vk::DeviceMemory>>, + memory: TextureMemory, format: wgt::TextureFormat, copy_size: crate::CopyExtent, identity: ResourceIdentity<vk::Image>, @@ -721,9 +740,10 @@ impl Texture { /// # Safety /// - /// - The external memory must not be manually freed - pub unsafe fn external_memory(&self) -> Option<vk::DeviceMemory> { - self.external_memory + /// - The caller must not free the `vk::DeviceMemory` or + /// `gpu_alloc::MemoryBlock` in the returned `TextureMemory`. + pub unsafe fn memory(&self) -> &TextureMemory { + &self.memory } } diff --git a/third_party/rust/wgpu-hal/src/vulkan/swapchain/native.rs b/third_party/rust/wgpu-hal/src/vulkan/swapchain/native.rs @@ -498,8 +498,7 @@ impl Swapchain for NativeSwapchain { texture: crate::vulkan::Texture { raw: self.images[index as usize], drop_guard: None, - block: None, - external_memory: None, + memory: crate::vulkan::TextureMemory::External, format: self.config.format, copy_size: crate::CopyExtent { width: self.config.extent.width, @@ -595,7 +594,7 @@ impl Swapchain for NativeSwapchain { // (i.e `VkSwapchainCreateInfoKHR::preTransform` not being equal to the current device orientation). // This is always the case when the device orientation is anything other than the identity one, as we unconditionally use `VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR`. #[cfg(not(target_os = "android"))] - log::warn!("Suboptimal present of frame {}", texture.index); + log::debug!("Suboptimal present of frame {}", texture.index); } Ok(()) } diff --git a/third_party/rust/wgpu-types/.cargo-checksum.json b/third_party/rust/wgpu-types/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"7294fed45f6bf1bfbb8a32daf982744671a76cd966a67a610c95c06ac135547e","LICENSE.APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE.MIT":"dc0d97139e8205818c703741c7be7cb3b96888bd5917b8d6fc6133731e403c21","src/assertions.rs":"e4d2d40bc1e870a59637f4b9574743e19565a62f6dbcc21cb18a76b666b796eb","src/cast_utils.rs":"33f03a57ccbedef2699f2305bec584c623db1fd28bfdf584d1260da4fbecd529","src/counters.rs":"1cae2f153a7e4e4e8f3d4cc8ead3635a9a7486d676729f0d7cbba3a6e91ce448","src/env.rs":"26ffc91867625784159bcf391881187aa92cf92b81b1f40959ce1b96ae6d554d","src/error.rs":"0109e6209cf152abbfd0cee85dd934fb24f2304bf6adad6fb684b77f151fb158","src/features.rs":"44591ecd078fd63fea93f1946472a5ab5d50ca0a54b6804afa50afbc0da5634a","src/instance.rs":"deeb5ca694163baf46c386316a498cee011cf960d48b749d4cd01125e9fca57f","src/lib.rs":"c518ebb506b820b8fb771f619b06fbc99bf3672b2c34a2355b726ffc97d03712","src/math.rs":"916d78724b50ed6e9f84240e0aa19b0afc886e21a1b18428ca6ace5a1e6cc80c","src/tokens.rs":"cdf192e0c9b4ea4f3cd4148d07be2e895f937f8154acbf52caf67f7fb4df11dc","src/transfers.rs":"25f47e9cbc5887f849f5eb4d8952d89de6377df40f480ebbea61c58d2e0e7fc6"},"package":null} -\ No newline at end of file +{"files":{"Cargo.toml":"88f5c2cf560dd3f41042435d751c86123a6499b0b473ada111f39c0b3d58fd58","LICENSE.APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE.MIT":"dc0d97139e8205818c703741c7be7cb3b96888bd5917b8d6fc6133731e403c21","src/adapter.rs":"a4090392d78c62a12e4f070385190ca95781ff4c5ef91a694a372d07e639d5fd","src/assertions.rs":"e4d2d40bc1e870a59637f4b9574743e19565a62f6dbcc21cb18a76b666b796eb","src/backend.rs":"cca0b725680cdc601a2735f64da0371b77b4ff741d76c56401866816a07f47e9","src/binding.rs":"907bab2a89a818ff5da925bff3eb07c6aad82b97bc71b8812cf0c43593626ec2","src/buffer.rs":"bc1d2976e3d54b81686f4597db7155d40d806a2b97d0eeb291bbaabf4b1eaefb","src/cast_utils.rs":"33f03a57ccbedef2699f2305bec584c623db1fd28bfdf584d1260da4fbecd529","src/counters.rs":"e85dc9fa3b52896b35a051de78b2e8d351df5bbf56923aece2d4756c8490fdd0","src/device.rs":"5c82d48cd0482ef53fd0fd8c3c29f80b9054d02f4f0a805c66f79e520647cb54","src/env.rs":"26ffc91867625784159bcf391881187aa92cf92b81b1f40959ce1b96ae6d554d","src/error.rs":"0109e6209cf152abbfd0cee85dd934fb24f2304bf6adad6fb684b77f151fb158","src/features.rs":"55e1a0e9a51df6cd29aa62fd4e44dcf65467eeff408e6003be884613cfccbf73","src/instance.rs":"81e4819d9c266eb2a4965c06ed74f5defe5e7bb8b438c54fd0376adef623f075","src/lib.rs":"68a435e552a2f4605150855ee1b2f3dbfabec8514803262c162fa6eefce66a65","src/limits.rs":"22e40b273afb9d60a9e1668e40e2f38929bcb9d70fdd983e77b2df4eb4df2f8b","src/math.rs":"916d78724b50ed6e9f84240e0aa19b0afc886e21a1b18428ca6ace5a1e6cc80c","src/origin_extent.rs":"31a6956c59c86046aac284bcd6b2d65462554db4ee6552b8fa0f660838113f97","src/ray_tracing.rs":"349245d907020947a40b99fc8fe78b2d0a34ddce6190415284aec2c37d6ba4e2","src/render.rs":"206cc471da7650e72a0d8e2d5b856e48c162f76acd422f9a735e32d3b5125352","src/send_sync.rs":"8d1db3a9b78eecab2e082751aea3b249dbd1a22a6274fe3cebc5384280dd474e","src/shader.rs":"1ccb7e213c77126964bbe5fd927d13146034ea8a1e5731dab136baac28cbe9ee","src/surface.rs":"a073c28347eb2756b7268ae44bcac0562d90614eba24a286f1e7b0632a75fcc0","src/texture.rs":"ed85930050d64b0fd9b0629d12f7797cea9583ddf74ef844f9e14b0de866b9bb","src/texture/external_image.rs":"2bb94e9f8c2f7d9df92b320c2dcc628e4a681204b32a8fbcf4da7f0ee32da9da","src/texture/external_texture.rs":"c4be6cd9f352141f72ae0dfbc4a2baa0995bbe968c9973c7067a2a879542eb3a","src/texture/format.rs":"771ae2bcac55ea8336ae86fed5f1eff9b7911f1b6587ef68d6d38972ce4ac625","src/tokens.rs":"db51d2ea67b9ba65347197db69b87202bfc6863104a44b10abb246139dcccb8c","src/transfers.rs":"25f47e9cbc5887f849f5eb4d8952d89de6377df40f480ebbea61c58d2e0e7fc6","src/vertex.rs":"3c66086cb02edbaf7a36b0b62b6e022e8f81333db20d80fa892ce2fc84da9814"},"package":null} +\ No newline at end of file diff --git a/third_party/rust/wgpu-types/Cargo.toml b/third_party/rust/wgpu-types/Cargo.toml @@ -13,7 +13,7 @@ edition = "2021" rust-version = "1.82.0" name = "wgpu-types" -version = "27.0.0" +version = "28.0.0" authors = ["gfx-rs developers"] build = false autolib = false diff --git a/third_party/rust/wgpu-types/src/adapter.rs b/third_party/rust/wgpu-types/src/adapter.rs @@ -0,0 +1,245 @@ +use alloc::string::String; +use core::{fmt, mem}; + +use crate::{link_to_wgpu_docs, Backend, Backends}; + +#[cfg(any(feature = "serde", test))] +use serde::{Deserialize, Serialize}; + +#[cfg(doc)] +use crate::{Features, TextureUsages}; + +/// Options for requesting adapter. +/// +/// Corresponds to [WebGPU `GPURequestAdapterOptions`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gpurequestadapteroptions). +#[repr(C)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct RequestAdapterOptions<S> { + /// Power preference for the adapter. + pub power_preference: PowerPreference, + /// Indicates that only a fallback adapter can be returned. This is generally a "software" + /// implementation on the system. + pub force_fallback_adapter: bool, + /// Surface that is required to be presentable with the requested adapter. This does not + /// create the surface, only guarantees that the adapter can present to said surface. + /// For WebGL, this is strictly required, as an adapter can not be created without a surface. + pub compatible_surface: Option<S>, +} + +impl<S> Default for RequestAdapterOptions<S> { + fn default() -> Self { + Self { + power_preference: PowerPreference::default(), + force_fallback_adapter: false, + compatible_surface: None, + } + } +} + +/// Power Preference when choosing a physical adapter. +/// +/// Corresponds to [WebGPU `GPUPowerPreference`]( +/// https://gpuweb.github.io/gpuweb/#enumdef-gpupowerpreference). +#[repr(C)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] +pub enum PowerPreference { + #[default] + /// Power usage is not considered when choosing an adapter. + None = 0, + /// Adapter that uses the least possible power. This is often an integrated GPU. + LowPower = 1, + /// Adapter that has the highest performance. This is often a discrete GPU. + HighPerformance = 2, +} + +impl PowerPreference { + /// Get a power preference from the environment variable `WGPU_POWER_PREF`. + pub fn from_env() -> Option<Self> { + let env = crate::env::var("WGPU_POWER_PREF")?; + match env.to_lowercase().as_str() { + "low" => Some(Self::LowPower), + "high" => Some(Self::HighPerformance), + "none" => Some(Self::None), + _ => None, + } + } +} + +/// Supported physical device types. +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum DeviceType { + /// Other or Unknown. + Other, + /// Integrated GPU with shared CPU/GPU memory. + IntegratedGpu, + /// Discrete GPU with separate CPU/GPU memory. + DiscreteGpu, + /// Virtual / Hosted. + VirtualGpu, + /// Cpu / Software Rendering. + Cpu, +} + +//TODO: convert `vendor` and `device` to `u32` + +/// Information about an adapter. +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct AdapterInfo { + /// Adapter name + pub name: String, + /// [`Backend`]-specific vendor ID of the adapter + /// + /// This generally is a 16-bit PCI vendor ID in the least significant bytes of this field. + /// However, more significant bytes may be non-zero if the backend uses a different + /// representation. + /// + /// * For [`Backend::Vulkan`], the [`VkPhysicalDeviceProperties::vendorID`] is used, which is + /// a superset of PCI IDs. + /// + /// [`VkPhysicalDeviceProperties::vendorID`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkPhysicalDeviceProperties.html + pub vendor: u32, + /// [`Backend`]-specific device ID of the adapter + /// + /// + /// This generally is a 16-bit PCI device ID in the least significant bytes of this field. + /// However, more significant bytes may be non-zero if the backend uses a different + /// representation. + /// + /// * For [`Backend::Vulkan`], the [`VkPhysicalDeviceProperties::deviceID`] is used, which is + /// a superset of PCI IDs. + /// + /// [`VkPhysicalDeviceProperties::deviceID`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkPhysicalDeviceProperties.html + pub device: u32, + /// Type of device + pub device_type: DeviceType, + /// [`Backend`]-specific PCI bus ID of the adapter. + /// + /// * For [`Backend::Vulkan`], [`VkPhysicalDevicePCIBusInfoPropertiesEXT`] is used, + /// if available, in the form `bus:device.function`, e.g. `0000:01:00.0`. + /// + /// [`VkPhysicalDevicePCIBusInfoPropertiesEXT`]: https://registry.khronos.org/vulkan/specs/latest/man/html/VkPhysicalDevicePCIBusInfoPropertiesEXT.html + pub device_pci_bus_id: String, + /// Driver name + pub driver: String, + /// Driver info + pub driver_info: String, + /// Backend used for device + pub backend: Backend, + /// Minimum possible size of a subgroup on this adapter. Will + /// never be lower than [`crate::MINIMUM_SUBGROUP_MIN_SIZE`]. + /// + /// This will vary from device to device. Typical values are listed below. + /// + /// - NVIDIA: 32 + /// - AMD GCN/Vega: 64 + /// - AMD RDNA+: 32 + /// - Intel: 8 or 16 + /// - Qualcomm: 64 + /// - WARP: 4 + /// - lavapipe: 8 + pub subgroup_min_size: u32, + /// Maximum possible size of a subgroup on this adapter. Will + /// never be higher than [`crate::MAXIMUM_SUBGROUP_MAX_SIZE`]. + /// + /// This will vary from device to device. Typical values are listed below: + /// + /// - NVIDIA: 32 + /// - AMD GCN/Vega: 64 + /// - AMD RDNA+: 64 + /// - Intel: 16 or 32 + /// - Qualcomm: 128 + /// - WARP: 4 or 128 + /// - lavapipe: 8 + pub subgroup_max_size: u32, + /// If true, adding [`TextureUsages::TRANSIENT`] to a texture will decrease memory usage. + pub transient_saves_memory: bool, +} + +/// Error when [`Instance::request_adapter()`] fails. +/// +/// This type is not part of the WebGPU standard, where `requestAdapter()` would simply return null. +/// +#[doc = link_to_wgpu_docs!(["`Instance::request_adapter()`"]: "struct.Instance.html#method.request_adapter")] +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[non_exhaustive] +pub enum RequestAdapterError { + /// No adapter available via the instance’s backends matched the request’s adapter criteria. + NotFound { + // These fields must be set by wgpu-core and wgpu, but are not intended to be stable API, + // only data for the production of the error message. + #[doc(hidden)] + active_backends: Backends, + #[doc(hidden)] + requested_backends: Backends, + #[doc(hidden)] + supported_backends: Backends, + #[doc(hidden)] + no_fallback_backends: Backends, + #[doc(hidden)] + no_adapter_backends: Backends, + #[doc(hidden)] + incompatible_surface_backends: Backends, + }, + + /// Attempted to obtain adapter specified by environment variable, but the environment variable + /// was not set. + EnvNotSet, +} + +impl core::error::Error for RequestAdapterError {} +impl fmt::Display for RequestAdapterError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + RequestAdapterError::NotFound { + active_backends, + requested_backends, + supported_backends, + no_fallback_backends, + no_adapter_backends, + incompatible_surface_backends, + } => { + write!(f, "No suitable graphics adapter found; ")?; + let mut first = true; + for backend in Backend::ALL { + let bit = Backends::from(backend); + let comma = if mem::take(&mut first) { "" } else { ", " }; + let explanation = if !requested_backends.contains(bit) { + // We prefer reporting this, because it makes the error most stable with + // respect to what is directly controllable by the caller, as opposed to + // compilation options or the run-time environment. + "not requested" + } else if !supported_backends.contains(bit) { + "support not compiled in" + } else if no_adapter_backends.contains(bit) { + "found no adapters" + } else if incompatible_surface_backends.contains(bit) { + "not compatible with provided surface" + } else if no_fallback_backends.contains(bit) { + "had no fallback adapters" + } else if !active_backends.contains(bit) { + // Backend requested but not active in this instance + if backend == Backend::Noop { + "not explicitly enabled" + } else { + "drivers/libraries could not be loaded" + } + } else { + // This path should be unreachable, but don't crash. + "[unknown reason]" + }; + write!(f, "{comma}{backend} {explanation}")?; + } + } + RequestAdapterError::EnvNotSet => f.write_str("WGPU_ADAPTER_NAME not set")?, + } + Ok(()) + } +} diff --git a/third_party/rust/wgpu-types/src/backend.rs b/third_party/rust/wgpu-types/src/backend.rs @@ -0,0 +1,674 @@ +//! [`Backend`], [`Backends`], and backend-specific options. + +use alloc::string::String; +use core::hash::Hash; + +#[cfg(any(feature = "serde", test))] +use serde::{Deserialize, Serialize}; + +use crate::link_to_wgpu_docs; + +#[cfg(doc)] +use crate::InstanceDescriptor; + +/// Backends supported by wgpu. +/// +/// See also [`Backends`]. +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum Backend { + /// Dummy backend, which may be used for testing. + /// + /// It performs no rendering or computation, but allows creation of stub GPU resource types, + /// so that code which manages GPU resources can be tested without an available GPU. + /// Specifically, the following operations are implemented: + /// + /// * Enumerating adapters will always return one noop adapter, which can be used to create + /// devices. + /// * Buffers may be created, written, mapped, and copied to other buffers. + /// * Command encoders may be created, but only buffer operations are useful. + /// + /// Other resources can be created but are nonfunctional; notably, + /// + /// * Render passes and compute passes are not executed. + /// * Textures may be created, but do not store any texels. + /// * There are no compatible surfaces. + /// + /// An adapter using the noop backend can only be obtained if [`NoopBackendOptions`] + /// enables it, in addition to the ordinary requirement of [`Backends::NOOP`] being set. + /// This ensures that applications not desiring a non-functional backend will not receive it. + Noop = 0, + /// Vulkan API (Windows, Linux, Android, MacOS via `vulkan-portability`/MoltenVK) + Vulkan = 1, + /// Metal API (Apple platforms) + Metal = 2, + /// Direct3D-12 (Windows) + Dx12 = 3, + /// OpenGL 3.3+ (Windows), OpenGL ES 3.0+ (Linux, Android, MacOS via Angle), and WebGL2 + Gl = 4, + /// WebGPU in the browser + BrowserWebGpu = 5, +} + +impl Backend { + /// Array of all [`Backend`] values, corresponding to [`Backends::all()`]. + pub const ALL: [Backend; Backends::all().bits().count_ones() as usize] = [ + Self::Noop, + Self::Vulkan, + Self::Metal, + Self::Dx12, + Self::Gl, + Self::BrowserWebGpu, + ]; + + /// Returns the string name of the backend. + #[must_use] + pub const fn to_str(self) -> &'static str { + match self { + Backend::Noop => "noop", + Backend::Vulkan => "vulkan", + Backend::Metal => "metal", + Backend::Dx12 => "dx12", + Backend::Gl => "gl", + Backend::BrowserWebGpu => "webgpu", + } + } +} + +impl core::fmt::Display for Backend { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(self.to_str()) + } +} + +bitflags::bitflags! { + /// Represents the backends that wgpu will use. + #[repr(transparent)] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + #[cfg_attr(feature = "serde", serde(transparent))] + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] + pub struct Backends: u32 { + /// [`Backend::Noop`]. + const NOOP = 1 << Backend::Noop as u32; + + /// [`Backend::Vulkan`]. + /// Supported on Windows, Linux/Android, and macOS/iOS via Vulkan Portability (with the Vulkan feature enabled) + const VULKAN = 1 << Backend::Vulkan as u32; + + /// [`Backend::Gl`]. + /// Supported on Linux/Android, the web through webassembly via WebGL, and Windows and + /// macOS/iOS via ANGLE + const GL = 1 << Backend::Gl as u32; + + /// [`Backend::Metal`]. + /// Supported on macOS and iOS. + const METAL = 1 << Backend::Metal as u32; + + /// [`Backend::Dx12`]. + /// Supported on Windows 10 and later + const DX12 = 1 << Backend::Dx12 as u32; + + /// [`Backend::BrowserWebGpu`]. + /// Supported when targeting the web through WebAssembly with the `webgpu` feature enabled. + /// + /// The WebGPU backend is special in several ways: + /// It is not not implemented by `wgpu_core` and instead by the higher level `wgpu` crate. + /// Whether WebGPU is targeted is decided upon the creation of the `wgpu::Instance`, + /// *not* upon adapter creation. See `wgpu::Instance::new`. + const BROWSER_WEBGPU = 1 << Backend::BrowserWebGpu as u32; + + /// All the apis that wgpu offers first tier of support for. + /// + /// * [`Backends::VULKAN`] + /// * [`Backends::METAL`] + /// * [`Backends::DX12`] + /// * [`Backends::BROWSER_WEBGPU`] + const PRIMARY = Self::VULKAN.bits() + | Self::METAL.bits() + | Self::DX12.bits() + | Self::BROWSER_WEBGPU.bits(); + + /// All the apis that wgpu offers second tier of support for. These may + /// be unsupported/still experimental. + /// + /// * [`Backends::GL`] + const SECONDARY = Self::GL.bits(); + } +} + +impl Default for Backends { + fn default() -> Self { + Self::all() + } +} + +impl From<Backend> for Backends { + fn from(backend: Backend) -> Self { + Self::from_bits(1 << backend as u32).unwrap() + } +} + +impl Backends { + /// Gets a set of backends from the environment variable `WGPU_BACKEND`. + /// + /// See [`Self::from_comma_list()`] for the format of the string. + pub fn from_env() -> Option<Self> { + let env = crate::env::var("WGPU_BACKEND")?; + Some(Self::from_comma_list(&env)) + } + + /// Takes the given options, modifies them based on the `WGPU_BACKEND` environment variable, and returns the result. + pub fn with_env(&self) -> Self { + if let Some(env) = Self::from_env() { + env + } else { + *self + } + } + + /// Generates a set of backends from a comma separated list of case-insensitive backend names. + /// + /// Whitespace is stripped, so both 'gl, dx12' and 'gl,dx12' are valid. + /// + /// Always returns WEBGPU on wasm over webgpu. + /// + /// Names: + /// - vulkan = "vulkan" or "vk" + /// - dx12 = "dx12" or "d3d12" + /// - metal = "metal" or "mtl" + /// - gles = "opengl" or "gles" or "gl" + /// - webgpu = "webgpu" + pub fn from_comma_list(string: &str) -> Self { + let mut backends = Self::empty(); + for backend in string.to_lowercase().split(',') { + backends |= match backend.trim() { + "vulkan" | "vk" => Self::VULKAN, + "dx12" | "d3d12" => Self::DX12, + "metal" | "mtl" => Self::METAL, + "opengl" | "gles" | "gl" => Self::GL, + "webgpu" => Self::BROWSER_WEBGPU, + "noop" => Self::NOOP, + b => { + log::warn!("unknown backend string '{b}'"); + continue; + } + } + } + + if backends.is_empty() { + log::warn!("no valid backend strings found!"); + } + + backends + } +} + +/// Options that are passed to a given backend. +/// +/// Part of [`InstanceDescriptor`]. +#[derive(Clone, Debug, Default)] +pub struct BackendOptions { + /// Options for the OpenGL/OpenGLES backend, [`Backend::Gl`]. + pub gl: GlBackendOptions, + /// Options for the DX12 backend, [`Backend::Dx12`]. + pub dx12: Dx12BackendOptions, + /// Options for the noop backend, [`Backend::Noop`]. + pub noop: NoopBackendOptions, +} + +impl BackendOptions { + /// Choose backend options by calling `from_env` on every field. + /// + /// See those methods for more information. + #[must_use] + pub fn from_env_or_default() -> Self { + Self { + gl: GlBackendOptions::from_env_or_default(), + dx12: Dx12BackendOptions::from_env_or_default(), + noop: NoopBackendOptions::from_env_or_default(), + } + } + + /// Takes the given options, modifies them based on the environment variables, and returns the result. + /// + /// This is equivalent to calling `with_env` on every field. + #[must_use] + pub fn with_env(self) -> Self { + Self { + gl: self.gl.with_env(), + dx12: self.dx12.with_env(), + noop: self.noop.with_env(), + } + } +} + +/// Configuration for the OpenGL/OpenGLES backend. +/// +/// Part of [`BackendOptions`]. +#[derive(Clone, Debug, Default)] +pub struct GlBackendOptions { + /// Which OpenGL ES 3 minor version to request, if using OpenGL ES. + pub gles_minor_version: Gles3MinorVersion, + /// Behavior of OpenGL fences. Affects how `on_completed_work_done` and `device.poll` behave. + pub fence_behavior: GlFenceBehavior, +} + +impl GlBackendOptions { + /// Choose OpenGL backend options by calling `from_env` on every field. + /// + /// See those methods for more information. + #[must_use] + pub fn from_env_or_default() -> Self { + let gles_minor_version = Gles3MinorVersion::from_env().unwrap_or_default(); + Self { + gles_minor_version, + fence_behavior: GlFenceBehavior::Normal, + } + } + + /// Takes the given options, modifies them based on the environment variables, and returns the result. + /// + /// This is equivalent to calling `with_env` on every field. + #[must_use] + pub fn with_env(self) -> Self { + let gles_minor_version = self.gles_minor_version.with_env(); + let short_circuit_fences = self.fence_behavior.with_env(); + Self { + gles_minor_version, + fence_behavior: short_circuit_fences, + } + } +} + +/// Configuration for the DX12 backend. +/// +/// Part of [`BackendOptions`]. +#[derive(Clone, Debug, Default)] +pub struct Dx12BackendOptions { + /// Which DX12 shader compiler to use. + pub shader_compiler: Dx12Compiler, + /// Presentation system to use. + pub presentation_system: Dx12SwapchainKind, + /// Whether to wait for the latency waitable object before acquiring the next swapchain image. + pub latency_waitable_object: Dx12UseFrameLatencyWaitableObject, +} + +impl Dx12BackendOptions { + /// Choose DX12 backend options by calling `from_env` on every field. + /// + /// See those methods for more information. + #[must_use] + pub fn from_env_or_default() -> Self { + let compiler = Dx12Compiler::from_env().unwrap_or_default(); + let presentation_system = Dx12SwapchainKind::from_env().unwrap_or_default(); + let latency_waitable_object = + Dx12UseFrameLatencyWaitableObject::from_env().unwrap_or_default(); + Self { + shader_compiler: compiler, + presentation_system, + latency_waitable_object, + } + } + + /// Takes the given options, modifies them based on the environment variables, and returns the result. + /// + /// This is equivalent to calling `with_env` on every field. + #[must_use] + pub fn with_env(self) -> Self { + let shader_compiler = self.shader_compiler.with_env(); + let presentation_system = self.presentation_system.with_env(); + let latency_waitable_object = self.latency_waitable_object.with_env(); + Self { + shader_compiler, + presentation_system, + latency_waitable_object, + } + } +} + +/// Configuration for the noop backend. +/// +/// Part of [`BackendOptions`]. +#[derive(Clone, Debug, Default)] +pub struct NoopBackendOptions { + /// Whether to allow the noop backend to be used. + /// + /// The noop backend stubs out all operations except for buffer creation and mapping, so + /// it must not be used when not expected. Therefore, it will not be used unless explicitly + /// enabled. + pub enable: bool, +} + +impl NoopBackendOptions { + /// Choose whether the noop backend is enabled from the environment. + /// + /// It will be enabled if the environment variable `WGPU_NOOP_BACKEND` has the value `1` + /// and not otherwise. Future versions may assign other meanings to other values. + #[must_use] + pub fn from_env_or_default() -> Self { + Self { + enable: Self::enable_from_env().unwrap_or(false), + } + } + + /// Takes the given options, modifies them based on the environment variables, and returns the + /// result. + /// + /// See [`from_env_or_default()`](Self::from_env_or_default) for the interpretation. + #[must_use] + pub fn with_env(self) -> Self { + Self { + enable: Self::enable_from_env().unwrap_or(self.enable), + } + } + + fn enable_from_env() -> Option<bool> { + let value = crate::env::var("WGPU_NOOP_BACKEND")?; + match value.as_str() { + "1" => Some(true), + "0" => Some(false), + _ => None, + } + } +} + +#[derive(Clone, Debug, Default, Copy, PartialEq, Eq)] +/// Selects which kind of swapchain to use on DX12. +pub enum Dx12SwapchainKind { + /// Use a DXGI swapchain made directly from the window's HWND. + /// + /// This does not support transparency but has better support from developer tooling from RenderDoc. + #[default] + DxgiFromHwnd, + /// Use a DXGI swapchain made from a DirectComposition visual made automatically from the window's HWND. + /// + /// This creates a single [`IDCompositionVisual`] over the entire window that is used by the `Surface`. + /// If a user wants to manage the composition tree themselves, they should create their own device and + /// composition, and pass the relevant visual down via [`SurfaceTargetUnsafe::CompositionVisual`][CV]. + /// + /// This supports transparent windows, but does not have support from RenderDoc. + /// + /// [`IDCompositionVisual`]: https://learn.microsoft.com/en-us/windows/win32/api/dcomp/nn-dcomp-idcompositionvisual + #[doc = link_to_wgpu_docs!(["CV"]: "struct.SurfaceTargetUnsafe.html#variant.CompositionVisual")] + DxgiFromVisual, +} + +impl Dx12SwapchainKind { + /// Choose which presentation system to use from the environment variable `WGPU_DX12_PRESENTATION_SYSTEM`. + /// + /// Valid values, case insensitive: + /// - `DxgiFromVisual` or `Visual` + /// - `DxgiFromHwnd` or `Hwnd` for [`Self::DxgiFromHwnd`] + #[must_use] + pub fn from_env() -> Option<Self> { + let value = crate::env::var("WGPU_DX12_PRESENTATION_SYSTEM") + .as_deref()? + .to_lowercase(); + match value.as_str() { + "dxgifromvisual" | "visual" => Some(Self::DxgiFromVisual), + "dxgifromhwnd" | "hwnd" => Some(Self::DxgiFromHwnd), + _ => None, + } + } + + /// Takes the given presentation system, modifies it based on the `WGPU_DX12_PRESENTATION_SYSTEM` environment variable, and returns the result. + /// + /// See [`from_env`](Self::from_env) for more information. + #[must_use] + pub fn with_env(self) -> Self { + if let Some(presentation_system) = Self::from_env() { + presentation_system + } else { + self + } + } +} + +/// DXC shader model. +#[derive(Clone, Debug)] +#[allow(missing_docs)] +pub enum DxcShaderModel { + V6_0, + V6_1, + V6_2, + V6_3, + V6_4, + V6_5, + V6_6, + V6_7, +} + +/// Selects which DX12 shader compiler to use. +#[derive(Clone, Debug, Default)] +pub enum Dx12Compiler { + /// The Fxc compiler (default) is old, slow and unmaintained. + /// + /// However, it doesn't require any additional .dlls to be shipped with the application. + #[default] + Fxc, + /// The Dxc compiler is new, fast and maintained. + /// + /// However, it requires `dxcompiler.dll` to be shipped with the application. + /// These files can be downloaded from <https://github.com/microsoft/DirectXShaderCompiler/releases>. + /// + /// Minimum supported version: [v1.8.2502](https://github.com/microsoft/DirectXShaderCompiler/releases/tag/v1.8.2502) + /// + /// It also requires WDDM 2.1 (Windows 10 version 1607). + DynamicDxc { + /// Path to `dxcompiler.dll`. + dxc_path: String, + /// Maximum shader model the given dll supports. + max_shader_model: DxcShaderModel, + }, + /// The statically-linked variant of Dxc. + /// + /// The `static-dxc` feature is required for this setting to be used successfully on DX12. + /// Not available on `windows-aarch64-pc-*` targets. + StaticDxc, +} + +impl Dx12Compiler { + /// Helper function to construct a `DynamicDxc` variant with default paths. + /// + /// The dll must support at least shader model 6.8. + pub fn default_dynamic_dxc() -> Self { + Self::DynamicDxc { + dxc_path: String::from("dxcompiler.dll"), + max_shader_model: DxcShaderModel::V6_7, // should be 6.8 but the variant is missing + } + } + + /// Choose which DX12 shader compiler to use from the environment variable `WGPU_DX12_COMPILER`. + /// + /// Valid values, case insensitive: + /// - `Fxc` + /// - `Dxc` or `DynamicDxc` + /// - `StaticDxc` + #[must_use] + pub fn from_env() -> Option<Self> { + let value = crate::env::var("WGPU_DX12_COMPILER") + .as_deref()? + .to_lowercase(); + match value.as_str() { + "dxc" | "dynamicdxc" => Some(Self::default_dynamic_dxc()), + "staticdxc" => Some(Self::StaticDxc), + "fxc" => Some(Self::Fxc), + _ => None, + } + } + + /// Takes the given compiler, modifies it based on the `WGPU_DX12_COMPILER` environment variable, and returns the result. + /// + /// See `from_env` for more information. + #[must_use] + pub fn with_env(self) -> Self { + if let Some(compiler) = Self::from_env() { + compiler + } else { + self + } + } +} + +/// Whether and how to use a waitable handle obtained from `GetFrameLatencyWaitableObject`. +#[derive(Clone, Debug, Default)] +pub enum Dx12UseFrameLatencyWaitableObject { + /// Do not obtain a waitable handle and do not wait for it. The swapchain will + /// be created without the `DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT` flag. + None, + /// Obtain a waitable handle and wait for it before acquiring the next swapchain image. + #[default] + Wait, + /// Create the swapchain with the `DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT` flag and + /// obtain a waitable handle, but do not wait for it before acquiring the next swapchain image. + /// This is useful if the application wants to wait for the waitable object itself. + DontWait, +} + +impl Dx12UseFrameLatencyWaitableObject { + /// Choose whether to use a frame latency waitable object from the environment variable `WGPU_DX12_USE_FRAME_LATENCY_WAITABLE_OBJECT`. + /// + /// Valid values, case insensitive: + /// - `None` + /// - `Wait` + /// - `DontWait` + #[must_use] + pub fn from_env() -> Option<Self> { + let value = crate::env::var("WGPU_DX12_USE_FRAME_LATENCY_WAITABLE_OBJECT") + .as_deref()? + .to_lowercase(); + match value.as_str() { + "none" => Some(Self::None), + "wait" => Some(Self::Wait), + "dontwait" => Some(Self::DontWait), + _ => None, + } + } + + /// Takes the given setting, modifies it based on the `WGPU_DX12_USE_FRAME_LATENCY_WAITABLE_OBJECT` environment variable, and returns the result. + /// + /// See `from_env` for more information. + #[must_use] + pub fn with_env(self) -> Self { + if let Some(compiler) = Self::from_env() { + compiler + } else { + self + } + } +} + +/// Selects which OpenGL ES 3 minor version to request. +/// +/// When using ANGLE as an OpenGL ES/EGL implementation, explicitly requesting `Version1` can provide a non-conformant ES 3.1 on APIs like D3D11. +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash)] +pub enum Gles3MinorVersion { + /// No explicit minor version is requested, the driver automatically picks the highest available. + #[default] + Automatic, + + /// Request an ES 3.0 context. + Version0, + + /// Request an ES 3.1 context. + Version1, + + /// Request an ES 3.2 context. + Version2, +} + +impl Gles3MinorVersion { + /// Choose which minor OpenGL ES version to use from the environment variable `WGPU_GLES_MINOR_VERSION`. + /// + /// Possible values are `0`, `1`, `2` or `automatic`. Case insensitive. + /// + /// Use with `unwrap_or_default()` to get the default value if the environment variable is not set. + #[must_use] + pub fn from_env() -> Option<Self> { + let value = crate::env::var("WGPU_GLES_MINOR_VERSION") + .as_deref()? + .to_lowercase(); + match value.as_str() { + "automatic" => Some(Self::Automatic), + "0" => Some(Self::Version0), + "1" => Some(Self::Version1), + "2" => Some(Self::Version2), + _ => None, + } + } + + /// Takes the given compiler, modifies it based on the `WGPU_GLES_MINOR_VERSION` environment variable, and returns the result. + /// + /// See `from_env` for more information. + #[must_use] + pub fn with_env(self) -> Self { + if let Some(compiler) = Self::from_env() { + compiler + } else { + self + } + } +} + +/// Dictate the behavior of fences in OpenGL. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] +pub enum GlFenceBehavior { + /// Fences in OpenGL behave normally. If you don't know what to pick, this is what you want. + #[default] + Normal, + /// Fences in OpenGL are short-circuited to always return `true` immediately. + /// + /// This solves a very specific issue that arose due to a bug in wgpu-core that made + /// many WebGL programs work when they "shouldn't" have. If you have code that is trying + /// to call `device.poll(wgpu::PollType::Wait)` on WebGL, you need to enable this option + /// for the "Wait" to behave how you would expect. + /// + /// Previously all `poll(Wait)` acted like the OpenGL fences were signalled even if they weren't. + /// See <https://github.com/gfx-rs/wgpu/issues/4589> for more information. + /// + /// When this is set `Queue::on_completed_work_done` will always return the next time the device + /// is maintained, not when the work is actually done on the GPU. + AutoFinish, +} + +impl GlFenceBehavior { + /// Returns true if the fence behavior is `AutoFinish`. + pub fn is_auto_finish(&self) -> bool { + matches!(self, Self::AutoFinish) + } + + /// Returns true if the fence behavior is `Normal`. + pub fn is_normal(&self) -> bool { + matches!(self, Self::Normal) + } + + /// Choose which minor OpenGL ES version to use from the environment variable `WGPU_GL_FENCE_BEHAVIOR`. + /// + /// Possible values are `Normal` or `AutoFinish`. Case insensitive. + /// + /// Use with `unwrap_or_default()` to get the default value if the environment variable is not set. + #[must_use] + pub fn from_env() -> Option<Self> { + let value = crate::env::var("WGPU_GL_FENCE_BEHAVIOR") + .as_deref()? + .to_lowercase(); + match value.as_str() { + "normal" => Some(Self::Normal), + "autofinish" => Some(Self::AutoFinish), + _ => None, + } + } + + /// Takes the given compiler, modifies it based on the `WGPU_GL_FENCE_BEHAVIOR` environment variable, and returns the result. + /// + /// See `from_env` for more information. + #[must_use] + pub fn with_env(self) -> Self { + if let Some(fence) = Self::from_env() { + fence + } else { + self + } + } +} diff --git a/third_party/rust/wgpu-types/src/binding.rs b/third_party/rust/wgpu-types/src/binding.rs @@ -0,0 +1,341 @@ +//! Bind groups and the bindings in them. + +use core::num::NonZeroU32; + +#[cfg(any(feature = "serde", test))] +use serde::{Deserialize, Serialize}; + +use crate::{link_to_wgpu_docs, link_to_wgpu_item, BufferSize}; + +#[cfg(doc)] +use crate::Features; + +/// Type of a binding in a [bind group layout][`BindGroupLayoutEntry`]. +/// +/// For each binding in a layout, a [`BindGroup`] must provide a [`BindingResource`] of the +/// corresponding type. +/// +/// Corresponds to WebGPU's mutually exclusive fields within [`GPUBindGroupLayoutEntry`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gpubindgrouplayoutentry). +/// +#[doc = link_to_wgpu_item!(enum BindingResource)] +#[doc = link_to_wgpu_item!(struct BindGroup)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum BindingType { + /// A buffer binding. + /// + /// Corresponds to [WebGPU `GPUBufferBindingLayout`]( + /// https://gpuweb.github.io/gpuweb/#dictdef-gpubufferbindinglayout). + Buffer { + /// Sub-type of the buffer binding. + ty: BufferBindingType, + + /// Indicates that the binding has a dynamic offset. + /// + /// One offset must be passed to [`RenderPass::set_bind_group`][RPsbg] + /// for each dynamic binding in increasing order of binding number. + /// + #[doc = link_to_wgpu_docs!(["RPsbg"]: "struct.RenderPass.html#method.set_bind_group")] + #[cfg_attr(feature = "serde", serde(default))] + has_dynamic_offset: bool, + + /// The minimum size for a [`BufferBinding`] matching this entry, in bytes. + /// + /// If this is `Some(size)`: + /// + /// - When calling [`create_bind_group`], the resource at this bind point + /// must be a [`BindingResource::Buffer`] whose effective size is at + /// least `size`. + /// + /// - When calling [`create_render_pipeline`] or [`create_compute_pipeline`], + /// `size` must be at least the [minimum buffer binding size] for the + /// shader module global at this bind point: large enough to hold the + /// global's value, along with one element of a trailing runtime-sized + /// array, if present. + /// + /// If this is `None`: + /// + /// - Each draw or dispatch command checks that the buffer range at this + /// bind point satisfies the [minimum buffer binding size]. + /// + #[doc = link_to_wgpu_item!(struct BufferBinding)] + #[doc = link_to_wgpu_docs!(["`create_bind_group`"]: "struct.Device.html#method.create_bind_group")] + #[doc = link_to_wgpu_docs!(["`BindingResource::Buffer`"]: "enum.BindingResource.html#variant.Buffer")] + /// [minimum buffer binding size]: https://www.w3.org/TR/webgpu/#minimum-buffer-binding-size + #[doc = link_to_wgpu_docs!(["`create_render_pipeline`"]: "struct.Device.html#method.create_render_pipeline")] + #[doc = link_to_wgpu_docs!(["`create_compute_pipeline`"]: "struct.Device.html#method.create_compute_pipeline")] + #[cfg_attr(feature = "serde", serde(default))] + min_binding_size: Option<BufferSize>, + }, + /// A sampler that can be used to sample a texture. + /// + /// Example WGSL syntax: + /// ```rust,ignore + /// @group(0) @binding(0) + /// var s: sampler; + /// ``` + /// + /// Example GLSL syntax: + /// ```cpp,ignore + /// layout(binding = 0) + /// uniform sampler s; + /// ``` + /// + /// Corresponds to [WebGPU `GPUSamplerBindingLayout`]( + /// https://gpuweb.github.io/gpuweb/#dictdef-gpusamplerbindinglayout). + Sampler(SamplerBindingType), + /// A texture binding. + /// + /// Example WGSL syntax: + /// ```rust,ignore + /// @group(0) @binding(0) + /// var t: texture_2d<f32>; + /// ``` + /// + /// Example GLSL syntax: + /// ```cpp,ignore + /// layout(binding = 0) + /// uniform texture2D t; + /// ``` + /// + /// Corresponds to [WebGPU `GPUTextureBindingLayout`]( + /// https://gpuweb.github.io/gpuweb/#dictdef-gputexturebindinglayout). + Texture { + /// Sample type of the texture binding. + sample_type: crate::TextureSampleType, + /// Dimension of the texture view that is going to be sampled. + view_dimension: crate::TextureViewDimension, + /// True if the texture has a sample count greater than 1. If this is true, + /// the texture must be declared as `texture_multisampled_2d` or + /// `texture_depth_multisampled_2d` in the shader, and read using `textureLoad`. + multisampled: bool, + }, + /// A storage texture. + /// + /// Example WGSL syntax: + /// ```rust,ignore + /// @group(0) @binding(0) + /// var my_storage_image: texture_storage_2d<r32float, write>; + /// ``` + /// + /// Example GLSL syntax: + /// ```cpp,ignore + /// layout(set=0, binding=0, r32f) writeonly uniform image2D myStorageImage; + /// ``` + /// Note that the texture format must be specified in the shader, along with the + /// access mode. For WGSL, the format must be one of the enumerants in the list + /// of [storage texel formats](https://gpuweb.github.io/gpuweb/wgsl/#storage-texel-formats). + /// + /// Corresponds to [WebGPU `GPUStorageTextureBindingLayout`]( + /// https://gpuweb.github.io/gpuweb/#dictdef-gpustoragetexturebindinglayout). + StorageTexture { + /// Allowed access to this texture. + access: crate::StorageTextureAccess, + /// Format of the texture. + format: crate::TextureFormat, + /// Dimension of the texture view that is going to be sampled. + view_dimension: crate::TextureViewDimension, + }, + + /// A ray-tracing acceleration structure binding. + /// + /// Example WGSL syntax: + /// ```rust,ignore + /// @group(0) @binding(0) + /// var as: acceleration_structure; + /// ``` + /// + /// or with vertex return enabled + /// ```rust,ignore + /// @group(0) @binding(0) + /// var as: acceleration_structure<vertex_return>; + /// ``` + /// + /// Example GLSL syntax: + /// ```cpp,ignore + /// layout(binding = 0) + /// uniform accelerationStructureEXT as; + /// ``` + AccelerationStructure { + /// Whether this acceleration structure can be used to + /// create a ray query that has flag vertex return in the shader + /// + /// If enabled requires [`Features::EXPERIMENTAL_RAY_HIT_VERTEX_RETURN`] + vertex_return: bool, + }, + + /// An external texture binding. + /// + /// Example WGSL syntax: + /// ```rust,ignore + /// @group(0) @binding(0) + /// var t: texture_external; + /// ``` + /// + /// Corresponds to [WebGPU `GPUExternalTextureBindingLayout`]( + /// https://gpuweb.github.io/gpuweb/#dictdef-gpuexternaltexturebindinglayout). + /// + /// Requires [`Features::EXTERNAL_TEXTURE`] + ExternalTexture, +} + +impl BindingType { + /// Returns true for buffer bindings with dynamic offset enabled. + #[must_use] + pub fn has_dynamic_offset(&self) -> bool { + match *self { + Self::Buffer { + has_dynamic_offset, .. + } => has_dynamic_offset, + _ => false, + } + } +} + +bitflags::bitflags! { + /// Describes the shader stages that a binding will be visible from. + /// + /// These can be combined so something that is visible from both vertex and fragment shaders can be defined as: + /// + /// `ShaderStages::VERTEX | ShaderStages::FRAGMENT` + /// + /// Corresponds to [WebGPU `GPUShaderStageFlags`]( + /// https://gpuweb.github.io/gpuweb/#typedefdef-gpushaderstageflags). + #[repr(transparent)] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + #[cfg_attr(feature = "serde", serde(transparent))] + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] + pub struct ShaderStages: u32 { + /// Binding is not visible from any shader stage. + const NONE = 0; + /// Binding is visible from the vertex shader of a render pipeline. + const VERTEX = 1 << 0; + /// Binding is visible from the fragment shader of a render pipeline. + const FRAGMENT = 1 << 1; + /// Binding is visible from the compute shader of a compute pipeline. + const COMPUTE = 1 << 2; + /// Binding is visible from the vertex and fragment shaders of a render pipeline. + const VERTEX_FRAGMENT = Self::VERTEX.bits() | Self::FRAGMENT.bits(); + /// Binding is visible from the task shader of a mesh pipeline. + const TASK = 1 << 3; + /// Binding is visible from the mesh shader of a mesh pipeline. + const MESH = 1 << 4; + } +} + +/// Specific type of a buffer binding. +/// +/// Corresponds to [WebGPU `GPUBufferBindingType`]( +/// https://gpuweb.github.io/gpuweb/#enumdef-gpubufferbindingtype). +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum BufferBindingType { + /// A buffer for uniform values. + /// + /// Example WGSL syntax: + /// ```rust,ignore + /// struct Globals { + /// a_uniform: vec2<f32>, + /// another_uniform: vec2<f32>, + /// } + /// @group(0) @binding(0) + /// var<uniform> globals: Globals; + /// ``` + /// + /// Example GLSL syntax: + /// ```cpp,ignore + /// layout(std140, binding = 0) + /// uniform Globals { + /// vec2 aUniform; + /// vec2 anotherUniform; + /// }; + /// ``` + #[default] + Uniform, + /// A storage buffer. + /// + /// Example WGSL syntax: + /// ```rust,ignore + /// @group(0) @binding(0) + /// var<storage, read_write> my_element: array<vec4<f32>>; + /// ``` + /// + /// Example GLSL syntax: + /// ```cpp,ignore + /// layout (set=0, binding=0) buffer myStorageBuffer { + /// vec4 myElement[]; + /// }; + /// ``` + Storage { + /// If `true`, the buffer can only be read in the shader, + /// and it: + /// - may or may not be annotated with `read` (WGSL). + /// - must be annotated with `readonly` (GLSL). + /// + /// Example WGSL syntax: + /// ```rust,ignore + /// @group(0) @binding(0) + /// var<storage, read> my_element: array<vec4<f32>>; + /// ``` + /// + /// Example GLSL syntax: + /// ```cpp,ignore + /// layout (set=0, binding=0) readonly buffer myStorageBuffer { + /// vec4 myElement[]; + /// }; + /// ``` + read_only: bool, + }, +} + +/// Specific type of a sampler binding. +/// +/// For use in [`BindingType::Sampler`]. +/// +/// Corresponds to [WebGPU `GPUSamplerBindingType`]( +/// https://gpuweb.github.io/gpuweb/#enumdef-gpusamplerbindingtype). +#[repr(C)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] +pub enum SamplerBindingType { + /// The sampling result is produced based on more than a single color sample from a texture, + /// e.g. when bilinear interpolation is enabled. + Filtering, + /// The sampling result is produced based on a single color sample from a texture. + NonFiltering, + /// Use as a comparison sampler instead of a normal sampler. + /// For more info take a look at the analogous functionality in OpenGL: <https://www.khronos.org/opengl/wiki/Sampler_Object#Comparison_mode>. + Comparison, +} + +/// Describes a single binding inside a bind group. +/// +/// Corresponds to [WebGPU `GPUBindGroupLayoutEntry`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gpubindgrouplayoutentry). +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct BindGroupLayoutEntry { + /// Binding index. Must match shader index and be unique inside a `BindGroupLayout`. A binding + /// of index 1, would be described as `@group(0) @binding(1)` in shaders. + pub binding: u32, + /// Which shader stages can see this binding. + pub visibility: ShaderStages, + /// The type of the binding + pub ty: BindingType, + /// If the binding is an array of multiple resources. Corresponds to `binding_array<T>` in the shader. + /// + /// When this is `Some` the following validation applies: + /// - Size must be of value 1 or greater. + /// - When `ty == BindingType::Texture`, [`Features::TEXTURE_BINDING_ARRAY`] must be supported. + /// - When `ty == BindingType::Sampler`, [`Features::TEXTURE_BINDING_ARRAY`] must be supported. + /// - When `ty == BindingType::Buffer`, [`Features::BUFFER_BINDING_ARRAY`] must be supported. + /// - When `ty == BindingType::Buffer` and `ty.ty == BufferBindingType::Storage`, [`Features::STORAGE_RESOURCE_BINDING_ARRAY`] must be supported. + /// - When `ty == BindingType::StorageTexture`, [`Features::STORAGE_RESOURCE_BINDING_ARRAY`] must be supported. + /// - When any binding in the group is an array, no `BindingType::Buffer` in the group may have `has_dynamic_offset == true` + /// - When any binding in the group is an array, no `BindingType::Buffer` in the group may have `ty.ty == BufferBindingType::Uniform`. + /// + #[cfg_attr(feature = "serde", serde(default))] + pub count: Option<NonZeroU32>, +} diff --git a/third_party/rust/wgpu-types/src/buffer.rs b/third_party/rust/wgpu-types/src/buffer.rs @@ -0,0 +1,162 @@ +#[cfg(any(feature = "serde", test))] +use serde::{Deserialize, Serialize}; + +#[cfg(doc)] +use crate::{DownlevelFlags, COPY_BUFFER_ALIGNMENT}; + +/// Describes a [`Buffer`](../wgpu/struct.Buffer.html). +/// +/// Corresponds to [WebGPU `GPUBufferDescriptor`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gpubufferdescriptor). +#[repr(C)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct BufferDescriptor<L> { + /// Debug label of a buffer. This will show up in graphics debuggers for easy identification. + pub label: L, + /// Size of a buffer, in bytes. + pub size: crate::BufferAddress, + /// Usages of a buffer. If the buffer is used in any way that isn't specified here, the operation + /// will panic. + /// + /// Specifying only usages the application will actually perform may increase performance. + /// Additionally, on the WebGL backend, there are restrictions on [`BufferUsages::INDEX`]; + /// see [`DownlevelFlags::UNRESTRICTED_INDEX_BUFFER`] for more information. + pub usage: BufferUsages, + /// Allows a buffer to be mapped immediately after they are made. It does not have to be [`BufferUsages::MAP_READ`] or + /// [`BufferUsages::MAP_WRITE`], all buffers are allowed to be mapped at creation. + /// + /// If this is `true`, [`size`](#structfield.size) must be a multiple of + /// [`COPY_BUFFER_ALIGNMENT`]. + pub mapped_at_creation: bool, +} + +impl<L> BufferDescriptor<L> { + /// Takes a closure and maps the label of the buffer descriptor into another. + #[must_use] + pub fn map_label<K>(&self, fun: impl FnOnce(&L) -> K) -> BufferDescriptor<K> { + BufferDescriptor { + label: fun(&self.label), + size: self.size, + usage: self.usage, + mapped_at_creation: self.mapped_at_creation, + } + } +} + +bitflags::bitflags! { + /// Different ways that you can use a buffer. + /// + /// The usages determine what kind of memory the buffer is allocated from and what + /// actions the buffer can partake in. + /// + /// Specifying only usages the application will actually perform may increase performance. + /// Additionally, on the WebGL backend, there are restrictions on [`BufferUsages::INDEX`]; + /// see [`DownlevelFlags::UNRESTRICTED_INDEX_BUFFER`] for more information. + /// + /// Corresponds to [WebGPU `GPUBufferUsageFlags`]( + /// https://gpuweb.github.io/gpuweb/#typedefdef-gpubufferusageflags). + #[repr(transparent)] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + #[cfg_attr(feature = "serde", serde(transparent))] + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] + pub struct BufferUsages: u32 { + /// Allow a buffer to be mapped for reading using [`Buffer::map_async`] + [`Buffer::get_mapped_range`]. + /// This does not include creating a buffer with [`BufferDescriptor::mapped_at_creation`] set. + /// + /// If [`Features::MAPPABLE_PRIMARY_BUFFERS`] isn't enabled, the only other usage a buffer + /// may have is COPY_DST. + const MAP_READ = 1 << 0; + /// Allow a buffer to be mapped for writing using [`Buffer::map_async`] + [`Buffer::get_mapped_range_mut`]. + /// This does not include creating a buffer with [`BufferDescriptor::mapped_at_creation`] set. + /// + /// If [`Features::MAPPABLE_PRIMARY_BUFFERS`] feature isn't enabled, the only other usage a buffer + /// may have is COPY_SRC. + const MAP_WRITE = 1 << 1; + /// Allow a buffer to be the source buffer for a [`CommandEncoder::copy_buffer_to_buffer`] or [`CommandEncoder::copy_buffer_to_texture`] + /// operation. + const COPY_SRC = 1 << 2; + /// Allow a buffer to be the destination buffer for a [`CommandEncoder::copy_buffer_to_buffer`], [`CommandEncoder::copy_texture_to_buffer`], + /// [`CommandEncoder::clear_buffer`] or [`Queue::write_buffer`] operation. + const COPY_DST = 1 << 3; + /// Allow a buffer to be the index buffer in a draw operation. + const INDEX = 1 << 4; + /// Allow a buffer to be the vertex buffer in a draw operation. + const VERTEX = 1 << 5; + /// Allow a buffer to be a [`BufferBindingType::Uniform`] inside a bind group. + const UNIFORM = 1 << 6; + /// Allow a buffer to be a [`BufferBindingType::Storage`] inside a bind group. + const STORAGE = 1 << 7; + /// Allow a buffer to be the indirect buffer in an indirect draw call. + const INDIRECT = 1 << 8; + /// Allow a buffer to be the destination buffer for a [`CommandEncoder::resolve_query_set`] operation. + const QUERY_RESOLVE = 1 << 9; + /// Allows a buffer to be used as input for a bottom level acceleration structure build + const BLAS_INPUT = 1 << 10; + /// Allows a buffer to be used as input for a top level acceleration structure build + const TLAS_INPUT = 1 << 11; + } +} + +bitflags::bitflags! { + /// Similar to `BufferUsages`, but used only for `CommandEncoder::transition_resources`. + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + #[cfg_attr(feature = "serde", serde(transparent))] + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] + pub struct BufferUses: u16 { + /// The argument to a read-only mapping. + const MAP_READ = 1 << 0; + /// The argument to a write-only mapping. + const MAP_WRITE = 1 << 1; + /// The source of a hardware copy. + /// cbindgen:ignore + const COPY_SRC = 1 << 2; + /// The destination of a hardware copy. + /// cbindgen:ignore + const COPY_DST = 1 << 3; + /// The index buffer used for drawing. + const INDEX = 1 << 4; + /// A vertex buffer used for drawing. + const VERTEX = 1 << 5; + /// A uniform buffer bound in a bind group. + const UNIFORM = 1 << 6; + /// A read-only storage buffer used in a bind group. + /// cbindgen:ignore + const STORAGE_READ_ONLY = 1 << 7; + /// A read-write buffer used in a bind group. + /// cbindgen:ignore + const STORAGE_READ_WRITE = 1 << 8; + /// The indirect or count buffer in a indirect draw or dispatch. + const INDIRECT = 1 << 9; + /// A buffer used to store query results. + const QUERY_RESOLVE = 1 << 10; + /// Buffer used for acceleration structure building. + const ACCELERATION_STRUCTURE_SCRATCH = 1 << 11; + /// Buffer used for bottom level acceleration structure building. + const BOTTOM_LEVEL_ACCELERATION_STRUCTURE_INPUT = 1 << 12; + /// Buffer used for top level acceleration structure building. + const TOP_LEVEL_ACCELERATION_STRUCTURE_INPUT = 1 << 13; + /// A buffer used to store the compacted size of an acceleration structure + const ACCELERATION_STRUCTURE_QUERY = 1 << 14; + /// The combination of states that a buffer may be in _at the same time_. + const INCLUSIVE = Self::MAP_READ.bits() | Self::COPY_SRC.bits() | + Self::INDEX.bits() | Self::VERTEX.bits() | Self::UNIFORM.bits() | + Self::STORAGE_READ_ONLY.bits() | Self::INDIRECT.bits() | Self::BOTTOM_LEVEL_ACCELERATION_STRUCTURE_INPUT.bits() | Self::TOP_LEVEL_ACCELERATION_STRUCTURE_INPUT.bits(); + /// The combination of states that a buffer must exclusively be in. + const EXCLUSIVE = Self::MAP_WRITE.bits() | Self::COPY_DST.bits() | Self::STORAGE_READ_WRITE.bits() | Self::ACCELERATION_STRUCTURE_SCRATCH.bits(); + /// The combination of all usages that the are guaranteed to be be ordered by the hardware. + /// If a usage is ordered, then if the buffer state doesn't change between draw calls, there + /// are no barriers needed for synchronization. + const ORDERED = Self::INCLUSIVE.bits() | Self::MAP_WRITE.bits(); + } +} + +/// A buffer transition for use with `CommandEncoder::transition_resources`. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct BufferTransition<T> { + /// The buffer to transition. + pub buffer: T, + /// The new state to transition to. + pub state: BufferUses, +} diff --git a/third_party/rust/wgpu-types/src/counters.rs b/third_party/rust/wgpu-types/src/counters.rs @@ -96,8 +96,8 @@ impl Default for InternalCounter { } } -impl core::fmt::Debug for InternalCounter { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { +impl fmt::Debug for InternalCounter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.read().fmt(f) } } diff --git a/third_party/rust/wgpu-types/src/device.rs b/third_party/rust/wgpu-types/src/device.rs @@ -0,0 +1,103 @@ +use core::ops::Range; + +#[cfg(any(feature = "serde", test))] +use serde::{Deserialize, Serialize}; + +/// Describes a [`Device`](../wgpu/struct.Device.html). +/// +/// Corresponds to [WebGPU `GPUDeviceDescriptor`]( +/// https://gpuweb.github.io/gpuweb/#gpudevicedescriptor). +#[derive(Clone, Debug, Default)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct DeviceDescriptor<L> { + /// Debug label for the device. + pub label: L, + /// Specifies the features that are required by the device request. + /// The request will fail if the adapter cannot provide these features. + /// + /// Exactly the specified set of features, and no more or less, + /// will be allowed in validation of API calls on the resulting device. + pub required_features: crate::Features, + /// Specifies the limits that are required by the device request. + /// The request will fail if the adapter cannot provide these limits. + /// + /// Exactly the specified limits, and no better or worse, + /// will be allowed in validation of API calls on the resulting device. + pub required_limits: crate::Limits, + /// Specifies whether `self.required_features` is allowed to contain experimental features. + #[cfg_attr(feature = "serde", serde(skip))] + pub experimental_features: crate::ExperimentalFeatures, + /// Hints for memory allocation strategies. + pub memory_hints: MemoryHints, + /// Whether API tracing for debugging is enabled, + /// and where the trace is written if so. + pub trace: Trace, +} + +impl<L> DeviceDescriptor<L> { + /// Takes a closure and maps the label of the device descriptor into another. + #[must_use] + pub fn map_label<K>(&self, fun: impl FnOnce(&L) -> K) -> DeviceDescriptor<K> { + DeviceDescriptor { + label: fun(&self.label), + required_features: self.required_features, + required_limits: self.required_limits.clone(), + experimental_features: self.experimental_features, + memory_hints: self.memory_hints.clone(), + trace: self.trace.clone(), + } + } +} + +/// Hints to the device about the memory allocation strategy. +/// +/// Some backends may ignore these hints. +#[derive(Clone, Debug, Default)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum MemoryHints { + /// Favor performance over memory usage (the default value). + #[default] + Performance, + /// Favor memory usage over performance. + MemoryUsage, + /// Applications that have control over the content that is rendered + /// (typically games) may find an optimal compromise between memory + /// usage and performance by specifying the allocation configuration. + Manual { + /// Defines the range of allowed memory block sizes for sub-allocated + /// resources. + /// + /// The backend may attempt to group multiple resources into fewer + /// device memory blocks (sub-allocation) for performance reasons. + /// The start of the provided range specifies the initial memory + /// block size for sub-allocated resources. After running out of + /// space in existing memory blocks, the backend may chose to + /// progressively increase the block size of subsequent allocations + /// up to a limit specified by the end of the range. + /// + /// This does not limit resource sizes. If a resource does not fit + /// in the specified range, it will typically be placed in a dedicated + /// memory block. + suballocated_device_memory_block_size: Range<u64>, + }, +} + +/// Controls API call tracing and specifies where the trace is written. +/// +/// **Note:** Tracing is currently unavailable. +/// See [issue 5974](https://github.com/gfx-rs/wgpu/issues/5974) for updates. +#[derive(Clone, Debug, Default)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +// This enum must be non-exhaustive so that enabling the "trace" feature is not a semver break. +#[non_exhaustive] +pub enum Trace { + /// Tracing disabled. + #[default] + Off, + + /// Tracing enabled. + #[cfg(feature = "trace")] + // This must be owned rather than `&'a Path`, because if it were that, then the lifetime + // parameter would be unused when the "trace" feature is disabled, which is prohibited. + Directory(std::path::PathBuf), +} diff --git a/third_party/rust/wgpu-types/src/features.rs b/third_party/rust/wgpu-types/src/features.rs @@ -1,4 +1,4 @@ -use crate::VertexFormat; +use crate::{link_to_wgpu_docs, link_to_wgpu_item, VertexFormat}; #[cfg(feature = "serde")] use alloc::fmt; use alloc::vec::Vec; @@ -62,6 +62,9 @@ mod webgpu_impl { #[doc(hidden)] pub const WEBGPU_FEATURE_CLIP_DISTANCES: u64 = 1 << 14; + + #[doc(hidden)] + pub const WEBGPU_FEATURE_IMMEDIATES: u64 = 1 << 15; } macro_rules! bitflags_array_impl { @@ -800,39 +803,11 @@ bitflags_array! { /// /// This is a native only feature. /// - /// [`RenderPass::multi_draw_indirect`]: ../wgpu/struct.RenderPass.html#method.multi_draw_indirect - /// [`RenderPass::multi_draw_indexed_indirect`]: ../wgpu/struct.RenderPass.html#method.multi_draw_indexed_indirect - /// [`RenderPass::multi_draw_indirect_count`]: ../wgpu/struct.RenderPass.html#method.multi_draw_indirect_count - /// [`RenderPass::multi_draw_indexed_indirect_count`]: ../wgpu/struct.RenderPass.html#method.multi_draw_indexed_indirect_count + #[doc = link_to_wgpu_docs!(["`RenderPass::multi_draw_indirect`"]: "struct.RenderPass.html#method.multi_draw_indirect")] + #[doc = link_to_wgpu_docs!(["`RenderPass::multi_draw_indexed_indirect`"]: "struct.RenderPass.html#method.multi_draw_indexed_indirect")] + #[doc = link_to_wgpu_docs!(["`RenderPass::multi_draw_indirect_count`"]: "struct.RenderPass.html#method.multi_draw_indirect_count")] + #[doc = link_to_wgpu_docs!(["`RenderPass::multi_draw_indexed_indirect_count`"]: "struct.RenderPass.html#method.multi_draw_indexed_indirect_count")] const MULTI_DRAW_INDIRECT_COUNT = 1 << 15; - /// Allows the use of push constants: small, fast bits of memory that can be updated - /// inside a [`RenderPass`]. - /// - /// Allows the user to call [`RenderPass::set_push_constants`], provide a non-empty array - /// to [`PipelineLayoutDescriptor`], and provide a non-zero limit to [`Limits::max_push_constant_size`]. - /// - /// A block of push constants can be declared in WGSL with `var<push_constant>`: - /// - /// ```rust,ignore - /// struct PushConstants { example: f32, } - /// var<push_constant> c: PushConstants; - /// ``` - /// - /// In GLSL, this corresponds to `layout(push_constant) uniform Name {..}`. - /// - /// Supported platforms: - /// - DX12 - /// - Vulkan - /// - Metal - /// - OpenGL (emulated with uniforms) - /// - /// This is a native only feature. - /// - /// [`RenderPass`]: ../wgpu/struct.RenderPass.html - /// [`PipelineLayoutDescriptor`]: ../wgpu/struct.PipelineLayoutDescriptor.html - /// [`RenderPass::set_push_constants`]: ../wgpu/struct.RenderPass.html#method.set_push_constants - /// [`Limits::max_push_constant_size`]: super::Limits - const PUSH_CONSTANTS = 1 << 16; /// Allows the use of [`AddressMode::ClampToBorder`] with a border color /// of [`SamplerBorderColor::Zero`]. /// @@ -1169,13 +1144,21 @@ bitflags_array! { const UNIFORM_BUFFER_BINDING_ARRAYS = 1 << 47; /// Enables mesh shaders and task shaders in mesh shader pipelines. This extension does NOT imply support for - /// compiling mesh shaders at runtime. Rather, the user must use custom passthrough shaders. + /// compiling mesh shaders at runtime. /// /// Supported platforms: /// - Vulkan (with [VK_EXT_mesh_shader](https://registry.khronos.org/vulkan/specs/latest/man/html/VK_EXT_mesh_shader.html)) /// - DX12 /// - Metal /// + /// Naga is only supported on vulkan. On other platforms you will have to use passthrough shaders. + /// + /// Some Mesa drivers including LLVMPIPE but not RADV fail to run the naga generated code. + /// [This may be our bug and will be investigated.](https://github.com/gfx-rs/wgpu/issues/8727) + /// However, due to the nature of the failure, the fact that it is unique, and the random changes + /// that make it go away, this is believed to be a Mesa bug. See + /// [this Mesa issue.](https://gitlab.freedesktop.org/mesa/mesa/-/issues/14376) + /// /// This is a native only feature. const EXPERIMENTAL_MESH_SHADER = 1 << 48; @@ -1263,6 +1246,15 @@ bitflags_array! { /// /// This is a native only feature. const EXPERIMENTAL_MESH_SHADER_POINTS = 1 << 55; + + /// Enables creating texture arrays that are also multisampled. + /// + /// Without this feature, you cannot create a texture that has both a `sample_count` higher + /// than 1, and a `depth_or_array_layers` higher than 1. + /// + /// Supported platforms: + /// - Vulkan (except VK_KHR_portability_subset if multisampleArrayImage is not available) + const MULTISAMPLE_ARRAY = 1 << 56; } /// Features that are not guaranteed to be supported. @@ -1440,7 +1432,7 @@ bitflags_array! { const INDIRECT_FIRST_INSTANCE = WEBGPU_FEATURE_INDIRECT_FIRST_INSTANCE; /// Allows shaders to use 16-bit floating point types. You may use them uniform buffers, - /// storage buffers, and local variables. You may not use them in push constants. + /// storage buffers, and local variables. You may not use them in immediates. /// /// In order to use this in WGSL shaders, you must add `enable f16;` to the top of your shader, /// before any global items. @@ -1512,6 +1504,37 @@ bitflags_array! { /// /// This is a web and native feature. const CLIP_DISTANCES = WEBGPU_FEATURE_CLIP_DISTANCES; + + /// Allows the use of immediate data: small, fast bits of memory that can be updated + /// inside a [`RenderPass`]. + /// + /// Allows the user to call [`RenderPass::set_immediates`], provide a non-zero immediate data size + /// to [`PipelineLayoutDescriptor`], and provide a non-zero limit to [`Limits::max_immediate_size`]. + /// + /// A block of immediate data can be declared in WGSL with `var<immediate>`: + /// + /// ```rust,ignore + /// struct Immediates { example: f32, } + /// var<immediate> c: Immediates; + /// ``` + /// + /// In GLSL, this corresponds to `layout(immediates) uniform Name {..}`. + /// + /// Supported platforms: + /// - DX12 + /// - Vulkan + /// - Metal + /// - OpenGL (emulated with uniforms) + /// + /// WebGPU support is currently a proposal and will be available in browsers in the future. + /// + /// This is a web and native feature. + /// + #[doc = link_to_wgpu_item!(struct RenderPass)] + #[doc = link_to_wgpu_item!(struct PipelineLayoutDescriptor)] + #[doc = link_to_wgpu_docs!(["`RenderPass::set_immediates`"]: "struct.RenderPass.html#method.set_immediates")] + /// [`Limits::max_immediate_size`]: super::Limits + const IMMEDIATES = WEBGPU_FEATURE_IMMEDIATES; } } diff --git a/third_party/rust/wgpu-types/src/instance.rs b/third_party/rust/wgpu-types/src/instance.rs @@ -1,8 +1,6 @@ -//! Types for dealing with Instances +//! Types for dealing with Instances. -use alloc::string::String; - -use crate::Backends; +use crate::{link_to_wgpu_docs, Backends}; #[cfg(doc)] use crate::{Backend, DownlevelFlags}; @@ -36,7 +34,7 @@ pub struct InstanceDescriptor { /// Memory budget thresholds used by some backends. pub memory_budget_thresholds: MemoryBudgetThresholds, /// Options the control the behavior of specific backends. - pub backend_options: BackendOptions, + pub backend_options: crate::BackendOptions, } impl InstanceDescriptor { @@ -165,7 +163,7 @@ bitflags::bitflags! { /// can be a hassle to do manually. When this is enabled, the timestamp period returned by the queue /// will always be `1.0`. /// - /// [rqs]: ../wgpu/struct.CommandEncoder.html#method.resolve_query_set + #[doc = link_to_wgpu_docs!(["rqs"]: "struct.CommandEncoder.html#method.resolve_query_set")] const AUTOMATIC_TIMESTAMP_NORMALIZATION = 1 << 6; } } @@ -274,472 +272,3 @@ pub struct MemoryBudgetThresholds { /// If not specified, devices might still become lost due to memory pressure. pub for_device_loss: Option<u8>, } - -/// Options that are passed to a given backend. -/// -/// Part of [`InstanceDescriptor`]. -#[derive(Clone, Debug, Default)] -pub struct BackendOptions { - /// Options for the OpenGL/OpenGLES backend, [`Backend::Gl`]. - pub gl: GlBackendOptions, - /// Options for the DX12 backend, [`Backend::Dx12`]. - pub dx12: Dx12BackendOptions, - /// Options for the noop backend, [`Backend::Noop`]. - pub noop: NoopBackendOptions, -} - -impl BackendOptions { - /// Choose backend options by calling `from_env` on every field. - /// - /// See those methods for more information. - #[must_use] - pub fn from_env_or_default() -> Self { - Self { - gl: GlBackendOptions::from_env_or_default(), - dx12: Dx12BackendOptions::from_env_or_default(), - noop: NoopBackendOptions::from_env_or_default(), - } - } - - /// Takes the given options, modifies them based on the environment variables, and returns the result. - /// - /// This is equivalent to calling `with_env` on every field. - #[must_use] - pub fn with_env(self) -> Self { - Self { - gl: self.gl.with_env(), - dx12: self.dx12.with_env(), - noop: self.noop.with_env(), - } - } -} - -/// Configuration for the OpenGL/OpenGLES backend. -/// -/// Part of [`BackendOptions`]. -#[derive(Clone, Debug, Default)] -pub struct GlBackendOptions { - /// Which OpenGL ES 3 minor version to request, if using OpenGL ES. - pub gles_minor_version: Gles3MinorVersion, - /// Behavior of OpenGL fences. Affects how `on_completed_work_done` and `device.poll` behave. - pub fence_behavior: GlFenceBehavior, -} - -impl GlBackendOptions { - /// Choose OpenGL backend options by calling `from_env` on every field. - /// - /// See those methods for more information. - #[must_use] - pub fn from_env_or_default() -> Self { - let gles_minor_version = Gles3MinorVersion::from_env().unwrap_or_default(); - Self { - gles_minor_version, - fence_behavior: GlFenceBehavior::Normal, - } - } - - /// Takes the given options, modifies them based on the environment variables, and returns the result. - /// - /// This is equivalent to calling `with_env` on every field. - #[must_use] - pub fn with_env(self) -> Self { - let gles_minor_version = self.gles_minor_version.with_env(); - let short_circuit_fences = self.fence_behavior.with_env(); - Self { - gles_minor_version, - fence_behavior: short_circuit_fences, - } - } -} - -/// Configuration for the DX12 backend. -/// -/// Part of [`BackendOptions`]. -#[derive(Clone, Debug, Default)] -pub struct Dx12BackendOptions { - /// Which DX12 shader compiler to use. - pub shader_compiler: Dx12Compiler, - /// Presentation system to use. - pub presentation_system: Dx12SwapchainKind, - /// Whether to wait for the latency waitable object before acquiring the next swapchain image. - pub latency_waitable_object: Dx12UseFrameLatencyWaitableObject, -} - -impl Dx12BackendOptions { - /// Choose DX12 backend options by calling `from_env` on every field. - /// - /// See those methods for more information. - #[must_use] - pub fn from_env_or_default() -> Self { - let compiler = Dx12Compiler::from_env().unwrap_or_default(); - let presentation_system = Dx12SwapchainKind::from_env().unwrap_or_default(); - let latency_waitable_object = - Dx12UseFrameLatencyWaitableObject::from_env().unwrap_or_default(); - Self { - shader_compiler: compiler, - presentation_system, - latency_waitable_object, - } - } - - /// Takes the given options, modifies them based on the environment variables, and returns the result. - /// - /// This is equivalent to calling `with_env` on every field. - #[must_use] - pub fn with_env(self) -> Self { - let shader_compiler = self.shader_compiler.with_env(); - let presentation_system = self.presentation_system.with_env(); - let latency_waitable_object = self.latency_waitable_object.with_env(); - Self { - shader_compiler, - presentation_system, - latency_waitable_object, - } - } -} - -/// Configuration for the noop backend. -/// -/// Part of [`BackendOptions`]. -#[derive(Clone, Debug, Default)] -pub struct NoopBackendOptions { - /// Whether to allow the noop backend to be used. - /// - /// The noop backend stubs out all operations except for buffer creation and mapping, so - /// it must not be used when not expected. Therefore, it will not be used unless explicitly - /// enabled. - pub enable: bool, -} - -impl NoopBackendOptions { - /// Choose whether the noop backend is enabled from the environment. - /// - /// It will be enabled if the environment variable `WGPU_NOOP_BACKEND` has the value `1` - /// and not otherwise. Future versions may assign other meanings to other values. - #[must_use] - pub fn from_env_or_default() -> Self { - Self { - enable: Self::enable_from_env().unwrap_or(false), - } - } - - /// Takes the given options, modifies them based on the environment variables, and returns the - /// result. - /// - /// See [`from_env_or_default()`](Self::from_env_or_default) for the interpretation. - #[must_use] - pub fn with_env(self) -> Self { - Self { - enable: Self::enable_from_env().unwrap_or(self.enable), - } - } - - fn enable_from_env() -> Option<bool> { - let value = crate::env::var("WGPU_NOOP_BACKEND")?; - match value.as_str() { - "1" => Some(true), - "0" => Some(false), - _ => None, - } - } -} - -#[derive(Clone, Debug, Default, Copy, PartialEq, Eq)] -/// Selects which kind of swapchain to use on DX12. -pub enum Dx12SwapchainKind { - /// Use a DXGI swapchain made directly from the window's HWND. - /// - /// This does not support transparency but has better support from developer tooling from RenderDoc. - #[default] - DxgiFromHwnd, - /// Use a DXGI swapchain made from a DirectComposition visual made automatically from the window's HWND. - /// - /// This creates a single [`IDCompositionVisual`] over the entire window that is used by the `Surface`. - /// If a user wants to manage the composition tree themselves, they should create their own device and - /// composition, and pass the relevant visual down via [`SurfaceTargetUnsafe::CompositionVisual`][CV]. - /// - /// This supports transparent windows, but does not have support from RenderDoc. - /// - /// [`IDCompositionVisual`]: https://learn.microsoft.com/en-us/windows/win32/api/dcomp/nn-dcomp-idcompositionvisual - /// [CV]: ../wgpu/struct.SurfaceTargetUnsafe.html#variant.CompositionVisual - DxgiFromVisual, -} - -impl Dx12SwapchainKind { - /// Choose which presentation system to use from the environment variable `WGPU_DX12_PRESENTATION_SYSTEM`. - /// - /// Valid values, case insensitive: - /// - `DxgiFromVisual` or `Visual` - /// - `DxgiFromHwnd` or `Hwnd` for [`Self::DxgiFromHwnd`] - #[must_use] - pub fn from_env() -> Option<Self> { - let value = crate::env::var("WGPU_DX12_PRESENTATION_SYSTEM") - .as_deref()? - .to_lowercase(); - match value.as_str() { - "dxgifromvisual" | "visual" => Some(Self::DxgiFromVisual), - "dxgifromhwnd" | "hwnd" => Some(Self::DxgiFromHwnd), - _ => None, - } - } - - /// Takes the given presentation system, modifies it based on the `WGPU_DX12_PRESENTATION_SYSTEM` environment variable, and returns the result. - /// - /// See [`from_env`](Self::from_env) for more information. - #[must_use] - pub fn with_env(self) -> Self { - if let Some(presentation_system) = Self::from_env() { - presentation_system - } else { - self - } - } -} - -/// DXC shader model. -#[derive(Clone, Debug)] -#[allow(missing_docs)] -pub enum DxcShaderModel { - V6_0, - V6_1, - V6_2, - V6_3, - V6_4, - V6_5, - V6_6, - V6_7, -} - -/// Selects which DX12 shader compiler to use. -#[derive(Clone, Debug, Default)] -pub enum Dx12Compiler { - /// The Fxc compiler (default) is old, slow and unmaintained. - /// - /// However, it doesn't require any additional .dlls to be shipped with the application. - #[default] - Fxc, - /// The Dxc compiler is new, fast and maintained. - /// - /// However, it requires `dxcompiler.dll` to be shipped with the application. - /// These files can be downloaded from <https://github.com/microsoft/DirectXShaderCompiler/releases>. - /// - /// Minimum supported version: [v1.8.2502](https://github.com/microsoft/DirectXShaderCompiler/releases/tag/v1.8.2502) - /// - /// It also requires WDDM 2.1 (Windows 10 version 1607). - DynamicDxc { - /// Path to `dxcompiler.dll`. - dxc_path: String, - /// Maximum shader model the given dll supports. - max_shader_model: DxcShaderModel, - }, - /// The statically-linked variant of Dxc. - /// - /// The `static-dxc` feature is required for this setting to be used successfully on DX12. - /// Not available on `windows-aarch64-pc-*` targets. - StaticDxc, -} - -impl Dx12Compiler { - /// Helper function to construct a `DynamicDxc` variant with default paths. - /// - /// The dll must support at least shader model 6.8. - pub fn default_dynamic_dxc() -> Self { - Self::DynamicDxc { - dxc_path: String::from("dxcompiler.dll"), - max_shader_model: DxcShaderModel::V6_7, // should be 6.8 but the variant is missing - } - } - - /// Choose which DX12 shader compiler to use from the environment variable `WGPU_DX12_COMPILER`. - /// - /// Valid values, case insensitive: - /// - `Fxc` - /// - `Dxc` or `DynamicDxc` - /// - `StaticDxc` - #[must_use] - pub fn from_env() -> Option<Self> { - let value = crate::env::var("WGPU_DX12_COMPILER") - .as_deref()? - .to_lowercase(); - match value.as_str() { - "dxc" | "dynamicdxc" => Some(Self::default_dynamic_dxc()), - "staticdxc" => Some(Self::StaticDxc), - "fxc" => Some(Self::Fxc), - _ => None, - } - } - - /// Takes the given compiler, modifies it based on the `WGPU_DX12_COMPILER` environment variable, and returns the result. - /// - /// See `from_env` for more information. - #[must_use] - pub fn with_env(self) -> Self { - if let Some(compiler) = Self::from_env() { - compiler - } else { - self - } - } -} - -/// Whether and how to use a waitable handle obtained from `GetFrameLatencyWaitableObject`. -#[derive(Clone, Debug, Default)] -pub enum Dx12UseFrameLatencyWaitableObject { - /// Do not obtain a waitable handle and do not wait for it. The swapchain will - /// be created without the `DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT` flag. - None, - /// Obtain a waitable handle and wait for it before acquiring the next swapchain image. - #[default] - Wait, - /// Create the swapchain with the `DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT` flag and - /// obtain a waitable handle, but do not wait for it before acquiring the next swapchain image. - /// This is useful if the application wants to wait for the waitable object itself. - DontWait, -} - -impl Dx12UseFrameLatencyWaitableObject { - /// Choose whether to use a frame latency waitable object from the environment variable `WGPU_DX12_USE_FRAME_LATENCY_WAITABLE_OBJECT`. - /// - /// Valid values, case insensitive: - /// - `None` - /// - `Wait` - /// - `DontWait` - #[must_use] - pub fn from_env() -> Option<Self> { - let value = crate::env::var("WGPU_DX12_USE_FRAME_LATENCY_WAITABLE_OBJECT") - .as_deref()? - .to_lowercase(); - match value.as_str() { - "none" => Some(Self::None), - "wait" => Some(Self::Wait), - "dontwait" => Some(Self::DontWait), - _ => None, - } - } - - /// Takes the given setting, modifies it based on the `WGPU_DX12_USE_FRAME_LATENCY_WAITABLE_OBJECT` environment variable, and returns the result. - /// - /// See `from_env` for more information. - #[must_use] - pub fn with_env(self) -> Self { - if let Some(compiler) = Self::from_env() { - compiler - } else { - self - } - } -} - -/// Selects which OpenGL ES 3 minor version to request. -/// -/// When using ANGLE as an OpenGL ES/EGL implementation, explicitly requesting `Version1` can provide a non-conformant ES 3.1 on APIs like D3D11. -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash)] -pub enum Gles3MinorVersion { - /// No explicit minor version is requested, the driver automatically picks the highest available. - #[default] - Automatic, - - /// Request an ES 3.0 context. - Version0, - - /// Request an ES 3.1 context. - Version1, - - /// Request an ES 3.2 context. - Version2, -} - -impl Gles3MinorVersion { - /// Choose which minor OpenGL ES version to use from the environment variable `WGPU_GLES_MINOR_VERSION`. - /// - /// Possible values are `0`, `1`, `2` or `automatic`. Case insensitive. - /// - /// Use with `unwrap_or_default()` to get the default value if the environment variable is not set. - #[must_use] - pub fn from_env() -> Option<Self> { - let value = crate::env::var("WGPU_GLES_MINOR_VERSION") - .as_deref()? - .to_lowercase(); - match value.as_str() { - "automatic" => Some(Self::Automatic), - "0" => Some(Self::Version0), - "1" => Some(Self::Version1), - "2" => Some(Self::Version2), - _ => None, - } - } - - /// Takes the given compiler, modifies it based on the `WGPU_GLES_MINOR_VERSION` environment variable, and returns the result. - /// - /// See `from_env` for more information. - #[must_use] - pub fn with_env(self) -> Self { - if let Some(compiler) = Self::from_env() { - compiler - } else { - self - } - } -} - -/// Dictate the behavior of fences in OpenGL. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] -pub enum GlFenceBehavior { - /// Fences in OpenGL behave normally. If you don't know what to pick, this is what you want. - #[default] - Normal, - /// Fences in OpenGL are short-circuited to always return `true` immediately. - /// - /// This solves a very specific issue that arose due to a bug in wgpu-core that made - /// many WebGL programs work when they "shouldn't" have. If you have code that is trying - /// to call `device.poll(wgpu::PollType::Wait)` on WebGL, you need to enable this option - /// for the "Wait" to behave how you would expect. - /// - /// Previously all `poll(Wait)` acted like the OpenGL fences were signalled even if they weren't. - /// See <https://github.com/gfx-rs/wgpu/issues/4589> for more information. - /// - /// When this is set `Queue::on_completed_work_done` will always return the next time the device - /// is maintained, not when the work is actually done on the GPU. - AutoFinish, -} - -impl GlFenceBehavior { - /// Returns true if the fence behavior is `AutoFinish`. - pub fn is_auto_finish(&self) -> bool { - matches!(self, Self::AutoFinish) - } - - /// Returns true if the fence behavior is `Normal`. - pub fn is_normal(&self) -> bool { - matches!(self, Self::Normal) - } - - /// Choose which minor OpenGL ES version to use from the environment variable `WGPU_GL_FENCE_BEHAVIOR`. - /// - /// Possible values are `Normal` or `AutoFinish`. Case insensitive. - /// - /// Use with `unwrap_or_default()` to get the default value if the environment variable is not set. - #[must_use] - pub fn from_env() -> Option<Self> { - let value = crate::env::var("WGPU_GL_FENCE_BEHAVIOR") - .as_deref()? - .to_lowercase(); - match value.as_str() { - "normal" => Some(Self::Normal), - "autofinish" => Some(Self::AutoFinish), - _ => None, - } - } - - /// Takes the given compiler, modifies it based on the `WGPU_GL_FENCE_BEHAVIOR` environment variable, and returns the result. - /// - /// See `from_env` for more information. - #[must_use] - pub fn with_env(self) -> Self { - if let Some(fence) = Self::from_env() { - fence - } else { - self - } - } -} diff --git a/third_party/rust/wgpu-types/src/lib.rs b/third_party/rust/wgpu-types/src/lib.rs @@ -6,7 +6,12 @@ // We don't use syntax sugar where it's not necessary. clippy::match_like_matches_macro, )] -#![warn(clippy::ptr_as_ptr, missing_docs, unsafe_op_in_unsafe_fn)] +#![warn( + clippy::ptr_as_ptr, + missing_docs, + unsafe_op_in_unsafe_fn, + unused_qualifications +)] #![no_std] #[cfg(feature = "std")] @@ -14,46 +19,120 @@ extern crate std; extern crate alloc; -use alloc::borrow::Cow; -use alloc::{string::String, vec, vec::Vec}; -use core::{ - cmp::Ordering, - fmt, - hash::{Hash, Hasher}, - mem, - num::NonZeroU32, - ops::Range, - time::Duration, -}; - -use bytemuck::{Pod, Zeroable}; +use core::{fmt, hash::Hash, time::Duration}; #[cfg(any(feature = "serde", test))] -use { - alloc::format, - serde::{Deserialize, Serialize}, -}; +use serde::{Deserialize, Serialize}; +mod adapter; pub mod assertions; +mod backend; +mod binding; +mod buffer; mod cast_utils; mod counters; +mod device; mod env; pub mod error; mod features; pub mod instance; +mod limits; pub mod math; +mod origin_extent; +mod ray_tracing; +mod render; +#[doc(hidden)] // without this we get spurious missing_docs warnings +mod send_sync; +mod shader; +mod surface; +mod texture; mod tokens; mod transfers; +mod vertex; +pub use adapter::*; +pub use backend::*; +pub use binding::*; +pub use buffer::*; pub use counters::*; +pub use device::*; pub use features::*; pub use instance::*; +pub use limits::*; +pub use origin_extent::*; +pub use ray_tracing::*; +pub use render::*; +#[doc(hidden)] +pub use send_sync::*; +pub use shader::*; +pub use surface::*; +pub use texture::*; pub use tokens::*; pub use transfers::*; +pub use vertex::*; + +/// Create a Markdown link definition referring to the `wgpu` crate. +/// +/// This macro should be used inside a `#[doc = ...]` attribute. +/// The two arguments should be string literals or macros that expand to string literals. +/// If the module in which the item using this macro is located is not the crate root, +/// use the `../` syntax. +/// +/// We cannot simply use rustdoc links to `wgpu` because it is one of our dependents. +/// This link adapts to work in locally generated documentation (`cargo doc`) by default, +/// and work with `docs.rs` URL structure when building for `docs.rs`. +/// +/// Note: This macro cannot be used outside this crate, because `cfg(docsrs)` will not apply. +#[cfg(not(docsrs))] +macro_rules! link_to_wgpu_docs { + ([$reference:expr]: $url_path:expr) => { + concat!("[", $reference, "]: ../wgpu/", $url_path) + }; + + (../ [$reference:expr]: $url_path:expr) => { + concat!("[", $reference, "]: ../../wgpu/", $url_path) + }; +} +#[cfg(docsrs)] +macro_rules! link_to_wgpu_docs { + ($(../)? [$reference:expr]: $url_path:expr) => { + concat!( + "[", + $reference, + // URL path will have a base URL of https://docs.rs/ + "]: /wgpu/", + // The version of wgpu-types is not necessarily the same as the version of wgpu + // if a patch release of either has been published, so we cannot use the full version + // number. docs.rs will interpret this single number as a Cargo-style version + // requirement and redirect to the latest compatible version. + // + // This technique would break if `wgpu` and `wgpu-types` ever switch to having distinct + // major version numbering. An alternative would be to hardcode the corresponding `wgpu` + // version, but that would give us another thing to forget to update. + env!("CARGO_PKG_VERSION_MAJOR"), + "/wgpu/", + $url_path + ) + }; +} + +/// Create a Markdown link definition referring to an item in the `wgpu` crate. +/// +/// This macro should be used inside a `#[doc = ...]` attribute. +/// See [`link_to_wgpu_docs`] for more details. +macro_rules! link_to_wgpu_item { + ($kind:ident $name:ident) => { + $crate::link_to_wgpu_docs!( + [concat!("`", stringify!($name), "`")]: concat!("$kind.", stringify!($name), ".html") + ) + }; +} + +pub(crate) use {link_to_wgpu_docs, link_to_wgpu_item}; /// Integral type used for [`Buffer`] offsets and sizes. /// -/// [`Buffer`]: ../wgpu/struct.Buffer.html +#[doc = link_to_wgpu_item!(struct Buffer)] pub type BufferAddress = u64; /// Integral type used for [`BufferSlice`] sizes. @@ -61,15 +140,15 @@ pub type BufferAddress = u64; /// Note that while this type is non-zero, a [`Buffer`] *per se* can have a size of zero, /// but no slice or mapping can be created from it. /// -/// [`Buffer`]: ../wgpu/struct.Buffer.html -/// [`BufferSlice`]: ../wgpu/struct.BufferSlice.html +#[doc = link_to_wgpu_item!(struct Buffer)] +#[doc = link_to_wgpu_item!(struct BufferSlice)] pub type BufferSize = core::num::NonZeroU64; /// Integral type used for binding locations in shaders. /// /// Used in [`VertexAttribute`]s and errors. /// -/// [`VertexAttribute`]: ../wgpu/struct.VertexAttribute.html +#[doc = link_to_wgpu_item!(struct VertexAttribute)] pub type ShaderLocation = u32; /// Integral type used for @@ -82,14 +161,14 @@ pub type DynamicOffset = u32; /// and [`copy_texture_to_buffer()`]. /// /// [`bytes_per_row`]: TexelCopyBufferLayout::bytes_per_row -/// [`copy_buffer_to_texture()`]: ../wgpu/struct.Queue.html#method.copy_buffer_to_texture -/// [`copy_texture_to_buffer()`]: ../wgpu/struct.Queue.html#method.copy_texture_to_buffer -/// [Qwt]: ../wgpu/struct.Queue.html#method.write_texture +#[doc = link_to_wgpu_docs!(["`copy_buffer_to_texture()`"]: "struct.Queue.html#method.copy_buffer_to_texture")] +#[doc = link_to_wgpu_docs!(["`copy_texture_to_buffer()`"]: "struct.Queue.html#method.copy_texture_to_buffer")] +#[doc = link_to_wgpu_docs!(["Qwt"]: "struct.Queue.html#method.write_texture")] pub const COPY_BYTES_PER_ROW_ALIGNMENT: u32 = 256; /// An [offset into the query resolve buffer] has to be aligned to this. /// -/// [offset into the query resolve buffer]: ../wgpu/struct.CommandEncoder.html#method.resolve_query_set +#[doc = link_to_wgpu_docs!(["offset into the query resolve buffer"]: "struct.CommandEncoder.html#method.resolve_query_set")] pub const QUERY_RESOLVE_BUFFER_ALIGNMENT: BufferAddress = 256; /// Buffer to buffer copy as well as buffer clear offsets and sizes must be aligned to this number. @@ -99,7684 +178,284 @@ pub const COPY_BUFFER_ALIGNMENT: BufferAddress = 4; /// /// The range passed to [`map_async()`] or [`get_mapped_range()`] must be at least this aligned. /// -/// [`map_async()`]: ../wgpu/struct.Buffer.html#method.map_async -/// [`get_mapped_range()`]: ../wgpu/struct.Buffer.html#method.get_mapped_range +#[doc = link_to_wgpu_docs!(["`map_async()`"]: "struct.Buffer.html#method.map_async")] +#[doc = link_to_wgpu_docs!(["`get_mapped_range()`"]: "struct.Buffer.html#method.get_mapped_range")] pub const MAP_ALIGNMENT: BufferAddress = 8; /// [Vertex buffer offsets] and [strides] have to be a multiple of this number. /// -/// [Vertex buffer offsets]: ../wgpu/util/trait.RenderEncoder.html#tymethod.set_vertex_buffer -/// [strides]: ../wgpu/struct.VertexBufferLayout.html#structfield.array_stride +#[doc = link_to_wgpu_docs!(["Vertex buffer offsets"]: "util/trait.RenderEncoder.html#tymethod.set_vertex_buffer")] +#[doc = link_to_wgpu_docs!(["strides"]: "struct.VertexBufferLayout.html#structfield.array_stride")] pub const VERTEX_ALIGNMENT: BufferAddress = 4; /// [Vertex buffer strides] have to be a multiple of this number. /// -/// [Vertex buffer strides]: ../wgpu/struct.VertexBufferLayout.html#structfield.array_stride +#[doc = link_to_wgpu_docs!(["Vertex buffer strides"]: "struct.VertexBufferLayout.html#structfield.array_stride")] #[deprecated(note = "Use `VERTEX_ALIGNMENT` instead", since = "27.0.0")] pub const VERTEX_STRIDE_ALIGNMENT: BufferAddress = 4; -/// Ranges of [writes to push constant storage] must be at least this aligned. +/// Ranges of [writes to immediate data] must be at least this aligned. /// -/// [writes to push constant storage]: ../wgpu/struct.RenderPass.html#method.set_push_constants -pub const PUSH_CONSTANT_ALIGNMENT: u32 = 4; +#[doc = link_to_wgpu_docs!(["writes to immediate data"]: "struct.RenderPass.html#method.set_immediates")] +pub const IMMEDIATE_DATA_ALIGNMENT: u32 = 4; + +/// Storage buffer binding sizes must be multiples of this value. +#[doc(hidden)] +pub const STORAGE_BINDING_SIZE_ALIGNMENT: u32 = 4; /// Maximum queries in a [`QuerySetDescriptor`]. pub const QUERY_SET_MAX_QUERIES: u32 = 4096; /// Size in bytes of a single piece of [query] data. /// -/// [query]: ../wgpu/struct.QuerySet.html +#[doc = link_to_wgpu_docs!(["query"]: "struct.QuerySet.html")] pub const QUERY_SIZE: u32 = 8; -/// Backends supported by wgpu. +/// The minimum allowed value for [`AdapterInfo::subgroup_min_size`]. /// -/// See also [`Backends`]. -#[repr(u8)] -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum Backend { - /// Dummy backend, which may be used for testing. - /// - /// It performs no rendering or computation, but allows creation of stub GPU resource types, - /// so that code which manages GPU resources can be tested without an available GPU. - /// Specifically, the following operations are implemented: - /// - /// * Enumerating adapters will always return one noop adapter, which can be used to create - /// devices. - /// * Buffers may be created, written, mapped, and copied to other buffers. - /// * Command encoders may be created, but only buffer operations are useful. - /// - /// Other resources can be created but are nonfunctional; notably, - /// - /// * Render passes and compute passes are not executed. - /// * Textures may be created, but do not store any texels. - /// * There are no compatible surfaces. - /// - /// An adapter using the noop backend can only be obtained if [`NoopBackendOptions`] - /// enables it, in addition to the ordinary requirement of [`Backends::NOOP`] being set. - /// This ensures that applications not desiring a non-functional backend will not receive it. - Noop = 0, - /// Vulkan API (Windows, Linux, Android, MacOS via `vulkan-portability`/MoltenVK) - Vulkan = 1, - /// Metal API (Apple platforms) - Metal = 2, - /// Direct3D-12 (Windows) - Dx12 = 3, - /// OpenGL 3.3+ (Windows), OpenGL ES 3.0+ (Linux, Android, MacOS via Angle), and WebGL2 - Gl = 4, - /// WebGPU in the browser - BrowserWebGpu = 5, -} - -impl Backend { - /// Array of all [`Backend`] values, corresponding to [`Backends::all()`]. - pub const ALL: [Backend; Backends::all().bits().count_ones() as usize] = [ - Self::Noop, - Self::Vulkan, - Self::Metal, - Self::Dx12, - Self::Gl, - Self::BrowserWebGpu, - ]; - - /// Returns the string name of the backend. - #[must_use] - pub const fn to_str(self) -> &'static str { - match self { - Backend::Noop => "noop", - Backend::Vulkan => "vulkan", - Backend::Metal => "metal", - Backend::Dx12 => "dx12", - Backend::Gl => "gl", - Backend::BrowserWebGpu => "webgpu", - } - } -} - -impl core::fmt::Display for Backend { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.write_str(self.to_str()) - } -} - -/// Power Preference when choosing a physical adapter. +/// See <https://gpuweb.github.io/gpuweb/#gpuadapterinfo> +/// where you can always use these values on all devices +pub const MINIMUM_SUBGROUP_MIN_SIZE: u32 = 4; +/// The maximum allowed value for [`AdapterInfo::subgroup_max_size`]. /// -/// Corresponds to [WebGPU `GPUPowerPreference`]( -/// https://gpuweb.github.io/gpuweb/#enumdef-gpupowerpreference). -#[repr(C)] -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] -pub enum PowerPreference { - #[default] - /// Power usage is not considered when choosing an adapter. - None = 0, - /// Adapter that uses the least possible power. This is often an integrated GPU. - LowPower = 1, - /// Adapter that has the highest performance. This is often a discrete GPU. - HighPerformance = 2, -} - -impl PowerPreference { - /// Get a power preference from the environment variable `WGPU_POWER_PREF`. - pub fn from_env() -> Option<Self> { - let env = crate::env::var("WGPU_POWER_PREF")?; - match env.to_lowercase().as_str() { - "low" => Some(Self::LowPower), - "high" => Some(Self::HighPerformance), - "none" => Some(Self::None), - _ => None, - } - } -} - -bitflags::bitflags! { - /// Represents the backends that wgpu will use. - #[repr(transparent)] - #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] - #[cfg_attr(feature = "serde", serde(transparent))] - #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] - pub struct Backends: u32 { - /// [`Backend::Noop`]. - const NOOP = 1 << Backend::Noop as u32; +/// See <https://gpuweb.github.io/gpuweb/#gpuadapterinfo> +/// where you can always use these values on all devices. +pub const MAXIMUM_SUBGROUP_MAX_SIZE: u32 = 128; - /// [`Backend::Vulkan`]. - /// Supported on Windows, Linux/Android, and macOS/iOS via Vulkan Portability (with the Vulkan feature enabled) - const VULKAN = 1 << Backend::Vulkan as u32; - - /// [`Backend::Gl`]. - /// Supported on Linux/Android, the web through webassembly via WebGL, and Windows and - /// macOS/iOS via ANGLE - const GL = 1 << Backend::Gl as u32; - - /// [`Backend::Metal`]. - /// Supported on macOS and iOS. - const METAL = 1 << Backend::Metal as u32; - - /// [`Backend::Dx12`]. - /// Supported on Windows 10 and later - const DX12 = 1 << Backend::Dx12 as u32; - - /// [`Backend::BrowserWebGpu`]. - /// Supported when targeting the web through WebAssembly with the `webgpu` feature enabled. - /// - /// The WebGPU backend is special in several ways: - /// It is not not implemented by `wgpu_core` and instead by the higher level `wgpu` crate. - /// Whether WebGPU is targeted is decided upon the creation of the `wgpu::Instance`, - /// *not* upon adapter creation. See `wgpu::Instance::new`. - const BROWSER_WEBGPU = 1 << Backend::BrowserWebGpu as u32; - - /// All the apis that wgpu offers first tier of support for. +/// Passed to `Device::poll` to control how and if it should block. +#[derive(Clone, Debug)] +pub enum PollType<T> { + /// On wgpu-core based backends, block until the given submission has + /// completed execution, and any callbacks have been invoked. + /// + /// On WebGPU, this has no effect. Callbacks are invoked from the + /// window event loop. + Wait { + /// Submission index to wait for. /// - /// * [`Backends::VULKAN`] - /// * [`Backends::METAL`] - /// * [`Backends::DX12`] - /// * [`Backends::BROWSER_WEBGPU`] - const PRIMARY = Self::VULKAN.bits() - | Self::METAL.bits() - | Self::DX12.bits() - | Self::BROWSER_WEBGPU.bits(); + /// If not specified, will wait for the most recent submission at the time of the poll. + /// By the time the method returns, more submissions may have taken place. + submission_index: Option<T>, - /// All the apis that wgpu offers second tier of support for. These may - /// be unsupported/still experimental. + /// Max time to wait for the submission to complete. /// - /// * [`Backends::GL`] - const SECONDARY = Self::GL.bits(); - } -} - -impl Default for Backends { - fn default() -> Self { - Self::all() - } -} + /// If not specified, will wait indefinitely (or until an error is detected). + /// If waiting for the GPU device takes this long or longer, the poll will return [`PollError::Timeout`]. + timeout: Option<Duration>, + }, -impl From<Backend> for Backends { - fn from(backend: Backend) -> Self { - Self::from_bits(1 << backend as u32).unwrap() - } + /// Check the device for a single time without blocking. + Poll, } -impl Backends { - /// Gets a set of backends from the environment variable `WGPU_BACKEND`. +impl<T> PollType<T> { + /// Wait indefinitely until for the most recent submission to complete. /// - /// See [`Self::from_comma_list()`] for the format of the string. - pub fn from_env() -> Option<Self> { - let env = crate::env::var("WGPU_BACKEND")?; - Some(Self::from_comma_list(&env)) - } - - /// Takes the given options, modifies them based on the `WGPU_BACKEND` environment variable, and returns the result. - pub fn with_env(&self) -> Self { - if let Some(env) = Self::from_env() { - env - } else { - *self + /// This is a convenience function that creates a [`Self::Wait`] variant with + /// no timeout and no submission index. + #[must_use] + pub const fn wait_indefinitely() -> Self { + Self::Wait { + submission_index: None, + timeout: None, } } - /// Generates a set of backends from a comma separated list of case-insensitive backend names. - /// - /// Whitespace is stripped, so both 'gl, dx12' and 'gl,dx12' are valid. - /// - /// Always returns WEBGPU on wasm over webgpu. - /// - /// Names: - /// - vulkan = "vulkan" or "vk" - /// - dx12 = "dx12" or "d3d12" - /// - metal = "metal" or "mtl" - /// - gles = "opengl" or "gles" or "gl" - /// - webgpu = "webgpu" - pub fn from_comma_list(string: &str) -> Self { - let mut backends = Self::empty(); - for backend in string.to_lowercase().split(',') { - backends |= match backend.trim() { - "vulkan" | "vk" => Self::VULKAN, - "dx12" | "d3d12" => Self::DX12, - "metal" | "mtl" => Self::METAL, - "opengl" | "gles" | "gl" => Self::GL, - "webgpu" => Self::BROWSER_WEBGPU, - "noop" => Self::NOOP, - b => { - log::warn!("unknown backend string '{b}'"); - continue; - } - } - } - - if backends.is_empty() { - log::warn!("no valid backend strings found!"); + /// This `PollType` represents a wait of some kind. + #[must_use] + pub fn is_wait(&self) -> bool { + match *self { + Self::Wait { .. } => true, + Self::Poll => false, } - - backends } -} - -/// Options for requesting adapter. -/// -/// Corresponds to [WebGPU `GPURequestAdapterOptions`]( -/// https://gpuweb.github.io/gpuweb/#dictdef-gpurequestadapteroptions). -#[repr(C)] -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct RequestAdapterOptions<S> { - /// Power preference for the adapter. - pub power_preference: PowerPreference, - /// Indicates that only a fallback adapter can be returned. This is generally a "software" - /// implementation on the system. - pub force_fallback_adapter: bool, - /// Surface that is required to be presentable with the requested adapter. This does not - /// create the surface, only guarantees that the adapter can present to said surface. - /// For WebGL, this is strictly required, as an adapter can not be created without a surface. - pub compatible_surface: Option<S>, -} -impl<S> Default for RequestAdapterOptions<S> { - fn default() -> Self { - Self { - power_preference: PowerPreference::default(), - force_fallback_adapter: false, - compatible_surface: None, + /// Map on the wait index type. + #[must_use] + pub fn map_index<U, F>(self, func: F) -> PollType<U> + where + F: FnOnce(T) -> U, + { + match self { + Self::Wait { + submission_index, + timeout, + } => PollType::Wait { + submission_index: submission_index.map(func), + timeout, + }, + Self::Poll => PollType::Poll, } } } -/// Error when [`Instance::request_adapter()`] fails. -/// -/// This type is not part of the WebGPU standard, where `requestAdapter()` would simply return null. -/// -/// [`Instance::request_adapter()`]: ../wgpu/struct.Instance.html#method.request_adapter -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[non_exhaustive] -pub enum RequestAdapterError { - /// No adapter available via the instance’s backends matched the request’s adapter criteria. - NotFound { - // These fields must be set by wgpu-core and wgpu, but are not intended to be stable API, - // only data for the production of the error message. - #[doc(hidden)] - active_backends: Backends, - #[doc(hidden)] - requested_backends: Backends, - #[doc(hidden)] - supported_backends: Backends, - #[doc(hidden)] - no_fallback_backends: Backends, - #[doc(hidden)] - no_adapter_backends: Backends, - #[doc(hidden)] - incompatible_surface_backends: Backends, - }, - - /// Attempted to obtain adapter specified by environment variable, but the environment variable - /// was not set. - EnvNotSet, +/// Error states after a device poll. +#[derive(Debug)] +pub enum PollError { + /// The requested Wait timed out before the submission was completed. + Timeout, + /// The requested Wait was given a wrong submission index. + WrongSubmissionIndex(u64, u64), } -impl core::error::Error for RequestAdapterError {} -impl fmt::Display for RequestAdapterError { +// This impl could be derived by `thiserror`, but by not doing so, we can reduce the number of +// dependencies this early in the dependency graph, which may improve build parallelism. +impl fmt::Display for PollError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - RequestAdapterError::NotFound { - active_backends, - requested_backends, - supported_backends, - no_fallback_backends, - no_adapter_backends, - incompatible_surface_backends, - } => { - write!(f, "No suitable graphics adapter found; ")?; - let mut first = true; - for backend in Backend::ALL { - let bit = Backends::from(backend); - let comma = if mem::take(&mut first) { "" } else { ", " }; - let explanation = if !requested_backends.contains(bit) { - // We prefer reporting this, because it makes the error most stable with - // respect to what is directly controllable by the caller, as opposed to - // compilation options or the run-time environment. - "not requested" - } else if !supported_backends.contains(bit) { - "support not compiled in" - } else if no_adapter_backends.contains(bit) { - "found no adapters" - } else if incompatible_surface_backends.contains(bit) { - "not compatible with provided surface" - } else if no_fallback_backends.contains(bit) { - "had no fallback adapters" - } else if !active_backends.contains(bit) { - // Backend requested but not active in this instance - if backend == Backend::Noop { - "not explicitly enabled" - } else { - "drivers/libraries could not be loaded" - } - } else { - // This path should be unreachable, but don't crash. - "[unknown reason]" - }; - write!(f, "{comma}{backend} {explanation}")?; - } + PollError::Timeout => { + f.write_str("The requested Wait timed out before the submission was completed.") } - RequestAdapterError::EnvNotSet => f.write_str("WGPU_ADAPTER_NAME not set")?, + PollError::WrongSubmissionIndex(requested, successful) => write!( + f, + "Tried to wait using a submission index ({requested}) \ + that has not been returned by a successful submission \ + (last successful submission: {successful}" + ), } - Ok(()) } } -/// Invoke a macro for each of the limits. -/// -/// The supplied macro should take two arguments. The first is a limit name, as -/// an identifier, typically used to access a member of `struct Limits`. The -/// second is `Ordering::Less` if valid values are less than the limit (the -/// common case), or `Ordering::Greater` if valid values are more than the limit -/// (for limits like alignments, which are minima instead of maxima). -macro_rules! with_limits { - ($macro_name:ident) => { - $macro_name!(max_texture_dimension_1d, Ordering::Less); - $macro_name!(max_texture_dimension_1d, Ordering::Less); - $macro_name!(max_texture_dimension_2d, Ordering::Less); - $macro_name!(max_texture_dimension_3d, Ordering::Less); - $macro_name!(max_texture_array_layers, Ordering::Less); - $macro_name!(max_bind_groups, Ordering::Less); - $macro_name!(max_bindings_per_bind_group, Ordering::Less); - $macro_name!( - max_dynamic_uniform_buffers_per_pipeline_layout, - Ordering::Less - ); - $macro_name!( - max_dynamic_storage_buffers_per_pipeline_layout, - Ordering::Less - ); - $macro_name!(max_sampled_textures_per_shader_stage, Ordering::Less); - $macro_name!(max_samplers_per_shader_stage, Ordering::Less); - $macro_name!(max_storage_buffers_per_shader_stage, Ordering::Less); - $macro_name!(max_storage_textures_per_shader_stage, Ordering::Less); - $macro_name!(max_uniform_buffers_per_shader_stage, Ordering::Less); - $macro_name!(max_binding_array_elements_per_shader_stage, Ordering::Less); - $macro_name!(max_uniform_buffer_binding_size, Ordering::Less); - $macro_name!(max_storage_buffer_binding_size, Ordering::Less); - $macro_name!(max_vertex_buffers, Ordering::Less); - $macro_name!(max_buffer_size, Ordering::Less); - $macro_name!(max_vertex_attributes, Ordering::Less); - $macro_name!(max_vertex_buffer_array_stride, Ordering::Less); - $macro_name!(min_uniform_buffer_offset_alignment, Ordering::Greater); - $macro_name!(min_storage_buffer_offset_alignment, Ordering::Greater); - $macro_name!(max_inter_stage_shader_components, Ordering::Less); - $macro_name!(max_color_attachments, Ordering::Less); - $macro_name!(max_color_attachment_bytes_per_sample, Ordering::Less); - $macro_name!(max_compute_workgroup_storage_size, Ordering::Less); - $macro_name!(max_compute_invocations_per_workgroup, Ordering::Less); - $macro_name!(max_compute_workgroup_size_x, Ordering::Less); - $macro_name!(max_compute_workgroup_size_y, Ordering::Less); - $macro_name!(max_compute_workgroup_size_z, Ordering::Less); - $macro_name!(max_compute_workgroups_per_dimension, Ordering::Less); - - $macro_name!(min_subgroup_size, Ordering::Greater); - $macro_name!(max_subgroup_size, Ordering::Less); - - $macro_name!(max_push_constant_size, Ordering::Less); - $macro_name!(max_non_sampler_bindings, Ordering::Less); - - $macro_name!(max_task_workgroup_total_count, Ordering::Less); - $macro_name!(max_task_workgroups_per_dimension, Ordering::Less); - $macro_name!(max_mesh_multiview_view_count, Ordering::Less); - $macro_name!(max_mesh_output_layers, Ordering::Less); - - $macro_name!(max_blas_primitive_count, Ordering::Less); - $macro_name!(max_blas_geometry_count, Ordering::Less); - $macro_name!(max_tlas_instance_count, Ordering::Less); - - $macro_name!(max_multiview_view_count, Ordering::Less); - }; -} - -/// Represents the sets of limits an adapter/device supports. -/// -/// We provide three different defaults. -/// - [`Limits::downlevel_defaults()`]. This is a set of limits that is guaranteed to work on almost -/// all backends, including "downlevel" backends such as OpenGL and D3D11, other than WebGL. For -/// most applications we recommend using these limits, assuming they are high enough for your -/// application, and you do not intent to support WebGL. -/// - [`Limits::downlevel_webgl2_defaults()`] This is a set of limits that is lower even than the -/// [`downlevel_defaults()`], configured to be low enough to support running in the browser using -/// WebGL2. -/// - [`Limits::default()`]. This is the set of limits that is guaranteed to work on all modern -/// backends and is guaranteed to be supported by WebGPU. Applications needing more modern -/// features can use this as a reasonable set of limits if they are targeting only desktop and -/// modern mobile devices. -/// -/// We recommend starting with the most restrictive limits you can and manually increasing the -/// limits you need boosted. This will let you stay running on all hardware that supports the limits -/// you need. -/// -/// Limits "better" than the default must be supported by the adapter and requested when requesting -/// a device. If limits "better" than the adapter supports are requested, requesting a device will -/// panic. Once a device is requested, you may only use resources up to the limits requested _even_ -/// if the adapter supports "better" limits. -/// -/// Requesting limits that are "better" than you need may cause performance to decrease because the -/// implementation needs to support more than is needed. You should ideally only request exactly -/// what you need. -/// -/// Corresponds to [WebGPU `GPUSupportedLimits`]( -/// https://gpuweb.github.io/gpuweb/#gpusupportedlimits). -/// -/// [`downlevel_defaults()`]: Limits::downlevel_defaults -#[repr(C)] -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase", default))] -pub struct Limits { - /// Maximum allowed value for the `size.width` of a texture created with `TextureDimension::D1`. - /// Defaults to 8192. Higher is "better". - #[cfg_attr(feature = "serde", serde(rename = "maxTextureDimension1D"))] - pub max_texture_dimension_1d: u32, - /// Maximum allowed value for the `size.width` and `size.height` of a texture created with `TextureDimension::D2`. - /// Defaults to 8192. Higher is "better". - #[cfg_attr(feature = "serde", serde(rename = "maxTextureDimension2D"))] - pub max_texture_dimension_2d: u32, - /// Maximum allowed value for the `size.width`, `size.height`, and `size.depth_or_array_layers` - /// of a texture created with `TextureDimension::D3`. - /// Defaults to 2048. Higher is "better". - #[cfg_attr(feature = "serde", serde(rename = "maxTextureDimension3D"))] - pub max_texture_dimension_3d: u32, - /// Maximum allowed value for the `size.depth_or_array_layers` of a texture created with `TextureDimension::D2`. - /// Defaults to 256. Higher is "better". - pub max_texture_array_layers: u32, - /// Amount of bind groups that can be attached to a pipeline at the same time. Defaults to 4. Higher is "better". - pub max_bind_groups: u32, - /// Maximum binding index allowed in `create_bind_group_layout`. Defaults to 1000. Higher is "better". - pub max_bindings_per_bind_group: u32, - /// Amount of uniform buffer bindings that can be dynamic in a single pipeline. Defaults to 8. Higher is "better". - pub max_dynamic_uniform_buffers_per_pipeline_layout: u32, - /// Amount of storage buffer bindings that can be dynamic in a single pipeline. Defaults to 4. Higher is "better". - pub max_dynamic_storage_buffers_per_pipeline_layout: u32, - /// Amount of sampled textures visible in a single shader stage. Defaults to 16. Higher is "better". - pub max_sampled_textures_per_shader_stage: u32, - /// Amount of samplers visible in a single shader stage. Defaults to 16. Higher is "better". - pub max_samplers_per_shader_stage: u32, - /// Amount of storage buffers visible in a single shader stage. Defaults to 8. Higher is "better". - pub max_storage_buffers_per_shader_stage: u32, - /// Amount of storage textures visible in a single shader stage. Defaults to 4. Higher is "better". - pub max_storage_textures_per_shader_stage: u32, - /// Amount of uniform buffers visible in a single shader stage. Defaults to 12. Higher is "better". - pub max_uniform_buffers_per_shader_stage: u32, - /// Amount of individual resources within binding arrays that can be accessed in a single shader stage. Applies - /// to all types of bindings except samplers. - /// - /// This "defaults" to 0. However if binding arrays are supported, all devices can support 500,000. Higher is "better". - pub max_binding_array_elements_per_shader_stage: u32, - /// Amount of individual samplers within binding arrays that can be accessed in a single shader stage. - /// - /// This "defaults" to 0. However if binding arrays are supported, all devices can support 1,000. Higher is "better". - pub max_binding_array_sampler_elements_per_shader_stage: u32, - /// Maximum size in bytes of a binding to a uniform buffer. Defaults to 64 KiB. Higher is "better". - pub max_uniform_buffer_binding_size: u32, - /// Maximum size in bytes of a binding to a storage buffer. Defaults to 128 MiB. Higher is "better". - pub max_storage_buffer_binding_size: u32, - /// Maximum length of `VertexState::buffers` when creating a `RenderPipeline`. - /// Defaults to 8. Higher is "better". - pub max_vertex_buffers: u32, - /// A limit above which buffer allocations are guaranteed to fail. - /// Defaults to 256 MiB. Higher is "better". - /// - /// Buffer allocations below the maximum buffer size may not succeed depending on available memory, - /// fragmentation and other factors. - pub max_buffer_size: u64, - /// Maximum length of `VertexBufferLayout::attributes`, summed over all `VertexState::buffers`, - /// when creating a `RenderPipeline`. - /// Defaults to 16. Higher is "better". - pub max_vertex_attributes: u32, - /// Maximum value for `VertexBufferLayout::array_stride` when creating a `RenderPipeline`. - /// Defaults to 2048. Higher is "better". - pub max_vertex_buffer_array_stride: u32, - /// Required `BufferBindingType::Uniform` alignment for `BufferBinding::offset` - /// when creating a `BindGroup`, or for `set_bind_group` `dynamicOffsets`. - /// Defaults to 256. Lower is "better". - pub min_uniform_buffer_offset_alignment: u32, - /// Required `BufferBindingType::Storage` alignment for `BufferBinding::offset` - /// when creating a `BindGroup`, or for `set_bind_group` `dynamicOffsets`. - /// Defaults to 256. Lower is "better". - pub min_storage_buffer_offset_alignment: u32, - /// Maximum allowed number of components (scalars) of input or output locations for - /// inter-stage communication (vertex outputs to fragment inputs). Defaults to 60. - /// Higher is "better". - pub max_inter_stage_shader_components: u32, - /// The maximum allowed number of color attachments. - pub max_color_attachments: u32, - /// The maximum number of bytes necessary to hold one sample (pixel or subpixel) of render - /// pipeline output data, across all color attachments as described by [`TextureFormat::target_pixel_byte_cost`] - /// and [`TextureFormat::target_component_alignment`]. Defaults to 32. Higher is "better". - /// - /// ⚠️ `Rgba8Unorm`/`Rgba8Snorm`/`Bgra8Unorm`/`Bgra8Snorm` are deceptively 8 bytes per sample. ⚠️ - pub max_color_attachment_bytes_per_sample: u32, - /// Maximum number of bytes used for workgroup memory in a compute entry point. Defaults to - /// 16384. Higher is "better". - pub max_compute_workgroup_storage_size: u32, - /// Maximum value of the product of the `workgroup_size` dimensions for a compute entry-point. - /// Defaults to 256. Higher is "better". - pub max_compute_invocations_per_workgroup: u32, - /// The maximum value of the `workgroup_size` X dimension for a compute stage `ShaderModule` entry-point. - /// Defaults to 256. Higher is "better". - pub max_compute_workgroup_size_x: u32, - /// The maximum value of the `workgroup_size` Y dimension for a compute stage `ShaderModule` entry-point. - /// Defaults to 256. Higher is "better". - pub max_compute_workgroup_size_y: u32, - /// The maximum value of the `workgroup_size` Z dimension for a compute stage `ShaderModule` entry-point. - /// Defaults to 64. Higher is "better". - pub max_compute_workgroup_size_z: u32, - /// The maximum value for each dimension of a `ComputePass::dispatch(x, y, z)` operation. - /// Defaults to 65535. Higher is "better". - pub max_compute_workgroups_per_dimension: u32, +impl core::error::Error for PollError {} - /// Minimal number of invocations in a subgroup. Lower is "better". - pub min_subgroup_size: u32, - /// Maximal number of invocations in a subgroup. Higher is "better". - pub max_subgroup_size: u32, - /// Amount of storage available for push constants in bytes. Defaults to 0. Higher is "better". - /// Requesting more than 0 during device creation requires [`Features::PUSH_CONSTANTS`] to be enabled. - /// - /// Expect the size to be: - /// - Vulkan: 128-256 bytes - /// - DX12: 256 bytes - /// - Metal: 4096 bytes - /// - OpenGL doesn't natively support push constants, and are emulated with uniforms, - /// so this number is less useful but likely 256. - pub max_push_constant_size: u32, - /// Maximum number of live non-sampler bindings. - /// - /// <div class="warning"> - /// The default value is **1_000_000**, On systems with integrated GPUs (iGPUs)—particularly on Windows using the D3D12 - /// backend—this can lead to significant system RAM consumption since iGPUs share system memory directly with the CPU. - /// </div> +/// Status of device poll operation. +#[derive(Debug, PartialEq, Eq)] +pub enum PollStatus { + /// There are no active submissions in flight as of the beginning of the poll call. + /// Other submissions may have been queued on other threads during the call. /// - /// This limit only affects the d3d12 backend. Using a large number will allow the device - /// to create many bind groups at the cost of a large up-front allocation at device creation. - pub max_non_sampler_bindings: u32, - - /// The maximum total value of x*y*z for a given `draw_mesh_tasks` command - pub max_task_workgroup_total_count: u32, - /// The maximum value for each dimension of a `RenderPass::draw_mesh_tasks(x, y, z)` operation. - /// Defaults to 65535. Higher is "better". - pub max_task_workgroups_per_dimension: u32, - /// The maximum number of layers that can be output from a mesh shader - pub max_mesh_output_layers: u32, - /// The maximum number of views that can be used by a mesh shader in multiview rendering - pub max_mesh_multiview_view_count: u32, - - /// The maximum number of primitive (ex: triangles, aabbs) a BLAS is allowed to have. Requesting - /// more than 0 during device creation only makes sense if [`Features::EXPERIMENTAL_RAY_QUERY`] - /// is enabled. - pub max_blas_primitive_count: u32, - /// The maximum number of geometry descriptors a BLAS is allowed to have. Requesting - /// more than 0 during device creation only makes sense if [`Features::EXPERIMENTAL_RAY_QUERY`] - /// is enabled. - pub max_blas_geometry_count: u32, - /// The maximum number of instances a TLAS is allowed to have. Requesting more than 0 during - /// device creation only makes sense if [`Features::EXPERIMENTAL_RAY_QUERY`] - /// is enabled. - pub max_tlas_instance_count: u32, - /// The maximum number of acceleration structures allowed to be used in a shader stage. - /// Requesting more than 0 during device creation only makes sense if [`Features::EXPERIMENTAL_RAY_QUERY`] - /// is enabled. - pub max_acceleration_structures_per_shader_stage: u32, + /// This implies that the given Wait was satisfied before the timeout. + QueueEmpty, - /// The maximum number of views that can be used in multiview rendering - pub max_multiview_view_count: u32, -} + /// The requested Wait was satisfied before the timeout. + WaitSucceeded, -impl Default for Limits { - fn default() -> Self { - Self::defaults() - } + /// This was a poll. + Poll, } -impl Limits { - /// These default limits are guaranteed to to work on all modern - /// backends and guaranteed to be supported by WebGPU - /// - /// Those limits are as follows: - /// ```rust - /// # use wgpu_types::Limits; - /// assert_eq!(Limits::defaults(), Limits { - /// max_texture_dimension_1d: 8192, - /// max_texture_dimension_2d: 8192, - /// max_texture_dimension_3d: 2048, - /// max_texture_array_layers: 256, - /// max_bind_groups: 4, - /// max_bindings_per_bind_group: 1000, - /// max_dynamic_uniform_buffers_per_pipeline_layout: 8, - /// max_dynamic_storage_buffers_per_pipeline_layout: 4, - /// max_sampled_textures_per_shader_stage: 16, - /// max_samplers_per_shader_stage: 16, - /// max_storage_buffers_per_shader_stage: 8, - /// max_storage_textures_per_shader_stage: 4, - /// max_uniform_buffers_per_shader_stage: 12, - /// max_binding_array_elements_per_shader_stage: 0, - /// max_binding_array_sampler_elements_per_shader_stage: 0, - /// max_uniform_buffer_binding_size: 64 << 10, // (64 KiB) - /// max_storage_buffer_binding_size: 128 << 20, // (128 MiB) - /// max_vertex_buffers: 8, - /// max_buffer_size: 256 << 20, // (256 MiB) - /// max_vertex_attributes: 16, - /// max_vertex_buffer_array_stride: 2048, - /// min_uniform_buffer_offset_alignment: 256, - /// min_storage_buffer_offset_alignment: 256, - /// max_inter_stage_shader_components: 60, - /// max_color_attachments: 8, - /// max_color_attachment_bytes_per_sample: 32, - /// max_compute_workgroup_storage_size: 16384, - /// max_compute_invocations_per_workgroup: 256, - /// max_compute_workgroup_size_x: 256, - /// max_compute_workgroup_size_y: 256, - /// max_compute_workgroup_size_z: 64, - /// max_compute_workgroups_per_dimension: 65535, - /// min_subgroup_size: 0, - /// max_subgroup_size: 0, - /// max_push_constant_size: 0, - /// max_non_sampler_bindings: 1_000_000, - /// max_task_workgroup_total_count: 0, - /// max_task_workgroups_per_dimension: 0, - /// max_mesh_multiview_view_count: 0, - /// max_mesh_output_layers: 0, - /// max_blas_primitive_count: 0, - /// max_blas_geometry_count: 0, - /// max_tlas_instance_count: 0, - /// max_acceleration_structures_per_shader_stage: 0, - /// max_multiview_view_count: 0, - /// }); - /// ``` - /// - /// Rust doesn't allow const in trait implementations, so we break this out - /// to allow reusing these defaults in const contexts +impl PollStatus { + /// Returns true if the result is [`Self::QueueEmpty`]. #[must_use] - pub const fn defaults() -> Self { - Self { - max_texture_dimension_1d: 8192, - max_texture_dimension_2d: 8192, - max_texture_dimension_3d: 2048, - max_texture_array_layers: 256, - max_bind_groups: 4, - max_bindings_per_bind_group: 1000, - max_dynamic_uniform_buffers_per_pipeline_layout: 8, - max_dynamic_storage_buffers_per_pipeline_layout: 4, - max_sampled_textures_per_shader_stage: 16, - max_samplers_per_shader_stage: 16, - max_storage_buffers_per_shader_stage: 8, - max_storage_textures_per_shader_stage: 4, - max_uniform_buffers_per_shader_stage: 12, - max_binding_array_elements_per_shader_stage: 0, - max_binding_array_sampler_elements_per_shader_stage: 0, - max_uniform_buffer_binding_size: 64 << 10, // (64 KiB) - max_storage_buffer_binding_size: 128 << 20, // (128 MiB) - max_vertex_buffers: 8, - max_buffer_size: 256 << 20, // (256 MiB) - max_vertex_attributes: 16, - max_vertex_buffer_array_stride: 2048, - min_uniform_buffer_offset_alignment: 256, - min_storage_buffer_offset_alignment: 256, - max_inter_stage_shader_components: 60, - max_color_attachments: 8, - max_color_attachment_bytes_per_sample: 32, - max_compute_workgroup_storage_size: 16384, - max_compute_invocations_per_workgroup: 256, - max_compute_workgroup_size_x: 256, - max_compute_workgroup_size_y: 256, - max_compute_workgroup_size_z: 64, - max_compute_workgroups_per_dimension: 65535, - min_subgroup_size: 0, - max_subgroup_size: 0, - max_push_constant_size: 0, - max_non_sampler_bindings: 1_000_000, - - max_task_workgroup_total_count: 0, - max_task_workgroups_per_dimension: 0, - max_mesh_multiview_view_count: 0, - max_mesh_output_layers: 0, - - max_blas_primitive_count: 0, - max_blas_geometry_count: 0, - max_tlas_instance_count: 0, - max_acceleration_structures_per_shader_stage: 0, - - max_multiview_view_count: 0, - } + pub fn is_queue_empty(&self) -> bool { + matches!(self, Self::QueueEmpty) } - /// These default limits are guaranteed to be compatible with GLES-3.1, and D3D11 - /// - /// Those limits are as follows (different from default are marked with *): - /// ```rust - /// # use wgpu_types::Limits; - /// assert_eq!(Limits::downlevel_defaults(), Limits { - /// max_texture_dimension_1d: 2048, // * - /// max_texture_dimension_2d: 2048, // * - /// max_texture_dimension_3d: 256, // * - /// max_texture_array_layers: 256, - /// max_bind_groups: 4, - /// max_bindings_per_bind_group: 1000, - /// max_dynamic_uniform_buffers_per_pipeline_layout: 8, - /// max_dynamic_storage_buffers_per_pipeline_layout: 4, - /// max_sampled_textures_per_shader_stage: 16, - /// max_samplers_per_shader_stage: 16, - /// max_storage_buffers_per_shader_stage: 4, // * - /// max_storage_textures_per_shader_stage: 4, - /// max_uniform_buffers_per_shader_stage: 12, - /// max_binding_array_elements_per_shader_stage: 0, - /// max_binding_array_sampler_elements_per_shader_stage: 0, - /// max_uniform_buffer_binding_size: 16 << 10, // * (16 KiB) - /// max_storage_buffer_binding_size: 128 << 20, // (128 MiB) - /// max_vertex_buffers: 8, - /// max_vertex_attributes: 16, - /// max_vertex_buffer_array_stride: 2048, - /// min_subgroup_size: 0, - /// max_subgroup_size: 0, - /// max_push_constant_size: 0, - /// min_uniform_buffer_offset_alignment: 256, - /// min_storage_buffer_offset_alignment: 256, - /// max_inter_stage_shader_components: 60, - /// max_color_attachments: 4, - /// max_color_attachment_bytes_per_sample: 32, - /// max_compute_workgroup_storage_size: 16352, // * - /// max_compute_invocations_per_workgroup: 256, - /// max_compute_workgroup_size_x: 256, - /// max_compute_workgroup_size_y: 256, - /// max_compute_workgroup_size_z: 64, - /// max_compute_workgroups_per_dimension: 65535, - /// max_buffer_size: 256 << 20, // (256 MiB) - /// max_non_sampler_bindings: 1_000_000, - /// - /// max_task_workgroup_total_count: 0, - /// max_task_workgroups_per_dimension: 0, - /// max_mesh_multiview_view_count: 0, - /// max_mesh_output_layers: 0, - /// - /// max_blas_primitive_count: 0, - /// max_blas_geometry_count: 0, - /// max_tlas_instance_count: 0, - /// max_acceleration_structures_per_shader_stage: 0, - /// - /// max_multiview_view_count: 0, - /// }); - /// ``` + /// Returns true if the result is either [`Self::WaitSucceeded`] or [`Self::QueueEmpty`]. #[must_use] - pub const fn downlevel_defaults() -> Self { - Self { - max_texture_dimension_1d: 2048, - max_texture_dimension_2d: 2048, - max_texture_dimension_3d: 256, - max_storage_buffers_per_shader_stage: 4, - max_uniform_buffer_binding_size: 16 << 10, // (16 KiB) - max_color_attachments: 4, - // see: https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf#page=7 - max_compute_workgroup_storage_size: 16352, - - max_task_workgroups_per_dimension: 0, - max_task_workgroup_total_count: 0, - max_mesh_multiview_view_count: 0, - max_mesh_output_layers: 0, - ..Self::defaults() - } + pub fn wait_finished(&self) -> bool { + matches!(self, Self::WaitSucceeded | Self::QueueEmpty) } +} - /// These default limits are guaranteed to be compatible with GLES-3.0, and D3D11, and WebGL2 - /// - /// Those limits are as follows (different from `downlevel_defaults` are marked with +, - /// *'s from `downlevel_defaults` shown as well.): - /// ```rust - /// # use wgpu_types::Limits; - /// assert_eq!(Limits::downlevel_webgl2_defaults(), Limits { - /// max_texture_dimension_1d: 2048, // * - /// max_texture_dimension_2d: 2048, // * - /// max_texture_dimension_3d: 256, // * - /// max_texture_array_layers: 256, - /// max_bind_groups: 4, - /// max_bindings_per_bind_group: 1000, - /// max_dynamic_uniform_buffers_per_pipeline_layout: 8, - /// max_dynamic_storage_buffers_per_pipeline_layout: 0, // + - /// max_sampled_textures_per_shader_stage: 16, - /// max_samplers_per_shader_stage: 16, - /// max_storage_buffers_per_shader_stage: 0, // * + - /// max_storage_textures_per_shader_stage: 0, // + - /// max_uniform_buffers_per_shader_stage: 11, // + - /// max_binding_array_elements_per_shader_stage: 0, - /// max_binding_array_sampler_elements_per_shader_stage: 0, - /// max_uniform_buffer_binding_size: 16 << 10, // * (16 KiB) - /// max_storage_buffer_binding_size: 0, // * + - /// max_vertex_buffers: 8, - /// max_vertex_attributes: 16, - /// max_vertex_buffer_array_stride: 255, // + - /// min_subgroup_size: 0, - /// max_subgroup_size: 0, - /// max_push_constant_size: 0, - /// min_uniform_buffer_offset_alignment: 256, - /// min_storage_buffer_offset_alignment: 256, - /// max_inter_stage_shader_components: 31, - /// max_color_attachments: 4, - /// max_color_attachment_bytes_per_sample: 32, - /// max_compute_workgroup_storage_size: 0, // + - /// max_compute_invocations_per_workgroup: 0, // + - /// max_compute_workgroup_size_x: 0, // + - /// max_compute_workgroup_size_y: 0, // + - /// max_compute_workgroup_size_z: 0, // + - /// max_compute_workgroups_per_dimension: 0, // + - /// max_buffer_size: 256 << 20, // (256 MiB), - /// max_non_sampler_bindings: 1_000_000, - /// - /// max_task_workgroup_total_count: 0, - /// max_task_workgroups_per_dimension: 0, - /// max_mesh_multiview_view_count: 0, - /// max_mesh_output_layers: 0, - /// - /// max_blas_primitive_count: 0, - /// max_blas_geometry_count: 0, - /// max_tlas_instance_count: 0, - /// max_acceleration_structures_per_shader_stage: 0, - /// - /// max_multiview_view_count: 0, - /// }); - /// ``` - #[must_use] - pub const fn downlevel_webgl2_defaults() -> Self { - Self { - max_uniform_buffers_per_shader_stage: 11, - max_storage_buffers_per_shader_stage: 0, - max_storage_textures_per_shader_stage: 0, - max_dynamic_storage_buffers_per_pipeline_layout: 0, - max_storage_buffer_binding_size: 0, - max_vertex_buffer_array_stride: 255, - max_compute_workgroup_storage_size: 0, - max_compute_invocations_per_workgroup: 0, - max_compute_workgroup_size_x: 0, - max_compute_workgroup_size_y: 0, - max_compute_workgroup_size_z: 0, - max_compute_workgroups_per_dimension: 0, - min_subgroup_size: 0, - max_subgroup_size: 0, - - // Value supported by Intel Celeron B830 on Windows (OpenGL 3.1) - max_inter_stage_shader_components: 31, - - // Most of the values should be the same as the downlevel defaults - ..Self::downlevel_defaults() - } - } +/// Describes a [`CommandEncoder`](../wgpu/struct.CommandEncoder.html). +/// +/// Corresponds to [WebGPU `GPUCommandEncoderDescriptor`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gpucommandencoderdescriptor). +#[repr(C)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct CommandEncoderDescriptor<L> { + /// Debug label for the command encoder. This will show up in graphics debuggers for easy identification. + pub label: L, +} - /// Modify the current limits to use the resolution limits of the other. - /// - /// This is useful because the swapchain might need to be larger than any other image in the application. - /// - /// If your application only needs 512x512, you might be running on a 4k display and need extremely high resolution limits. +impl<L> CommandEncoderDescriptor<L> { + /// Takes a closure and maps the label of the command encoder descriptor into another. #[must_use] - pub const fn using_resolution(self, other: Self) -> Self { - Self { - max_texture_dimension_1d: other.max_texture_dimension_1d, - max_texture_dimension_2d: other.max_texture_dimension_2d, - max_texture_dimension_3d: other.max_texture_dimension_3d, - ..self + pub fn map_label<K>(&self, fun: impl FnOnce(&L) -> K) -> CommandEncoderDescriptor<K> { + CommandEncoderDescriptor { + label: fun(&self.label), } } +} - /// Modify the current limits to use the buffer alignment limits of the adapter. - /// - /// This is useful for when you'd like to dynamically use the "best" supported buffer alignments. - #[must_use] - pub const fn using_alignment(self, other: Self) -> Self { - Self { - min_uniform_buffer_offset_alignment: other.min_uniform_buffer_offset_alignment, - min_storage_buffer_offset_alignment: other.min_storage_buffer_offset_alignment, - ..self - } +impl<T> Default for CommandEncoderDescriptor<Option<T>> { + fn default() -> Self { + Self { label: None } } +} - /// The minimum guaranteed limits for acceleration structures if you enable [`Features::EXPERIMENTAL_RAY_QUERY`] - #[must_use] - pub const fn using_minimum_supported_acceleration_structure_values(self) -> Self { - Self { - max_blas_geometry_count: (1 << 24) - 1, // 2^24 - 1: Vulkan's minimum - max_tlas_instance_count: (1 << 24) - 1, // 2^24 - 1: Vulkan's minimum - max_blas_primitive_count: (1 << 24) - 1, // Should be 2^28: Metal's minimum, but due to an llvmpipe bug it is 2^24 - 1 - max_acceleration_structures_per_shader_stage: 16, // Vulkan's minimum - ..self - } - } - - /// Modify the current limits to use the acceleration structure limits of `other` (`other` could - /// be the limits of the adapter). - #[must_use] - pub const fn using_acceleration_structure_values(self, other: Self) -> Self { - Self { - max_blas_geometry_count: other.max_blas_geometry_count, - max_tlas_instance_count: other.max_tlas_instance_count, - max_blas_primitive_count: other.max_blas_primitive_count, - max_acceleration_structures_per_shader_stage: other - .max_acceleration_structures_per_shader_stage, - ..self - } - } - - /// The recommended minimum limits for mesh shaders if you enable [`Features::EXPERIMENTAL_MESH_SHADER`] - /// - /// These are chosen somewhat arbitrarily. They are small enough that they should cover all physical devices, - /// but not necessarily all use cases. - #[must_use] - pub const fn using_recommended_minimum_mesh_shader_values(self) -> Self { - Self { - // This is a common limit for apple devices. It's not immediately clear why. - max_task_workgroup_total_count: 1024, - max_task_workgroups_per_dimension: 1024, - // llvmpipe reports 0 multiview count, which just means no multiview is allowed - max_mesh_multiview_view_count: 0, - // llvmpipe once again requires this to be <=8. An RTX 3060 supports well over 1024. - max_mesh_output_layers: 8, - ..self - } - } - - /// Compares every limits within self is within the limits given in `allowed`. - /// - /// If you need detailed information on failures, look at [`Limits::check_limits_with_fail_fn`]. - #[must_use] - pub fn check_limits(&self, allowed: &Self) -> bool { - let mut within = true; - self.check_limits_with_fail_fn(allowed, true, |_, _, _| within = false); - within - } - - /// Compares every limits within self is within the limits given in `allowed`. - /// For an easy to use binary choice, use [`Limits::check_limits`]. - /// - /// If a value is not within the allowed limit, this function calls the `fail_fn` - /// with the: - /// - limit name - /// - self's limit - /// - allowed's limit. - /// - /// If fatal is true, a single failure bails out the comparison after a single failure. - pub fn check_limits_with_fail_fn( - &self, - allowed: &Self, - fatal: bool, - mut fail_fn: impl FnMut(&'static str, u64, u64), - ) { - macro_rules! check_with_fail_fn { - ($name:ident, $ordering:expr) => { - let invalid_ord = $ordering.reverse(); - // In the case of `min_subgroup_size`, requesting a value of - // zero means "I'm not going to use subgroups", so we have to - // special case that. If any of our minimum limits could - // meaningfully go all the way to zero, that would conflict with - // this. - if self.$name != 0 && self.$name.cmp(&allowed.$name) == invalid_ord { - fail_fn(stringify!($name), self.$name as u64, allowed.$name as u64); - if fatal { - return; - } - } - }; - } - - if self.min_subgroup_size > self.max_subgroup_size { - fail_fn( - "max_subgroup_size", - self.min_subgroup_size as u64, - allowed.min_subgroup_size as u64, - ); - } - with_limits!(check_with_fail_fn); - } - - /// For each limit in `other` that is better than the value in `self`, - /// replace the value in `self` with the value from `other`. - /// - /// A request for a limit value less than the WebGPU-specified default must - /// be ignored. This function is used to clamp such requests to the default - /// value. - /// - /// This function is not for clamping requests for values beyond the - /// supported limits. For that purpose the desired function would be - /// `or_worse_values_from` (which doesn't exist, but could be added if - /// needed). - #[must_use] - pub fn or_better_values_from(mut self, other: &Self) -> Self { - macro_rules! or_better_value_from { - ($name:ident, $ordering:expr) => { - match $ordering { - // Limits that are maximum values (most of them) - Ordering::Less => self.$name = self.$name.max(other.$name), - // Limits that are minimum values - Ordering::Greater => self.$name = self.$name.min(other.$name), - Ordering::Equal => unreachable!(), - } - }; - } - - with_limits!(or_better_value_from); - - self - } -} - -/// Represents the sets of additional limits on an adapter, -/// which take place when running on downlevel backends. -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct DownlevelLimits {} - -#[allow(clippy::derivable_impls)] -impl Default for DownlevelLimits { - fn default() -> Self { - DownlevelLimits {} - } -} - -/// Lists various ways the underlying platform does not conform to the WebGPU standard. -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct DownlevelCapabilities { - /// Combined boolean flags. - pub flags: DownlevelFlags, - /// Additional limits - pub limits: DownlevelLimits, - /// Which collections of features shaders support. Defined in terms of D3D's shader models. - pub shader_model: ShaderModel, -} - -impl Default for DownlevelCapabilities { - fn default() -> Self { - Self { - flags: DownlevelFlags::all(), - limits: DownlevelLimits::default(), - shader_model: ShaderModel::Sm5, - } - } -} - -impl DownlevelCapabilities { - /// Returns true if the underlying platform offers complete support of the baseline WebGPU standard. - /// - /// If this returns false, some parts of the API will result in validation errors where they would not normally. - /// These parts can be determined by the values in this structure. - #[must_use] - pub fn is_webgpu_compliant(&self) -> bool { - self.flags.contains(DownlevelFlags::compliant()) - && self.limits == DownlevelLimits::default() - && self.shader_model >= ShaderModel::Sm5 - } -} - -bitflags::bitflags! { - /// Binary flags listing features that may or may not be present on downlevel adapters. - /// - /// A downlevel adapter is a GPU adapter that wgpu supports, but with potentially limited - /// features, due to the lack of hardware feature support. - /// - /// Flags that are **not** present for a downlevel adapter or device usually indicates - /// non-compliance with the WebGPU specification, but not always. - /// - /// You can check whether a set of flags is compliant through the - /// [`DownlevelCapabilities::is_webgpu_compliant()`] function. - #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] - #[cfg_attr(feature = "serde", serde(transparent))] - #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] - pub struct DownlevelFlags: u32 { - /// The device supports compiling and using compute shaders. - /// - /// WebGL2, and GLES3.0 devices do not support compute. - const COMPUTE_SHADERS = 1 << 0; - /// Supports binding storage buffers and textures to fragment shaders. - const FRAGMENT_WRITABLE_STORAGE = 1 << 1; - /// Supports indirect drawing and dispatching. - /// - /// [`Self::COMPUTE_SHADERS`] must be present for this flag. - /// - /// WebGL2, GLES 3.0, and Metal on Apple1/Apple2 GPUs do not support indirect. - const INDIRECT_EXECUTION = 1 << 2; - /// Supports non-zero `base_vertex` parameter to direct indexed draw calls. - /// - /// Indirect calls, if supported, always support non-zero `base_vertex`. - /// - /// Supported by: - /// - Vulkan - /// - DX12 - /// - Metal on Apple3+ or Mac1+ - /// - OpenGL 3.2+ - /// - OpenGL ES 3.2 - const BASE_VERTEX = 1 << 3; - /// Supports reading from a depth/stencil texture while using it as a read-only - /// depth/stencil attachment. - /// - /// The WebGL2 and GLES backends do not support RODS. - const READ_ONLY_DEPTH_STENCIL = 1 << 4; - /// Supports textures with mipmaps which have a non power of two size. - const NON_POWER_OF_TWO_MIPMAPPED_TEXTURES = 1 << 5; - /// Supports textures that are cube arrays. - const CUBE_ARRAY_TEXTURES = 1 << 6; - /// Supports comparison samplers. - const COMPARISON_SAMPLERS = 1 << 7; - /// Supports different blend operations per color attachment. - const INDEPENDENT_BLEND = 1 << 8; - /// Supports storage buffers in vertex shaders. - const VERTEX_STORAGE = 1 << 9; - - /// Supports samplers with anisotropic filtering. Note this isn't actually required by - /// WebGPU, the implementation is allowed to completely ignore aniso clamp. This flag is - /// here for native backends so they can communicate to the user of aniso is enabled. - /// - /// All backends and all devices support anisotropic filtering. - const ANISOTROPIC_FILTERING = 1 << 10; - - /// Supports storage buffers in fragment shaders. - const FRAGMENT_STORAGE = 1 << 11; - - /// Supports sample-rate shading. - const MULTISAMPLED_SHADING = 1 << 12; - - /// Supports copies between depth textures and buffers. - /// - /// GLES/WebGL don't support this. - const DEPTH_TEXTURE_AND_BUFFER_COPIES = 1 << 13; - - /// Supports all the texture usages described in WebGPU. If this isn't supported, you - /// should call `get_texture_format_features` to get how you can use textures of a given format - const WEBGPU_TEXTURE_FORMAT_SUPPORT = 1 << 14; - - /// Supports buffer bindings with sizes that aren't a multiple of 16. - /// - /// WebGL doesn't support this. - const BUFFER_BINDINGS_NOT_16_BYTE_ALIGNED = 1 << 15; - - /// Supports buffers to combine [`BufferUsages::INDEX`] with usages other than [`BufferUsages::COPY_DST`] and [`BufferUsages::COPY_SRC`]. - /// Furthermore, in absence of this feature it is not allowed to copy index buffers from/to buffers with a set of usage flags containing - /// [`BufferUsages::VERTEX`]/[`BufferUsages::UNIFORM`]/[`BufferUsages::STORAGE`] or [`BufferUsages::INDIRECT`]. - /// - /// WebGL doesn't support this. - const UNRESTRICTED_INDEX_BUFFER = 1 << 16; - - /// Supports full 32-bit range indices (2^32-1 as opposed to 2^24-1 without this flag) - /// - /// Corresponds to Vulkan's `VkPhysicalDeviceFeatures.fullDrawIndexUint32` - const FULL_DRAW_INDEX_UINT32 = 1 << 17; - - /// Supports depth bias clamping - /// - /// Corresponds to Vulkan's `VkPhysicalDeviceFeatures.depthBiasClamp` - const DEPTH_BIAS_CLAMP = 1 << 18; - - /// Supports specifying which view format values are allowed when create_view() is called on a texture. - /// - /// The WebGL and GLES backends doesn't support this. - const VIEW_FORMATS = 1 << 19; - - /// With this feature not present, there are the following restrictions on `Queue::copy_external_image_to_texture`: - /// - The source must not be [`web_sys::OffscreenCanvas`] - /// - [`CopyExternalImageSourceInfo::origin`] must be zero. - /// - [`CopyExternalImageDestInfo::color_space`] must be srgb. - /// - If the source is an [`web_sys::ImageBitmap`]: - /// - [`CopyExternalImageSourceInfo::flip_y`] must be false. - /// - [`CopyExternalImageDestInfo::premultiplied_alpha`] must be false. - /// - /// WebGL doesn't support this. WebGPU does. - const UNRESTRICTED_EXTERNAL_TEXTURE_COPIES = 1 << 20; - - /// Supports specifying which view formats are allowed when calling create_view on the texture returned by - /// `Surface::get_current_texture`. - /// - /// The GLES/WebGL and Vulkan on Android doesn't support this. - const SURFACE_VIEW_FORMATS = 1 << 21; - - /// If this is true, calls to `CommandEncoder::resolve_query_set` will be performed on the queue timeline. - /// - /// If this is false, calls to `CommandEncoder::resolve_query_set` will be performed on the device (i.e. cpu) timeline - /// and will block that timeline until the query has data. You may work around this limitation by waiting until the submit - /// whose queries you are resolving is fully finished (through use of `queue.on_submitted_work_done`) and only - /// then submitting the resolve_query_set command. The queries will be guaranteed finished, so will not block. - /// - /// Supported by: - /// - Vulkan, - /// - DX12 - /// - Metal - /// - OpenGL 4.4+ - /// - /// Not Supported by: - /// - GL ES / WebGL - const NONBLOCKING_QUERY_RESOLVE = 1 << 22; - - /// Allows shaders to use `quantizeToF16`, `pack2x16float`, and `unpack2x16float`, which - /// operate on `f16`-precision values stored in `f32`s. - /// - /// Not supported by Vulkan on Mesa when [`Features::SHADER_F16`] is absent. - const SHADER_F16_IN_F32 = 1 << 23; - } -} - -impl DownlevelFlags { - /// All flags that indicate if the backend is WebGPU compliant - #[must_use] - pub const fn compliant() -> Self { - // We use manual bit twiddling to make this a const fn as `Sub` and `.remove` aren't const - - // WebGPU doesn't actually require aniso - Self::from_bits_truncate(Self::all().bits() & !Self::ANISOTROPIC_FILTERING.bits()) - } -} - -/// Collections of shader features a device supports if they support less than WebGPU normally allows. -// TODO: Fill out the differences between shader models more completely -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum ShaderModel { - /// Extremely limited shaders, including a total instruction limit. - Sm2, - /// Missing minor features and storage images. - Sm4, - /// WebGPU supports shader module 5. - Sm5, -} - -/// Supported physical device types. -#[repr(u8)] -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum DeviceType { - /// Other or Unknown. - Other, - /// Integrated GPU with shared CPU/GPU memory. - IntegratedGpu, - /// Discrete GPU with separate CPU/GPU memory. - DiscreteGpu, - /// Virtual / Hosted. - VirtualGpu, - /// Cpu / Software Rendering. - Cpu, -} - -//TODO: convert `vendor` and `device` to `u32` - -/// Information about an adapter. -#[derive(Clone, Debug, Eq, PartialEq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct AdapterInfo { - /// Adapter name - pub name: String, - /// [`Backend`]-specific vendor ID of the adapter - /// - /// This generally is a 16-bit PCI vendor ID in the least significant bytes of this field. - /// However, more significant bytes may be non-zero if the backend uses a different - /// representation. - /// - /// * For [`Backend::Vulkan`], the [`VkPhysicalDeviceProperties::vendorID`] is used, which is - /// a superset of PCI IDs. - /// - /// [`VkPhysicalDeviceProperties::vendorID`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkPhysicalDeviceProperties.html - pub vendor: u32, - /// [`Backend`]-specific device ID of the adapter - /// - /// - /// This generally is a 16-bit PCI device ID in the least significant bytes of this field. - /// However, more significant bytes may be non-zero if the backend uses a different - /// representation. - /// - /// * For [`Backend::Vulkan`], the [`VkPhysicalDeviceProperties::deviceID`] is used, which is - /// a superset of PCI IDs. - /// - /// [`VkPhysicalDeviceProperties::deviceID`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkPhysicalDeviceProperties.html - pub device: u32, - /// Type of device - pub device_type: DeviceType, - /// [`Backend`]-specific PCI bus ID of the adapter. - /// - /// * For [`Backend::Vulkan`], [`VkPhysicalDevicePCIBusInfoPropertiesEXT`] is used, - /// if available, in the form `bus:device.function`, e.g. `0000:01:00.0`. - /// - /// [`VkPhysicalDevicePCIBusInfoPropertiesEXT`]: https://registry.khronos.org/vulkan/specs/latest/man/html/VkPhysicalDevicePCIBusInfoPropertiesEXT.html - pub device_pci_bus_id: String, - /// Driver name - pub driver: String, - /// Driver info - pub driver_info: String, - /// Backend used for device - pub backend: Backend, - /// If true, adding [`TextureUsages::TRANSIENT`] to a texture will decrease memory usage. - pub transient_saves_memory: bool, -} - -/// Hints to the device about the memory allocation strategy. -/// -/// Some backends may ignore these hints. -#[derive(Clone, Debug, Default)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum MemoryHints { - /// Favor performance over memory usage (the default value). - #[default] - Performance, - /// Favor memory usage over performance. - MemoryUsage, - /// Applications that have control over the content that is rendered - /// (typically games) may find an optimal compromise between memory - /// usage and performance by specifying the allocation configuration. - Manual { - /// Defines the range of allowed memory block sizes for sub-allocated - /// resources. - /// - /// The backend may attempt to group multiple resources into fewer - /// device memory blocks (sub-allocation) for performance reasons. - /// The start of the provided range specifies the initial memory - /// block size for sub-allocated resources. After running out of - /// space in existing memory blocks, the backend may chose to - /// progressively increase the block size of subsequent allocations - /// up to a limit specified by the end of the range. - /// - /// This does not limit resource sizes. If a resource does not fit - /// in the specified range, it will typically be placed in a dedicated - /// memory block. - suballocated_device_memory_block_size: Range<u64>, - }, -} - -/// Describes a [`Device`](../wgpu/struct.Device.html). -/// -/// Corresponds to [WebGPU `GPUDeviceDescriptor`]( -/// https://gpuweb.github.io/gpuweb/#gpudevicedescriptor). -#[derive(Clone, Debug, Default)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct DeviceDescriptor<L> { - /// Debug label for the device. - pub label: L, - /// Specifies the features that are required by the device request. - /// The request will fail if the adapter cannot provide these features. - /// - /// Exactly the specified set of features, and no more or less, - /// will be allowed in validation of API calls on the resulting device. - pub required_features: Features, - /// Specifies the limits that are required by the device request. - /// The request will fail if the adapter cannot provide these limits. - /// - /// Exactly the specified limits, and no better or worse, - /// will be allowed in validation of API calls on the resulting device. - pub required_limits: Limits, - /// Specifies whether `self.required_features` is allowed to contain experimental features. - #[cfg_attr(feature = "serde", serde(skip))] - pub experimental_features: ExperimentalFeatures, - /// Hints for memory allocation strategies. - pub memory_hints: MemoryHints, - /// Whether API tracing for debugging is enabled, - /// and where the trace is written if so. - pub trace: Trace, -} - -impl<L> DeviceDescriptor<L> { - /// Takes a closure and maps the label of the device descriptor into another. - #[must_use] - pub fn map_label<K>(&self, fun: impl FnOnce(&L) -> K) -> DeviceDescriptor<K> { - DeviceDescriptor { - label: fun(&self.label), - required_features: self.required_features, - required_limits: self.required_limits.clone(), - experimental_features: self.experimental_features, - memory_hints: self.memory_hints.clone(), - trace: self.trace.clone(), - } - } -} - -/// Controls API call tracing and specifies where the trace is written. -/// -/// **Note:** Tracing is currently unavailable. -/// See [issue 5974](https://github.com/gfx-rs/wgpu/issues/5974) for updates. -#[derive(Clone, Debug, Default)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -// This enum must be non-exhaustive so that enabling the "trace" feature is not a semver break. -#[non_exhaustive] -pub enum Trace { - /// Tracing disabled. - #[default] - Off, - - /// Tracing enabled. - #[cfg(feature = "trace")] - // This must be owned rather than `&'a Path`, because if it were that, then the lifetime - // parameter would be unused when the "trace" feature is disabled, which is prohibited. - Directory(std::path::PathBuf), -} - -bitflags::bitflags! { - /// Describes the shader stages that a binding will be visible from. - /// - /// These can be combined so something that is visible from both vertex and fragment shaders can be defined as: - /// - /// `ShaderStages::VERTEX | ShaderStages::FRAGMENT` - /// - /// Corresponds to [WebGPU `GPUShaderStageFlags`]( - /// https://gpuweb.github.io/gpuweb/#typedefdef-gpushaderstageflags). - #[repr(transparent)] - #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] - #[cfg_attr(feature = "serde", serde(transparent))] - #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] - pub struct ShaderStages: u32 { - /// Binding is not visible from any shader stage. - const NONE = 0; - /// Binding is visible from the vertex shader of a render pipeline. - const VERTEX = 1 << 0; - /// Binding is visible from the fragment shader of a render pipeline. - const FRAGMENT = 1 << 1; - /// Binding is visible from the compute shader of a compute pipeline. - const COMPUTE = 1 << 2; - /// Binding is visible from the vertex and fragment shaders of a render pipeline. - const VERTEX_FRAGMENT = Self::VERTEX.bits() | Self::FRAGMENT.bits(); - /// Binding is visible from the task shader of a mesh pipeline. - const TASK = 1 << 3; - /// Binding is visible from the mesh shader of a mesh pipeline. - const MESH = 1 << 4; - } -} - -/// Order in which texture data is laid out in memory. -#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Hash)] -pub enum TextureDataOrder { - /// The texture is laid out densely in memory as: - /// - /// ```text - /// Layer0Mip0 Layer0Mip1 Layer0Mip2 - /// Layer1Mip0 Layer1Mip1 Layer1Mip2 - /// Layer2Mip0 Layer2Mip1 Layer2Mip2 - /// ```` - /// - /// This is the layout used by dds files. - #[default] - LayerMajor, - /// The texture is laid out densely in memory as: - /// - /// ```text - /// Layer0Mip0 Layer1Mip0 Layer2Mip0 - /// Layer0Mip1 Layer1Mip1 Layer2Mip1 - /// Layer0Mip2 Layer1Mip2 Layer2Mip2 - /// ``` - /// - /// This is the layout used by ktx and ktx2 files. - MipMajor, -} - -/// Dimensions of a particular texture view. -/// -/// Corresponds to [WebGPU `GPUTextureViewDimension`]( -/// https://gpuweb.github.io/gpuweb/#enumdef-gputextureviewdimension). -#[repr(C)] -#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum TextureViewDimension { - /// A one dimensional texture. `texture_1d` in WGSL and `texture1D` in GLSL. - #[cfg_attr(feature = "serde", serde(rename = "1d"))] - D1, - /// A two dimensional texture. `texture_2d` in WGSL and `texture2D` in GLSL. - #[cfg_attr(feature = "serde", serde(rename = "2d"))] - #[default] - D2, - /// A two dimensional array texture. `texture_2d_array` in WGSL and `texture2DArray` in GLSL. - #[cfg_attr(feature = "serde", serde(rename = "2d-array"))] - D2Array, - /// A cubemap texture. `texture_cube` in WGSL and `textureCube` in GLSL. - #[cfg_attr(feature = "serde", serde(rename = "cube"))] - Cube, - /// A cubemap array texture. `texture_cube_array` in WGSL and `textureCubeArray` in GLSL. - #[cfg_attr(feature = "serde", serde(rename = "cube-array"))] - CubeArray, - /// A three dimensional texture. `texture_3d` in WGSL and `texture3D` in GLSL. - #[cfg_attr(feature = "serde", serde(rename = "3d"))] - D3, -} - -impl TextureViewDimension { - /// Get the texture dimension required of this texture view dimension. - #[must_use] - pub fn compatible_texture_dimension(self) -> TextureDimension { - match self { - Self::D1 => TextureDimension::D1, - Self::D2 | Self::D2Array | Self::Cube | Self::CubeArray => TextureDimension::D2, - Self::D3 => TextureDimension::D3, - } - } -} - -/// Alpha blend factor. -/// -/// Corresponds to [WebGPU `GPUBlendFactor`]( -/// https://gpuweb.github.io/gpuweb/#enumdef-gpublendfactor). Values using `Src1` -/// require [`Features::DUAL_SOURCE_BLENDING`] and can only be used with the first -/// render target. -/// -/// For further details on how the blend factors are applied, see the analogous -/// functionality in OpenGL: <https://www.khronos.org/opengl/wiki/Blending#Blending_Parameters>. -#[repr(C)] -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] -pub enum BlendFactor { - /// 0.0 - Zero = 0, - /// 1.0 - One = 1, - /// S.component - Src = 2, - /// 1.0 - S.component - OneMinusSrc = 3, - /// S.alpha - SrcAlpha = 4, - /// 1.0 - S.alpha - OneMinusSrcAlpha = 5, - /// D.component - Dst = 6, - /// 1.0 - D.component - OneMinusDst = 7, - /// D.alpha - DstAlpha = 8, - /// 1.0 - D.alpha - OneMinusDstAlpha = 9, - /// min(S.alpha, 1.0 - D.alpha) - SrcAlphaSaturated = 10, - /// Constant - 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`]. - #[must_use] - pub fn ref_second_blend_source(&self) -> bool { - match self { - BlendFactor::Src1 - | BlendFactor::OneMinusSrc1 - | BlendFactor::Src1Alpha - | BlendFactor::OneMinusSrc1Alpha => true, - _ => false, - } - } -} - -/// Alpha blend operation. -/// -/// Corresponds to [WebGPU `GPUBlendOperation`]( -/// https://gpuweb.github.io/gpuweb/#enumdef-gpublendoperation). -/// -/// For further details on how the blend operations are applied, see -/// the analogous functionality in OpenGL: <https://www.khronos.org/opengl/wiki/Blending#Blend_Equations>. -#[repr(C)] -#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] -pub enum BlendOperation { - /// Src + Dst - #[default] - Add = 0, - /// Src - Dst - Subtract = 1, - /// Dst - Src - ReverseSubtract = 2, - /// min(Src, Dst) - Min = 3, - /// max(Src, Dst) - Max = 4, -} - -/// Describes a blend component of a [`BlendState`]. -/// -/// Corresponds to [WebGPU `GPUBlendComponent`]( -/// https://gpuweb.github.io/gpuweb/#dictdef-gpublendcomponent). -#[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct BlendComponent { - /// Multiplier for the source, which is produced by the fragment shader. - pub src_factor: BlendFactor, - /// Multiplier for the destination, which is stored in the target. - pub dst_factor: BlendFactor, - /// The binary operation applied to the source and destination, - /// multiplied by their respective factors. - pub operation: BlendOperation, -} - -impl BlendComponent { - /// Default blending state that replaces destination with the source. - pub const REPLACE: Self = Self { - src_factor: BlendFactor::One, - dst_factor: BlendFactor::Zero, - operation: BlendOperation::Add, - }; - - /// Blend state of `(1 * src) + ((1 - src_alpha) * dst)`. - pub const OVER: Self = Self { - src_factor: BlendFactor::One, - dst_factor: BlendFactor::OneMinusSrcAlpha, - operation: BlendOperation::Add, - }; - - /// Returns true if the state relies on the constant color, which is - /// set independently on a render command encoder. - #[must_use] - pub fn uses_constant(&self) -> bool { - match (self.src_factor, self.dst_factor) { - (BlendFactor::Constant, _) - | (BlendFactor::OneMinusConstant, _) - | (_, BlendFactor::Constant) - | (_, BlendFactor::OneMinusConstant) => true, - (_, _) => false, - } - } -} - -impl Default for BlendComponent { - fn default() -> Self { - Self::REPLACE - } -} - -/// Describe the blend state of a render pipeline, -/// within [`ColorTargetState`]. -/// -/// Corresponds to [WebGPU `GPUBlendState`]( -/// https://gpuweb.github.io/gpuweb/#dictdef-gpublendstate). -#[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct BlendState { - /// Color equation. - pub color: BlendComponent, - /// Alpha equation. - pub alpha: BlendComponent, -} - -impl BlendState { - /// Blend mode that does no color blending, just overwrites the output with the contents of the shader. - pub const REPLACE: Self = Self { - color: BlendComponent::REPLACE, - alpha: BlendComponent::REPLACE, - }; - - /// Blend mode that does standard alpha blending with non-premultiplied alpha. - pub const ALPHA_BLENDING: Self = Self { - color: BlendComponent { - src_factor: BlendFactor::SrcAlpha, - dst_factor: BlendFactor::OneMinusSrcAlpha, - operation: BlendOperation::Add, - }, - alpha: BlendComponent::OVER, - }; - - /// Blend mode that does standard alpha blending with premultiplied alpha. - pub const PREMULTIPLIED_ALPHA_BLENDING: Self = Self { - color: BlendComponent::OVER, - alpha: BlendComponent::OVER, - }; -} - -/// Describes the color state of a render pipeline. -/// -/// Corresponds to [WebGPU `GPUColorTargetState`]( -/// https://gpuweb.github.io/gpuweb/#dictdef-gpucolortargetstate). -#[repr(C)] -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct ColorTargetState { - /// The [`TextureFormat`] of the image that this pipeline will render to. Must match the format - /// of the corresponding color attachment in [`CommandEncoder::begin_render_pass`][CEbrp] - /// - /// [CEbrp]: ../wgpu/struct.CommandEncoder.html#method.begin_render_pass - pub format: TextureFormat, - /// The blending that is used for this pipeline. - #[cfg_attr(feature = "serde", serde(default))] - pub blend: Option<BlendState>, - /// Mask which enables/disables writes to different color/alpha channel. - #[cfg_attr(feature = "serde", serde(default))] - pub write_mask: ColorWrites, -} - -impl From<TextureFormat> for ColorTargetState { - fn from(format: TextureFormat) -> Self { - Self { - format, - blend: None, - write_mask: ColorWrites::ALL, - } - } -} - -/// Primitive type the input mesh is composed of. -/// -/// Corresponds to [WebGPU `GPUPrimitiveTopology`]( -/// https://gpuweb.github.io/gpuweb/#enumdef-gpuprimitivetopology). -#[repr(C)] -#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] -pub enum PrimitiveTopology { - /// Vertex data is a list of points. Each vertex is a new point. - PointList = 0, - /// Vertex data is a list of lines. Each pair of vertices composes a new line. - /// - /// Vertices `0 1 2 3` create two lines `0 1` and `2 3` - LineList = 1, - /// Vertex data is a strip of lines. Each set of two adjacent vertices form a line. - /// - /// Vertices `0 1 2 3` create three lines `0 1`, `1 2`, and `2 3`. - LineStrip = 2, - /// Vertex data is a list of triangles. Each set of 3 vertices composes a new triangle. - /// - /// Vertices `0 1 2 3 4 5` create two triangles `0 1 2` and `3 4 5` - #[default] - TriangleList = 3, - /// Vertex data is a triangle strip. Each set of three adjacent vertices form a triangle. - /// - /// Vertices `0 1 2 3 4 5` create four triangles `0 1 2`, `2 1 3`, `2 3 4`, and `4 3 5` - TriangleStrip = 4, -} - -impl PrimitiveTopology { - /// Returns true for strip topologies. - #[must_use] - pub fn is_strip(&self) -> bool { - match *self { - Self::PointList | Self::LineList | Self::TriangleList => false, - Self::LineStrip | Self::TriangleStrip => true, - } - } -} - -/// Vertex winding order which classifies the "front" face of a triangle. -/// -/// Corresponds to [WebGPU `GPUFrontFace`]( -/// https://gpuweb.github.io/gpuweb/#enumdef-gpufrontface). -#[repr(C)] -#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] -pub enum FrontFace { - /// Triangles with vertices in counter clockwise order are considered the front face. - /// - /// This is the default with right handed coordinate spaces. - #[default] - Ccw = 0, - /// Triangles with vertices in clockwise order are considered the front face. - /// - /// This is the default with left handed coordinate spaces. - Cw = 1, -} - -/// Face of a vertex. -/// -/// Corresponds to [WebGPU `GPUCullMode`]( -/// https://gpuweb.github.io/gpuweb/#enumdef-gpucullmode), -/// except that the `"none"` value is represented using `Option<Face>` instead. -#[repr(C)] -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] -pub enum Face { - /// Front face - Front = 0, - /// Back face - Back = 1, -} - -/// Type of drawing mode for polygons -#[repr(C)] -#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] -pub enum PolygonMode { - /// Polygons are filled - #[default] - Fill = 0, - /// Polygons are drawn as line segments - Line = 1, - /// Polygons are drawn as points - Point = 2, -} - -/// Describes the state of primitive assembly and rasterization in a render pipeline. -/// -/// Corresponds to [WebGPU `GPUPrimitiveState`]( -/// https://gpuweb.github.io/gpuweb/#dictdef-gpuprimitivestate). -#[repr(C)] -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct PrimitiveState { - /// The primitive topology used to interpret vertices. - pub topology: PrimitiveTopology, - /// When drawing strip topologies with indices, this is the required format for the index buffer. - /// This has no effect on non-indexed or non-strip draws. - /// - /// Specifying this value enables primitive restart, allowing individual strips to be separated - /// with the index value `0xFFFF` when using `Uint16`, or `0xFFFFFFFF` when using `Uint32`. - #[cfg_attr(feature = "serde", serde(default))] - pub strip_index_format: Option<IndexFormat>, - /// The face to consider the front for the purpose of culling and stencil operations. - #[cfg_attr(feature = "serde", serde(default))] - pub front_face: FrontFace, - /// The face culling mode. - #[cfg_attr(feature = "serde", serde(default))] - pub cull_mode: Option<Face>, - /// If set to true, the polygon depth is not clipped to 0-1 before rasterization. - /// - /// Enabling this requires [`Features::DEPTH_CLIP_CONTROL`] to be enabled. - #[cfg_attr(feature = "serde", serde(default))] - pub unclipped_depth: bool, - /// Controls the way each polygon is rasterized. Can be either `Fill` (default), `Line` or `Point` - /// - /// Setting this to `Line` requires [`Features::POLYGON_MODE_LINE`] to be enabled. - /// - /// Setting this to `Point` requires [`Features::POLYGON_MODE_POINT`] to be enabled. - #[cfg_attr(feature = "serde", serde(default))] - pub polygon_mode: PolygonMode, - /// If set to true, the primitives are rendered with conservative overestimation. I.e. any rastered pixel touched by it is filled. - /// Only valid for `[PolygonMode::Fill`]! - /// - /// Enabling this requires [`Features::CONSERVATIVE_RASTERIZATION`] to be enabled. - pub conservative: bool, -} - -/// Describes the multi-sampling state of a render pipeline. -/// -/// Corresponds to [WebGPU `GPUMultisampleState`]( -/// https://gpuweb.github.io/gpuweb/#dictdef-gpumultisamplestate). -#[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct MultisampleState { - /// The number of samples calculated per pixel (for MSAA). For non-multisampled textures, - /// this should be `1` - pub count: u32, - /// Bitmask that restricts the samples of a pixel modified by this pipeline. All samples - /// can be enabled using the value `!0` - pub mask: u64, - /// When enabled, produces another sample mask per pixel based on the alpha output value, that - /// is ANDed with the sample mask and the primitive coverage to restrict the set of samples - /// affected by a primitive. - /// - /// The implicit mask produced for alpha of zero is guaranteed to be zero, and for alpha of one - /// is guaranteed to be all 1-s. - pub alpha_to_coverage_enabled: bool, -} - -impl Default for MultisampleState { - fn default() -> Self { - MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - } - } -} - -bitflags::bitflags! { - /// Feature flags for a texture format. - #[repr(transparent)] - #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] - #[cfg_attr(feature = "serde", serde(transparent))] - #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] - pub struct TextureFormatFeatureFlags: u32 { - /// If not present, the texture can't be sampled with a filtering sampler. - /// This may overwrite TextureSampleType::Float.filterable - const FILTERABLE = 1 << 0; - /// Allows [`TextureDescriptor::sample_count`] to be `2`. - const MULTISAMPLE_X2 = 1 << 1; - /// Allows [`TextureDescriptor::sample_count`] to be `4`. - const MULTISAMPLE_X4 = 1 << 2 ; - /// Allows [`TextureDescriptor::sample_count`] to be `8`. - const MULTISAMPLE_X8 = 1 << 3 ; - /// Allows [`TextureDescriptor::sample_count`] to be `16`. - const MULTISAMPLE_X16 = 1 << 4; - /// Allows a texture of this format to back a view passed as `resolve_target` - /// to a render pass for an automatic driver-implemented resolve. - const MULTISAMPLE_RESOLVE = 1 << 5; - /// When used as a STORAGE texture, then a texture with this format can be bound with - /// [`StorageTextureAccess::ReadOnly`]. - const STORAGE_READ_ONLY = 1 << 6; - /// When used as a STORAGE texture, then a texture with this format can be bound with - /// [`StorageTextureAccess::WriteOnly`]. - const STORAGE_WRITE_ONLY = 1 << 7; - /// When used as a STORAGE texture, then a texture with this format can be bound with - /// [`StorageTextureAccess::ReadWrite`]. - const STORAGE_READ_WRITE = 1 << 8; - /// When used as a STORAGE texture, then a texture with this format can be bound with - /// [`StorageTextureAccess::Atomic`]. - const STORAGE_ATOMIC = 1 << 9; - /// If not present, the texture can't be blended into the render target. - const BLENDABLE = 1 << 10; - } -} - -impl TextureFormatFeatureFlags { - /// Sample count supported by a given texture format. - /// - /// returns `true` if `count` is a supported sample count. - #[must_use] - pub fn sample_count_supported(&self, count: u32) -> bool { - use TextureFormatFeatureFlags as tfsc; - - match count { - 1 => true, - 2 => self.contains(tfsc::MULTISAMPLE_X2), - 4 => self.contains(tfsc::MULTISAMPLE_X4), - 8 => self.contains(tfsc::MULTISAMPLE_X8), - 16 => self.contains(tfsc::MULTISAMPLE_X16), - _ => false, - } - } - - /// A `Vec` of supported sample counts. - #[must_use] - pub fn supported_sample_counts(&self) -> Vec<u32> { - let all_possible_sample_counts: [u32; 5] = [1, 2, 4, 8, 16]; - all_possible_sample_counts - .into_iter() - .filter(|&sc| self.sample_count_supported(sc)) - .collect() - } -} - -/// Features supported by a given texture format -/// -/// Features are defined by WebGPU specification unless [`Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES`] is enabled. -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct TextureFormatFeatures { - /// Valid bits for `TextureDescriptor::Usage` provided for format creation. - pub allowed_usages: TextureUsages, - /// Additional property flags for the format. - pub flags: TextureFormatFeatureFlags, -} - -/// ASTC block dimensions -#[repr(C)] -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum AstcBlock { - /// 4x4 block compressed texture. 16 bytes per block (8 bit/px). - B4x4, - /// 5x4 block compressed texture. 16 bytes per block (6.4 bit/px). - B5x4, - /// 5x5 block compressed texture. 16 bytes per block (5.12 bit/px). - B5x5, - /// 6x5 block compressed texture. 16 bytes per block (4.27 bit/px). - B6x5, - /// 6x6 block compressed texture. 16 bytes per block (3.56 bit/px). - B6x6, - /// 8x5 block compressed texture. 16 bytes per block (3.2 bit/px). - B8x5, - /// 8x6 block compressed texture. 16 bytes per block (2.67 bit/px). - B8x6, - /// 8x8 block compressed texture. 16 bytes per block (2 bit/px). - B8x8, - /// 10x5 block compressed texture. 16 bytes per block (2.56 bit/px). - B10x5, - /// 10x6 block compressed texture. 16 bytes per block (2.13 bit/px). - B10x6, - /// 10x8 block compressed texture. 16 bytes per block (1.6 bit/px). - B10x8, - /// 10x10 block compressed texture. 16 bytes per block (1.28 bit/px). - B10x10, - /// 12x10 block compressed texture. 16 bytes per block (1.07 bit/px). - B12x10, - /// 12x12 block compressed texture. 16 bytes per block (0.89 bit/px). - B12x12, -} - -/// ASTC RGBA channel -#[repr(C)] -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum AstcChannel { - /// 8 bit integer RGBA, [0, 255] converted to/from linear-color float [0, 1] in shader. - /// - /// [`Features::TEXTURE_COMPRESSION_ASTC`] must be enabled to use this channel. - Unorm, - /// 8 bit integer RGBA, Srgb-color [0, 255] converted to/from linear-color float [0, 1] in shader. - /// - /// [`Features::TEXTURE_COMPRESSION_ASTC`] must be enabled to use this channel. - UnormSrgb, - /// floating-point RGBA, linear-color float can be outside of the [0, 1] range. - /// - /// [`Features::TEXTURE_COMPRESSION_ASTC_HDR`] must be enabled to use this channel. - Hdr, -} - -/// Format in which a texture’s texels are stored in GPU memory. -/// -/// Certain formats additionally specify a conversion. -/// When these formats are used in a shader, the conversion automatically takes place when loading -/// from or storing to the texture. -/// -/// * `Unorm` formats linearly scale the integer range of the storage format to a floating-point -/// range of 0 to 1, inclusive. -/// * `Snorm` formats linearly scale the integer range of the storage format to a floating-point -/// range of −1 to 1, inclusive, except that the most negative value -/// (−128 for 8-bit, −32768 for 16-bit) is excluded; on conversion, -/// it is treated as identical to the second most negative -/// (−127 for 8-bit, −32767 for 16-bit), -/// so that the positive and negative ranges are symmetric. -/// * `UnormSrgb` formats apply the [sRGB transfer function] so that the storage is sRGB encoded -/// while the shader works with linear intensity values. -/// * `Uint`, `Sint`, and `Float` formats perform no conversion. -/// -/// Corresponds to [WebGPU `GPUTextureFormat`]( -/// https://gpuweb.github.io/gpuweb/#enumdef-gputextureformat). -/// -/// [sRGB transfer function]: https://en.wikipedia.org/wiki/SRGB#Transfer_function_(%22gamma%22) -#[repr(C)] -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] -pub enum TextureFormat { - // Normal 8 bit formats - /// Red channel only. 8 bit integer per channel. [0, 255] converted to/from float [0, 1] in shader. - R8Unorm, - /// Red channel only. 8 bit integer per channel. [−127, 127] converted to/from float [−1, 1] in shader. - R8Snorm, - /// Red channel only. 8 bit integer per channel. Unsigned in shader. - R8Uint, - /// Red channel only. 8 bit integer per channel. Signed in shader. - R8Sint, - - // Normal 16 bit formats - /// Red channel only. 16 bit integer per channel. Unsigned in shader. - R16Uint, - /// Red channel only. 16 bit integer per channel. Signed in shader. - R16Sint, - /// Red channel only. 16 bit integer per channel. [0, 65535] converted to/from float [0, 1] in shader. - /// - /// [`Features::TEXTURE_FORMAT_16BIT_NORM`] must be enabled to use this texture format. - R16Unorm, - /// Red channel only. 16 bit integer per channel. [−32767, 32767] converted to/from float [−1, 1] in shader. - /// - /// [`Features::TEXTURE_FORMAT_16BIT_NORM`] must be enabled to use this texture format. - R16Snorm, - /// Red channel only. 16 bit float per channel. Float in shader. - R16Float, - /// Red and green channels. 8 bit integer per channel. [0, 255] converted to/from float [0, 1] in shader. - Rg8Unorm, - /// Red and green channels. 8 bit integer per channel. [−127, 127] converted to/from float [−1, 1] in shader. - Rg8Snorm, - /// Red and green channels. 8 bit integer per channel. Unsigned in shader. - Rg8Uint, - /// Red and green channels. 8 bit integer per channel. Signed in shader. - Rg8Sint, - - // Normal 32 bit formats - /// Red channel only. 32 bit integer per channel. Unsigned in shader. - R32Uint, - /// Red channel only. 32 bit integer per channel. Signed in shader. - R32Sint, - /// Red channel only. 32 bit float per channel. Float in shader. - R32Float, - /// Red and green channels. 16 bit integer per channel. Unsigned in shader. - Rg16Uint, - /// Red and green channels. 16 bit integer per channel. Signed in shader. - Rg16Sint, - /// Red and green channels. 16 bit integer per channel. [0, 65535] converted to/from float [0, 1] in shader. - /// - /// [`Features::TEXTURE_FORMAT_16BIT_NORM`] must be enabled to use this texture format. - Rg16Unorm, - /// Red and green channels. 16 bit integer per channel. [−32767, 32767] converted to/from float [−1, 1] in shader. - /// - /// [`Features::TEXTURE_FORMAT_16BIT_NORM`] must be enabled to use this texture format. - Rg16Snorm, - /// Red and green channels. 16 bit float per channel. Float in shader. - Rg16Float, - /// Red, green, blue, and alpha channels. 8 bit integer per channel. [0, 255] converted to/from float [0, 1] in shader. - Rgba8Unorm, - /// Red, green, blue, and alpha channels. 8 bit integer per channel. Srgb-color [0, 255] converted to/from linear-color float [0, 1] in shader. - Rgba8UnormSrgb, - /// Red, green, blue, and alpha channels. 8 bit integer per channel. [−127, 127] converted to/from float [−1, 1] in shader. - Rgba8Snorm, - /// Red, green, blue, and alpha channels. 8 bit integer per channel. Unsigned in shader. - Rgba8Uint, - /// Red, green, blue, and alpha channels. 8 bit integer per channel. Signed in shader. - Rgba8Sint, - /// Blue, green, red, and alpha channels. 8 bit integer per channel. [0, 255] converted to/from float [0, 1] in shader. - Bgra8Unorm, - /// Blue, green, red, and alpha channels. 8 bit integer per channel. Srgb-color [0, 255] converted to/from linear-color float [0, 1] in shader. - Bgra8UnormSrgb, - - // Packed 32 bit formats - /// Packed unsigned float with 9 bits mantisa for each RGB component, then a common 5 bits exponent - Rgb9e5Ufloat, - /// Red, green, blue, and alpha channels. 10 bit integer for RGB channels, 2 bit integer for alpha channel. Unsigned in shader. - Rgb10a2Uint, - /// Red, green, blue, and alpha channels. 10 bit integer for RGB channels, 2 bit integer for alpha channel. [0, 1023] ([0, 3] for alpha) converted to/from float [0, 1] in shader. - Rgb10a2Unorm, - /// Red, green, and blue channels. 11 bit float with no sign bit for RG channels. 10 bit float with no sign bit for blue channel. Float in shader. - Rg11b10Ufloat, - - // Normal 64 bit formats - /// Red channel only. 64 bit integer per channel. Unsigned in shader. - /// - /// [`Features::TEXTURE_INT64_ATOMIC`] must be enabled to use this texture format. - R64Uint, - /// Red and green channels. 32 bit integer per channel. Unsigned in shader. - Rg32Uint, - /// Red and green channels. 32 bit integer per channel. Signed in shader. - Rg32Sint, - /// Red and green channels. 32 bit float per channel. Float in shader. - Rg32Float, - /// Red, green, blue, and alpha channels. 16 bit integer per channel. Unsigned in shader. - Rgba16Uint, - /// Red, green, blue, and alpha channels. 16 bit integer per channel. Signed in shader. - Rgba16Sint, - /// Red, green, blue, and alpha channels. 16 bit integer per channel. [0, 65535] converted to/from float [0, 1] in shader. - /// - /// [`Features::TEXTURE_FORMAT_16BIT_NORM`] must be enabled to use this texture format. - Rgba16Unorm, - /// Red, green, blue, and alpha. 16 bit integer per channel. [−32767, 32767] converted to/from float [−1, 1] in shader. - /// - /// [`Features::TEXTURE_FORMAT_16BIT_NORM`] must be enabled to use this texture format. - Rgba16Snorm, - /// Red, green, blue, and alpha channels. 16 bit float per channel. Float in shader. - Rgba16Float, - - // Normal 128 bit formats - /// Red, green, blue, and alpha channels. 32 bit integer per channel. Unsigned in shader. - Rgba32Uint, - /// Red, green, blue, and alpha channels. 32 bit integer per channel. Signed in shader. - Rgba32Sint, - /// Red, green, blue, and alpha channels. 32 bit float per channel. Float in shader. - Rgba32Float, - - // Depth and stencil formats - /// Stencil format with 8 bit integer stencil. - Stencil8, - /// Special depth format with 16 bit integer depth. - Depth16Unorm, - /// Special depth format with at least 24 bit integer depth. - Depth24Plus, - /// Special depth/stencil format with at least 24 bit integer depth and 8 bits integer stencil. - Depth24PlusStencil8, - /// Special depth format with 32 bit floating point depth. - Depth32Float, - /// Special depth/stencil format with 32 bit floating point depth and 8 bits integer stencil. - /// - /// [`Features::DEPTH32FLOAT_STENCIL8`] must be enabled to use this texture format. - Depth32FloatStencil8, - - /// YUV 4:2:0 chroma subsampled format. - /// - /// Contains two planes: - /// - 0: Single 8 bit channel luminance. - /// - 1: Dual 8 bit channel chrominance at half width and half height. - /// - /// Valid view formats for luminance are [`TextureFormat::R8Unorm`]. - /// - /// Valid view formats for chrominance are [`TextureFormat::Rg8Unorm`]. - /// - /// Width and height must be even. - /// - /// [`Features::TEXTURE_FORMAT_NV12`] must be enabled to use this texture format. - NV12, - - /// YUV 4:2:0 chroma subsampled format. - /// - /// Contains two planes: - /// - 0: Single 16 bit channel luminance, of which only the high 10 bits - /// are used. - /// - 1: Dual 16 bit channel chrominance at half width and half height, of - /// which only the high 10 bits are used. - /// - /// Valid view formats for luminance are [`TextureFormat::R16Unorm`]. - /// - /// Valid view formats for chrominance are [`TextureFormat::Rg16Unorm`]. - /// - /// Width and height must be even. - /// - /// [`Features::TEXTURE_FORMAT_P010`] must be enabled to use this texture format. - P010, - - // Compressed textures usable with `TEXTURE_COMPRESSION_BC` feature. `TEXTURE_COMPRESSION_SLICED_3D` is required to use with 3D textures. - /// 4x4 block compressed texture. 8 bytes per block (4 bit/px). 4 color + alpha pallet. 5 bit R + 6 bit G + 5 bit B + 1 bit alpha. - /// [0, 63] ([0, 1] for alpha) converted to/from float [0, 1] in shader. - /// - /// Also known as DXT1. - /// - /// [`Features::TEXTURE_COMPRESSION_BC`] must be enabled to use this texture format. - /// [`Features::TEXTURE_COMPRESSION_BC_SLICED_3D`] must be enabled to use this texture format with 3D dimension. - Bc1RgbaUnorm, - /// 4x4 block compressed texture. 8 bytes per block (4 bit/px). 4 color + alpha pallet. 5 bit R + 6 bit G + 5 bit B + 1 bit alpha. - /// Srgb-color [0, 63] ([0, 1] for alpha) converted to/from linear-color float [0, 1] in shader. - /// - /// Also known as DXT1. - /// - /// [`Features::TEXTURE_COMPRESSION_BC`] must be enabled to use this texture format. - /// [`Features::TEXTURE_COMPRESSION_BC_SLICED_3D`] must be enabled to use this texture format with 3D dimension. - Bc1RgbaUnormSrgb, - /// 4x4 block compressed texture. 16 bytes per block (8 bit/px). 4 color pallet. 5 bit R + 6 bit G + 5 bit B + 4 bit alpha. - /// [0, 63] ([0, 15] for alpha) converted to/from float [0, 1] in shader. - /// - /// Also known as DXT3. - /// - /// [`Features::TEXTURE_COMPRESSION_BC`] must be enabled to use this texture format. - /// [`Features::TEXTURE_COMPRESSION_BC_SLICED_3D`] must be enabled to use this texture format with 3D dimension. - Bc2RgbaUnorm, - /// 4x4 block compressed texture. 16 bytes per block (8 bit/px). 4 color pallet. 5 bit R + 6 bit G + 5 bit B + 4 bit alpha. - /// Srgb-color [0, 63] ([0, 255] for alpha) converted to/from linear-color float [0, 1] in shader. - /// - /// Also known as DXT3. - /// - /// [`Features::TEXTURE_COMPRESSION_BC`] must be enabled to use this texture format. - /// [`Features::TEXTURE_COMPRESSION_BC_SLICED_3D`] must be enabled to use this texture format with 3D dimension. - Bc2RgbaUnormSrgb, - /// 4x4 block compressed texture. 16 bytes per block (8 bit/px). 4 color pallet + 8 alpha pallet. 5 bit R + 6 bit G + 5 bit B + 8 bit alpha. - /// [0, 63] ([0, 255] for alpha) converted to/from float [0, 1] in shader. - /// - /// Also known as DXT5. - /// - /// [`Features::TEXTURE_COMPRESSION_BC`] must be enabled to use this texture format. - /// [`Features::TEXTURE_COMPRESSION_BC_SLICED_3D`] must be enabled to use this texture format with 3D dimension. - Bc3RgbaUnorm, - /// 4x4 block compressed texture. 16 bytes per block (8 bit/px). 4 color pallet + 8 alpha pallet. 5 bit R + 6 bit G + 5 bit B + 8 bit alpha. - /// Srgb-color [0, 63] ([0, 255] for alpha) converted to/from linear-color float [0, 1] in shader. - /// - /// Also known as DXT5. - /// - /// [`Features::TEXTURE_COMPRESSION_BC`] must be enabled to use this texture format. - /// [`Features::TEXTURE_COMPRESSION_BC_SLICED_3D`] must be enabled to use this texture format with 3D dimension. - Bc3RgbaUnormSrgb, - /// 4x4 block compressed texture. 8 bytes per block (4 bit/px). 8 color pallet. 8 bit R. - /// [0, 255] converted to/from float [0, 1] in shader. - /// - /// Also known as RGTC1. - /// - /// [`Features::TEXTURE_COMPRESSION_BC`] must be enabled to use this texture format. - /// [`Features::TEXTURE_COMPRESSION_BC_SLICED_3D`] must be enabled to use this texture format with 3D dimension. - Bc4RUnorm, - /// 4x4 block compressed texture. 8 bytes per block (4 bit/px). 8 color pallet. 8 bit R. - /// [−127, 127] converted to/from float [−1, 1] in shader. - /// - /// Also known as RGTC1. - /// - /// [`Features::TEXTURE_COMPRESSION_BC`] must be enabled to use this texture format. - /// [`Features::TEXTURE_COMPRESSION_BC_SLICED_3D`] must be enabled to use this texture format with 3D dimension. - Bc4RSnorm, - /// 4x4 block compressed texture. 16 bytes per block (8 bit/px). 8 color red pallet + 8 color green pallet. 8 bit RG. - /// [0, 255] converted to/from float [0, 1] in shader. - /// - /// Also known as RGTC2. - /// - /// [`Features::TEXTURE_COMPRESSION_BC`] must be enabled to use this texture format. - /// [`Features::TEXTURE_COMPRESSION_BC_SLICED_3D`] must be enabled to use this texture format with 3D dimension. - Bc5RgUnorm, - /// 4x4 block compressed texture. 16 bytes per block (8 bit/px). 8 color red pallet + 8 color green pallet. 8 bit RG. - /// [−127, 127] converted to/from float [−1, 1] in shader. - /// - /// Also known as RGTC2. - /// - /// [`Features::TEXTURE_COMPRESSION_BC`] must be enabled to use this texture format. - /// [`Features::TEXTURE_COMPRESSION_BC_SLICED_3D`] must be enabled to use this texture format with 3D dimension. - Bc5RgSnorm, - /// 4x4 block compressed texture. 16 bytes per block (8 bit/px). Variable sized pallet. 16 bit unsigned float RGB. Float in shader. - /// - /// Also known as BPTC (float). - /// - /// [`Features::TEXTURE_COMPRESSION_BC`] must be enabled to use this texture format. - /// [`Features::TEXTURE_COMPRESSION_BC_SLICED_3D`] must be enabled to use this texture format with 3D dimension. - Bc6hRgbUfloat, - /// 4x4 block compressed texture. 16 bytes per block (8 bit/px). Variable sized pallet. 16 bit signed float RGB. Float in shader. - /// - /// Also known as BPTC (float). - /// - /// [`Features::TEXTURE_COMPRESSION_BC`] must be enabled to use this texture format. - /// [`Features::TEXTURE_COMPRESSION_BC_SLICED_3D`] must be enabled to use this texture format with 3D dimension. - Bc6hRgbFloat, - /// 4x4 block compressed texture. 16 bytes per block (8 bit/px). Variable sized pallet. 8 bit integer RGBA. - /// [0, 255] converted to/from float [0, 1] in shader. - /// - /// Also known as BPTC (unorm). - /// - /// [`Features::TEXTURE_COMPRESSION_BC`] must be enabled to use this texture format. - /// [`Features::TEXTURE_COMPRESSION_BC_SLICED_3D`] must be enabled to use this texture format with 3D dimension. - Bc7RgbaUnorm, - /// 4x4 block compressed texture. 16 bytes per block (8 bit/px). Variable sized pallet. 8 bit integer RGBA. - /// Srgb-color [0, 255] converted to/from linear-color float [0, 1] in shader. - /// - /// Also known as BPTC (unorm). - /// - /// [`Features::TEXTURE_COMPRESSION_BC`] must be enabled to use this texture format. - /// [`Features::TEXTURE_COMPRESSION_BC_SLICED_3D`] must be enabled to use this texture format with 3D dimension. - Bc7RgbaUnormSrgb, - /// 4x4 block compressed texture. 8 bytes per block (4 bit/px). Complex pallet. 8 bit integer RGB. - /// [0, 255] converted to/from float [0, 1] in shader. - /// - /// [`Features::TEXTURE_COMPRESSION_ETC2`] must be enabled to use this texture format. - Etc2Rgb8Unorm, - /// 4x4 block compressed texture. 8 bytes per block (4 bit/px). Complex pallet. 8 bit integer RGB. - /// Srgb-color [0, 255] converted to/from linear-color float [0, 1] in shader. - /// - /// [`Features::TEXTURE_COMPRESSION_ETC2`] must be enabled to use this texture format. - Etc2Rgb8UnormSrgb, - /// 4x4 block compressed texture. 8 bytes per block (4 bit/px). Complex pallet. 8 bit integer RGB + 1 bit alpha. - /// [0, 255] ([0, 1] for alpha) converted to/from float [0, 1] in shader. - /// - /// [`Features::TEXTURE_COMPRESSION_ETC2`] must be enabled to use this texture format. - Etc2Rgb8A1Unorm, - /// 4x4 block compressed texture. 8 bytes per block (4 bit/px). Complex pallet. 8 bit integer RGB + 1 bit alpha. - /// Srgb-color [0, 255] ([0, 1] for alpha) converted to/from linear-color float [0, 1] in shader. - /// - /// [`Features::TEXTURE_COMPRESSION_ETC2`] must be enabled to use this texture format. - Etc2Rgb8A1UnormSrgb, - /// 4x4 block compressed texture. 16 bytes per block (8 bit/px). Complex pallet. 8 bit integer RGB + 8 bit alpha. - /// [0, 255] converted to/from float [0, 1] in shader. - /// - /// [`Features::TEXTURE_COMPRESSION_ETC2`] must be enabled to use this texture format. - Etc2Rgba8Unorm, - /// 4x4 block compressed texture. 16 bytes per block (8 bit/px). Complex pallet. 8 bit integer RGB + 8 bit alpha. - /// Srgb-color [0, 255] converted to/from linear-color float [0, 1] in shader. - /// - /// [`Features::TEXTURE_COMPRESSION_ETC2`] must be enabled to use this texture format. - Etc2Rgba8UnormSrgb, - /// 4x4 block compressed texture. 8 bytes per block (4 bit/px). Complex pallet. 11 bit integer R. - /// [0, 255] converted to/from float [0, 1] in shader. - /// - /// [`Features::TEXTURE_COMPRESSION_ETC2`] must be enabled to use this texture format. - EacR11Unorm, - /// 4x4 block compressed texture. 8 bytes per block (4 bit/px). Complex pallet. 11 bit integer R. - /// [−127, 127] converted to/from float [−1, 1] in shader. - /// - /// [`Features::TEXTURE_COMPRESSION_ETC2`] must be enabled to use this texture format. - EacR11Snorm, - /// 4x4 block compressed texture. 16 bytes per block (8 bit/px). Complex pallet. 11 bit integer R + 11 bit integer G. - /// [0, 255] converted to/from float [0, 1] in shader. - /// - /// [`Features::TEXTURE_COMPRESSION_ETC2`] must be enabled to use this texture format. - EacRg11Unorm, - /// 4x4 block compressed texture. 16 bytes per block (8 bit/px). Complex pallet. 11 bit integer R + 11 bit integer G. - /// [−127, 127] converted to/from float [−1, 1] in shader. - /// - /// [`Features::TEXTURE_COMPRESSION_ETC2`] must be enabled to use this texture format. - EacRg11Snorm, - /// block compressed texture. 16 bytes per block. - /// - /// Features [`TEXTURE_COMPRESSION_ASTC`] or [`TEXTURE_COMPRESSION_ASTC_HDR`] - /// must be enabled to use this texture format. - /// - /// [`TEXTURE_COMPRESSION_ASTC`]: Features::TEXTURE_COMPRESSION_ASTC - /// [`TEXTURE_COMPRESSION_ASTC_HDR`]: Features::TEXTURE_COMPRESSION_ASTC_HDR - Astc { - /// compressed block dimensions - block: AstcBlock, - /// ASTC RGBA channel - channel: AstcChannel, - }, -} - -#[cfg(any(feature = "serde", test))] -impl<'de> Deserialize<'de> for TextureFormat { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: serde::Deserializer<'de>, - { - use serde::de::{self, Error, Unexpected}; - - struct TextureFormatVisitor; - - impl de::Visitor<'_> for TextureFormatVisitor { - type Value = TextureFormat; - - fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { - formatter.write_str("a valid texture format") - } - - fn visit_str<E: Error>(self, s: &str) -> Result<Self::Value, E> { - let format = match s { - "r8unorm" => TextureFormat::R8Unorm, - "r8snorm" => TextureFormat::R8Snorm, - "r8uint" => TextureFormat::R8Uint, - "r8sint" => TextureFormat::R8Sint, - "r16uint" => TextureFormat::R16Uint, - "r16sint" => TextureFormat::R16Sint, - "r16unorm" => TextureFormat::R16Unorm, - "r16snorm" => TextureFormat::R16Snorm, - "r16float" => TextureFormat::R16Float, - "rg8unorm" => TextureFormat::Rg8Unorm, - "rg8snorm" => TextureFormat::Rg8Snorm, - "rg8uint" => TextureFormat::Rg8Uint, - "rg8sint" => TextureFormat::Rg8Sint, - "r32uint" => TextureFormat::R32Uint, - "r32sint" => TextureFormat::R32Sint, - "r32float" => TextureFormat::R32Float, - "rg16uint" => TextureFormat::Rg16Uint, - "rg16sint" => TextureFormat::Rg16Sint, - "rg16unorm" => TextureFormat::Rg16Unorm, - "rg16snorm" => TextureFormat::Rg16Snorm, - "rg16float" => TextureFormat::Rg16Float, - "rgba8unorm" => TextureFormat::Rgba8Unorm, - "rgba8unorm-srgb" => TextureFormat::Rgba8UnormSrgb, - "rgba8snorm" => TextureFormat::Rgba8Snorm, - "rgba8uint" => TextureFormat::Rgba8Uint, - "rgba8sint" => TextureFormat::Rgba8Sint, - "bgra8unorm" => TextureFormat::Bgra8Unorm, - "bgra8unorm-srgb" => TextureFormat::Bgra8UnormSrgb, - "rgb10a2uint" => TextureFormat::Rgb10a2Uint, - "rgb10a2unorm" => TextureFormat::Rgb10a2Unorm, - "rg11b10ufloat" => TextureFormat::Rg11b10Ufloat, - "r64uint" => TextureFormat::R64Uint, - "rg32uint" => TextureFormat::Rg32Uint, - "rg32sint" => TextureFormat::Rg32Sint, - "rg32float" => TextureFormat::Rg32Float, - "rgba16uint" => TextureFormat::Rgba16Uint, - "rgba16sint" => TextureFormat::Rgba16Sint, - "rgba16unorm" => TextureFormat::Rgba16Unorm, - "rgba16snorm" => TextureFormat::Rgba16Snorm, - "rgba16float" => TextureFormat::Rgba16Float, - "rgba32uint" => TextureFormat::Rgba32Uint, - "rgba32sint" => TextureFormat::Rgba32Sint, - "rgba32float" => TextureFormat::Rgba32Float, - "stencil8" => TextureFormat::Stencil8, - "depth32float" => TextureFormat::Depth32Float, - "depth32float-stencil8" => TextureFormat::Depth32FloatStencil8, - "depth16unorm" => TextureFormat::Depth16Unorm, - "depth24plus" => TextureFormat::Depth24Plus, - "depth24plus-stencil8" => TextureFormat::Depth24PlusStencil8, - "nv12" => TextureFormat::NV12, - "p010" => TextureFormat::P010, - "rgb9e5ufloat" => TextureFormat::Rgb9e5Ufloat, - "bc1-rgba-unorm" => TextureFormat::Bc1RgbaUnorm, - "bc1-rgba-unorm-srgb" => TextureFormat::Bc1RgbaUnormSrgb, - "bc2-rgba-unorm" => TextureFormat::Bc2RgbaUnorm, - "bc2-rgba-unorm-srgb" => TextureFormat::Bc2RgbaUnormSrgb, - "bc3-rgba-unorm" => TextureFormat::Bc3RgbaUnorm, - "bc3-rgba-unorm-srgb" => TextureFormat::Bc3RgbaUnormSrgb, - "bc4-r-unorm" => TextureFormat::Bc4RUnorm, - "bc4-r-snorm" => TextureFormat::Bc4RSnorm, - "bc5-rg-unorm" => TextureFormat::Bc5RgUnorm, - "bc5-rg-snorm" => TextureFormat::Bc5RgSnorm, - "bc6h-rgb-ufloat" => TextureFormat::Bc6hRgbUfloat, - "bc6h-rgb-float" => TextureFormat::Bc6hRgbFloat, - "bc7-rgba-unorm" => TextureFormat::Bc7RgbaUnorm, - "bc7-rgba-unorm-srgb" => TextureFormat::Bc7RgbaUnormSrgb, - "etc2-rgb8unorm" => TextureFormat::Etc2Rgb8Unorm, - "etc2-rgb8unorm-srgb" => TextureFormat::Etc2Rgb8UnormSrgb, - "etc2-rgb8a1unorm" => TextureFormat::Etc2Rgb8A1Unorm, - "etc2-rgb8a1unorm-srgb" => TextureFormat::Etc2Rgb8A1UnormSrgb, - "etc2-rgba8unorm" => TextureFormat::Etc2Rgba8Unorm, - "etc2-rgba8unorm-srgb" => TextureFormat::Etc2Rgba8UnormSrgb, - "eac-r11unorm" => TextureFormat::EacR11Unorm, - "eac-r11snorm" => TextureFormat::EacR11Snorm, - "eac-rg11unorm" => TextureFormat::EacRg11Unorm, - "eac-rg11snorm" => TextureFormat::EacRg11Snorm, - other => { - if let Some(parts) = other.strip_prefix("astc-") { - let (block, channel) = parts - .split_once('-') - .ok_or_else(|| E::invalid_value(Unexpected::Str(s), &self))?; - - let block = match block { - "4x4" => AstcBlock::B4x4, - "5x4" => AstcBlock::B5x4, - "5x5" => AstcBlock::B5x5, - "6x5" => AstcBlock::B6x5, - "6x6" => AstcBlock::B6x6, - "8x5" => AstcBlock::B8x5, - "8x6" => AstcBlock::B8x6, - "8x8" => AstcBlock::B8x8, - "10x5" => AstcBlock::B10x5, - "10x6" => AstcBlock::B10x6, - "10x8" => AstcBlock::B10x8, - "10x10" => AstcBlock::B10x10, - "12x10" => AstcBlock::B12x10, - "12x12" => AstcBlock::B12x12, - _ => return Err(E::invalid_value(Unexpected::Str(s), &self)), - }; - - let channel = match channel { - "unorm" => AstcChannel::Unorm, - "unorm-srgb" => AstcChannel::UnormSrgb, - "hdr" => AstcChannel::Hdr, - _ => return Err(E::invalid_value(Unexpected::Str(s), &self)), - }; - - TextureFormat::Astc { block, channel } - } else { - return Err(E::invalid_value(Unexpected::Str(s), &self)); - } - } - }; - - Ok(format) - } - } - - deserializer.deserialize_str(TextureFormatVisitor) - } -} - -#[cfg(any(feature = "serde", test))] -impl Serialize for TextureFormat { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: serde::Serializer, - { - let s: String; - let name = match *self { - TextureFormat::R8Unorm => "r8unorm", - TextureFormat::R8Snorm => "r8snorm", - TextureFormat::R8Uint => "r8uint", - TextureFormat::R8Sint => "r8sint", - TextureFormat::R16Uint => "r16uint", - TextureFormat::R16Sint => "r16sint", - TextureFormat::R16Unorm => "r16unorm", - TextureFormat::R16Snorm => "r16snorm", - TextureFormat::R16Float => "r16float", - TextureFormat::Rg8Unorm => "rg8unorm", - TextureFormat::Rg8Snorm => "rg8snorm", - TextureFormat::Rg8Uint => "rg8uint", - TextureFormat::Rg8Sint => "rg8sint", - TextureFormat::R32Uint => "r32uint", - TextureFormat::R32Sint => "r32sint", - TextureFormat::R32Float => "r32float", - TextureFormat::Rg16Uint => "rg16uint", - TextureFormat::Rg16Sint => "rg16sint", - TextureFormat::Rg16Unorm => "rg16unorm", - TextureFormat::Rg16Snorm => "rg16snorm", - TextureFormat::Rg16Float => "rg16float", - TextureFormat::Rgba8Unorm => "rgba8unorm", - TextureFormat::Rgba8UnormSrgb => "rgba8unorm-srgb", - TextureFormat::Rgba8Snorm => "rgba8snorm", - TextureFormat::Rgba8Uint => "rgba8uint", - TextureFormat::Rgba8Sint => "rgba8sint", - TextureFormat::Bgra8Unorm => "bgra8unorm", - TextureFormat::Bgra8UnormSrgb => "bgra8unorm-srgb", - TextureFormat::Rgb10a2Uint => "rgb10a2uint", - TextureFormat::Rgb10a2Unorm => "rgb10a2unorm", - TextureFormat::Rg11b10Ufloat => "rg11b10ufloat", - TextureFormat::R64Uint => "r64uint", - TextureFormat::Rg32Uint => "rg32uint", - TextureFormat::Rg32Sint => "rg32sint", - TextureFormat::Rg32Float => "rg32float", - TextureFormat::Rgba16Uint => "rgba16uint", - TextureFormat::Rgba16Sint => "rgba16sint", - TextureFormat::Rgba16Unorm => "rgba16unorm", - TextureFormat::Rgba16Snorm => "rgba16snorm", - TextureFormat::Rgba16Float => "rgba16float", - TextureFormat::Rgba32Uint => "rgba32uint", - TextureFormat::Rgba32Sint => "rgba32sint", - TextureFormat::Rgba32Float => "rgba32float", - TextureFormat::Stencil8 => "stencil8", - TextureFormat::Depth32Float => "depth32float", - TextureFormat::Depth16Unorm => "depth16unorm", - TextureFormat::Depth32FloatStencil8 => "depth32float-stencil8", - TextureFormat::Depth24Plus => "depth24plus", - TextureFormat::Depth24PlusStencil8 => "depth24plus-stencil8", - TextureFormat::NV12 => "nv12", - TextureFormat::P010 => "p010", - TextureFormat::Rgb9e5Ufloat => "rgb9e5ufloat", - TextureFormat::Bc1RgbaUnorm => "bc1-rgba-unorm", - TextureFormat::Bc1RgbaUnormSrgb => "bc1-rgba-unorm-srgb", - TextureFormat::Bc2RgbaUnorm => "bc2-rgba-unorm", - TextureFormat::Bc2RgbaUnormSrgb => "bc2-rgba-unorm-srgb", - TextureFormat::Bc3RgbaUnorm => "bc3-rgba-unorm", - TextureFormat::Bc3RgbaUnormSrgb => "bc3-rgba-unorm-srgb", - TextureFormat::Bc4RUnorm => "bc4-r-unorm", - TextureFormat::Bc4RSnorm => "bc4-r-snorm", - TextureFormat::Bc5RgUnorm => "bc5-rg-unorm", - TextureFormat::Bc5RgSnorm => "bc5-rg-snorm", - TextureFormat::Bc6hRgbUfloat => "bc6h-rgb-ufloat", - TextureFormat::Bc6hRgbFloat => "bc6h-rgb-float", - TextureFormat::Bc7RgbaUnorm => "bc7-rgba-unorm", - TextureFormat::Bc7RgbaUnormSrgb => "bc7-rgba-unorm-srgb", - TextureFormat::Etc2Rgb8Unorm => "etc2-rgb8unorm", - TextureFormat::Etc2Rgb8UnormSrgb => "etc2-rgb8unorm-srgb", - TextureFormat::Etc2Rgb8A1Unorm => "etc2-rgb8a1unorm", - TextureFormat::Etc2Rgb8A1UnormSrgb => "etc2-rgb8a1unorm-srgb", - TextureFormat::Etc2Rgba8Unorm => "etc2-rgba8unorm", - TextureFormat::Etc2Rgba8UnormSrgb => "etc2-rgba8unorm-srgb", - TextureFormat::EacR11Unorm => "eac-r11unorm", - TextureFormat::EacR11Snorm => "eac-r11snorm", - TextureFormat::EacRg11Unorm => "eac-rg11unorm", - TextureFormat::EacRg11Snorm => "eac-rg11snorm", - TextureFormat::Astc { block, channel } => { - let block = match block { - AstcBlock::B4x4 => "4x4", - AstcBlock::B5x4 => "5x4", - AstcBlock::B5x5 => "5x5", - AstcBlock::B6x5 => "6x5", - AstcBlock::B6x6 => "6x6", - AstcBlock::B8x5 => "8x5", - AstcBlock::B8x6 => "8x6", - AstcBlock::B8x8 => "8x8", - AstcBlock::B10x5 => "10x5", - AstcBlock::B10x6 => "10x6", - AstcBlock::B10x8 => "10x8", - AstcBlock::B10x10 => "10x10", - AstcBlock::B12x10 => "12x10", - AstcBlock::B12x12 => "12x12", - }; - - let channel = match channel { - AstcChannel::Unorm => "unorm", - AstcChannel::UnormSrgb => "unorm-srgb", - AstcChannel::Hdr => "hdr", - }; - - s = format!("astc-{block}-{channel}"); - &s - } - }; - serializer.serialize_str(name) - } -} - -impl TextureAspect { - /// Returns the texture aspect for a given plane. - #[must_use] - pub fn from_plane(plane: u32) -> Option<Self> { - Some(match plane { - 0 => Self::Plane0, - 1 => Self::Plane1, - 2 => Self::Plane2, - _ => return None, - }) - } - - /// Returns the plane for a given texture aspect. - #[must_use] - pub fn to_plane(&self) -> Option<u32> { - match self { - TextureAspect::Plane0 => Some(0), - TextureAspect::Plane1 => Some(1), - TextureAspect::Plane2 => Some(2), - _ => None, - } - } -} - -// There are some additional texture format helpers in `wgpu-core/src/conv.rs`, -// that may need to be modified along with the ones here. -impl TextureFormat { - /// Returns the aspect-specific format of the original format - /// - /// see <https://gpuweb.github.io/gpuweb/#abstract-opdef-resolving-gputextureaspect> - #[must_use] - pub fn aspect_specific_format(&self, aspect: TextureAspect) -> Option<Self> { - match (*self, aspect) { - (Self::Stencil8, TextureAspect::StencilOnly) => Some(*self), - ( - Self::Depth16Unorm | Self::Depth24Plus | Self::Depth32Float, - TextureAspect::DepthOnly, - ) => Some(*self), - ( - Self::Depth24PlusStencil8 | Self::Depth32FloatStencil8, - TextureAspect::StencilOnly, - ) => Some(Self::Stencil8), - (Self::Depth24PlusStencil8, TextureAspect::DepthOnly) => Some(Self::Depth24Plus), - (Self::Depth32FloatStencil8, TextureAspect::DepthOnly) => Some(Self::Depth32Float), - (Self::NV12, TextureAspect::Plane0) => Some(Self::R8Unorm), - (Self::NV12, TextureAspect::Plane1) => Some(Self::Rg8Unorm), - (Self::P010, TextureAspect::Plane0) => Some(Self::R16Unorm), - (Self::P010, TextureAspect::Plane1) => Some(Self::Rg16Unorm), - // views to multi-planar formats must specify the plane - (format, TextureAspect::All) if !format.is_multi_planar_format() => Some(format), - _ => None, - } - } - - /// Returns `true` if `self` is a depth or stencil component of the given - /// combined depth-stencil format - #[must_use] - pub fn is_depth_stencil_component(&self, combined_format: Self) -> bool { - match (combined_format, *self) { - (Self::Depth24PlusStencil8, Self::Depth24Plus | Self::Stencil8) - | (Self::Depth32FloatStencil8, Self::Depth32Float | Self::Stencil8) => true, - _ => false, - } - } - - /// Returns `true` if the format is a depth and/or stencil format - /// - /// see <https://gpuweb.github.io/gpuweb/#depth-formats> - #[must_use] - pub fn is_depth_stencil_format(&self) -> bool { - match *self { - Self::Stencil8 - | Self::Depth16Unorm - | Self::Depth24Plus - | Self::Depth24PlusStencil8 - | Self::Depth32Float - | Self::Depth32FloatStencil8 => true, - _ => false, - } - } - - /// Returns `true` if the format is a combined depth-stencil format - /// - /// see <https://gpuweb.github.io/gpuweb/#combined-depth-stencil-format> - #[must_use] - pub fn is_combined_depth_stencil_format(&self) -> bool { - match *self { - Self::Depth24PlusStencil8 | Self::Depth32FloatStencil8 => true, - _ => false, - } - } - - /// Returns `true` if the format is a multi-planar format - #[must_use] - pub fn is_multi_planar_format(&self) -> bool { - self.planes().is_some() - } - - /// Returns the number of planes a multi-planar format has. - #[must_use] - pub fn planes(&self) -> Option<u32> { - match *self { - Self::NV12 => Some(2), - Self::P010 => Some(2), - _ => None, - } - } - - /// Returns the subsampling factor for the indicated plane of a multi-planar format. - #[must_use] - pub fn subsampling_factors(&self, plane: Option<u32>) -> (u32, u32) { - match *self { - Self::NV12 | Self::P010 => match plane { - Some(0) => (1, 1), - Some(1) => (2, 2), - Some(plane) => unreachable!("plane {plane} is not valid for {self:?}"), - None => unreachable!("the plane must be specified for multi-planar formats"), - }, - _ => (1, 1), - } - } - - /// Returns `true` if the format has a color aspect - #[must_use] - pub fn has_color_aspect(&self) -> bool { - !self.is_depth_stencil_format() - } - - /// Returns `true` if the format has a depth aspect - #[must_use] - pub fn has_depth_aspect(&self) -> bool { - match *self { - Self::Depth16Unorm - | Self::Depth24Plus - | Self::Depth24PlusStencil8 - | Self::Depth32Float - | Self::Depth32FloatStencil8 => true, - _ => false, - } - } - - /// Returns `true` if the format has a stencil aspect - #[must_use] - pub fn has_stencil_aspect(&self) -> bool { - match *self { - Self::Stencil8 | Self::Depth24PlusStencil8 | Self::Depth32FloatStencil8 => true, - _ => false, - } - } - - /// Returns the size multiple requirement for a texture using this format. - /// - /// `create_texture` currently enforces a stricter restriction than this for - /// mipmapped multi-planar formats. - /// TODO(<https://github.com/gfx-rs/wgpu/issues/8491>): Remove this note. - #[must_use] - pub fn size_multiple_requirement(&self) -> (u32, u32) { - match *self { - Self::NV12 => (2, 2), - Self::P010 => (2, 2), - _ => self.block_dimensions(), - } - } - - /// Returns the dimension of a [block](https://gpuweb.github.io/gpuweb/#texel-block) of texels. - /// - /// Uncompressed formats have a block dimension of `(1, 1)`. - #[must_use] - pub fn block_dimensions(&self) -> (u32, u32) { - match *self { - Self::R8Unorm - | Self::R8Snorm - | Self::R8Uint - | Self::R8Sint - | Self::R16Uint - | Self::R16Sint - | Self::R16Unorm - | Self::R16Snorm - | Self::R16Float - | Self::Rg8Unorm - | Self::Rg8Snorm - | Self::Rg8Uint - | Self::Rg8Sint - | Self::R32Uint - | Self::R32Sint - | Self::R32Float - | Self::Rg16Uint - | Self::Rg16Sint - | Self::Rg16Unorm - | Self::Rg16Snorm - | Self::Rg16Float - | Self::Rgba8Unorm - | Self::Rgba8UnormSrgb - | Self::Rgba8Snorm - | Self::Rgba8Uint - | Self::Rgba8Sint - | Self::Bgra8Unorm - | Self::Bgra8UnormSrgb - | Self::Rgb9e5Ufloat - | Self::Rgb10a2Uint - | Self::Rgb10a2Unorm - | Self::Rg11b10Ufloat - | Self::R64Uint - | Self::Rg32Uint - | Self::Rg32Sint - | Self::Rg32Float - | Self::Rgba16Uint - | Self::Rgba16Sint - | Self::Rgba16Unorm - | Self::Rgba16Snorm - | Self::Rgba16Float - | Self::Rgba32Uint - | Self::Rgba32Sint - | Self::Rgba32Float - | Self::Stencil8 - | Self::Depth16Unorm - | Self::Depth24Plus - | Self::Depth24PlusStencil8 - | Self::Depth32Float - | Self::Depth32FloatStencil8 - | Self::NV12 - | Self::P010 => (1, 1), - - Self::Bc1RgbaUnorm - | Self::Bc1RgbaUnormSrgb - | Self::Bc2RgbaUnorm - | Self::Bc2RgbaUnormSrgb - | Self::Bc3RgbaUnorm - | Self::Bc3RgbaUnormSrgb - | Self::Bc4RUnorm - | Self::Bc4RSnorm - | Self::Bc5RgUnorm - | Self::Bc5RgSnorm - | Self::Bc6hRgbUfloat - | Self::Bc6hRgbFloat - | Self::Bc7RgbaUnorm - | Self::Bc7RgbaUnormSrgb => (4, 4), - - Self::Etc2Rgb8Unorm - | Self::Etc2Rgb8UnormSrgb - | Self::Etc2Rgb8A1Unorm - | Self::Etc2Rgb8A1UnormSrgb - | Self::Etc2Rgba8Unorm - | Self::Etc2Rgba8UnormSrgb - | Self::EacR11Unorm - | Self::EacR11Snorm - | Self::EacRg11Unorm - | Self::EacRg11Snorm => (4, 4), - - Self::Astc { block, .. } => match block { - AstcBlock::B4x4 => (4, 4), - AstcBlock::B5x4 => (5, 4), - AstcBlock::B5x5 => (5, 5), - AstcBlock::B6x5 => (6, 5), - AstcBlock::B6x6 => (6, 6), - AstcBlock::B8x5 => (8, 5), - AstcBlock::B8x6 => (8, 6), - AstcBlock::B8x8 => (8, 8), - AstcBlock::B10x5 => (10, 5), - AstcBlock::B10x6 => (10, 6), - AstcBlock::B10x8 => (10, 8), - AstcBlock::B10x10 => (10, 10), - AstcBlock::B12x10 => (12, 10), - AstcBlock::B12x12 => (12, 12), - }, - } - } - - /// Returns `true` for compressed formats. - #[must_use] - pub fn is_compressed(&self) -> bool { - self.block_dimensions() != (1, 1) - } - - /// Returns `true` for BCn compressed formats. - #[must_use] - pub fn is_bcn(&self) -> bool { - self.required_features() == Features::TEXTURE_COMPRESSION_BC - } - - /// Returns `true` for ASTC compressed formats. - #[must_use] - pub fn is_astc(&self) -> bool { - self.required_features() == Features::TEXTURE_COMPRESSION_ASTC - || self.required_features() == Features::TEXTURE_COMPRESSION_ASTC_HDR - } - - /// Returns the required features (if any) in order to use the texture. - #[must_use] - pub fn required_features(&self) -> Features { - match *self { - Self::R8Unorm - | Self::R8Snorm - | Self::R8Uint - | Self::R8Sint - | Self::R16Uint - | Self::R16Sint - | Self::R16Float - | Self::Rg8Unorm - | Self::Rg8Snorm - | Self::Rg8Uint - | Self::Rg8Sint - | Self::R32Uint - | Self::R32Sint - | Self::R32Float - | Self::Rg16Uint - | Self::Rg16Sint - | Self::Rg16Float - | Self::Rgba8Unorm - | Self::Rgba8UnormSrgb - | Self::Rgba8Snorm - | Self::Rgba8Uint - | Self::Rgba8Sint - | Self::Bgra8Unorm - | Self::Bgra8UnormSrgb - | Self::Rgb9e5Ufloat - | Self::Rgb10a2Uint - | Self::Rgb10a2Unorm - | Self::Rg11b10Ufloat - | Self::Rg32Uint - | Self::Rg32Sint - | Self::Rg32Float - | Self::Rgba16Uint - | Self::Rgba16Sint - | Self::Rgba16Float - | Self::Rgba32Uint - | Self::Rgba32Sint - | Self::Rgba32Float - | Self::Stencil8 - | Self::Depth16Unorm - | Self::Depth24Plus - | Self::Depth24PlusStencil8 - | Self::Depth32Float => Features::empty(), - - Self::R64Uint => Features::TEXTURE_INT64_ATOMIC, - - Self::Depth32FloatStencil8 => Features::DEPTH32FLOAT_STENCIL8, - - Self::NV12 => Features::TEXTURE_FORMAT_NV12, - Self::P010 => Features::TEXTURE_FORMAT_P010, - - Self::R16Unorm - | Self::R16Snorm - | Self::Rg16Unorm - | Self::Rg16Snorm - | Self::Rgba16Unorm - | Self::Rgba16Snorm => Features::TEXTURE_FORMAT_16BIT_NORM, - - Self::Bc1RgbaUnorm - | Self::Bc1RgbaUnormSrgb - | Self::Bc2RgbaUnorm - | Self::Bc2RgbaUnormSrgb - | Self::Bc3RgbaUnorm - | Self::Bc3RgbaUnormSrgb - | Self::Bc4RUnorm - | Self::Bc4RSnorm - | Self::Bc5RgUnorm - | Self::Bc5RgSnorm - | Self::Bc6hRgbUfloat - | Self::Bc6hRgbFloat - | Self::Bc7RgbaUnorm - | Self::Bc7RgbaUnormSrgb => Features::TEXTURE_COMPRESSION_BC, - - Self::Etc2Rgb8Unorm - | Self::Etc2Rgb8UnormSrgb - | Self::Etc2Rgb8A1Unorm - | Self::Etc2Rgb8A1UnormSrgb - | Self::Etc2Rgba8Unorm - | Self::Etc2Rgba8UnormSrgb - | Self::EacR11Unorm - | Self::EacR11Snorm - | Self::EacRg11Unorm - | Self::EacRg11Snorm => Features::TEXTURE_COMPRESSION_ETC2, - - Self::Astc { channel, .. } => match channel { - AstcChannel::Hdr => Features::TEXTURE_COMPRESSION_ASTC_HDR, - AstcChannel::Unorm | AstcChannel::UnormSrgb => Features::TEXTURE_COMPRESSION_ASTC, - }, - } - } - - /// Returns the format features guaranteed by the WebGPU spec. - /// - /// Additional features are available if `Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES` is enabled. - #[must_use] - pub fn guaranteed_format_features(&self, device_features: Features) -> TextureFormatFeatures { - // Multisampling - let none = TextureFormatFeatureFlags::empty(); - let msaa = TextureFormatFeatureFlags::MULTISAMPLE_X4; - let msaa_resolve = msaa | TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE; - - let s_ro_wo = TextureFormatFeatureFlags::STORAGE_READ_ONLY - | TextureFormatFeatureFlags::STORAGE_WRITE_ONLY; - let s_all = s_ro_wo | TextureFormatFeatureFlags::STORAGE_READ_WRITE; - - // Flags - let basic = - TextureUsages::COPY_SRC | TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING; - let attachment = basic | TextureUsages::RENDER_ATTACHMENT | TextureUsages::TRANSIENT; - let storage = basic | TextureUsages::STORAGE_BINDING; - let binding = TextureUsages::TEXTURE_BINDING; - let all_flags = attachment | storage | binding; - let atomic_64 = if device_features.contains(Features::TEXTURE_ATOMIC) { - storage | binding | TextureUsages::STORAGE_ATOMIC - } else { - storage | binding - }; - let atomic = attachment | atomic_64; - let (rg11b10f_f, rg11b10f_u) = - if device_features.contains(Features::RG11B10UFLOAT_RENDERABLE) { - (msaa_resolve, attachment) - } else { - (msaa, basic) - }; - let (bgra8unorm_f, bgra8unorm) = if device_features.contains(Features::BGRA8UNORM_STORAGE) { - ( - msaa_resolve | TextureFormatFeatureFlags::STORAGE_WRITE_ONLY, - attachment | TextureUsages::STORAGE_BINDING, - ) - } else { - (msaa_resolve, attachment) - }; - - #[rustfmt::skip] // lets make a nice table - let ( - mut flags, - allowed_usages, - ) = match *self { - Self::R8Unorm => (msaa_resolve, attachment), - Self::R8Snorm => ( none, basic), - Self::R8Uint => ( msaa, attachment), - Self::R8Sint => ( msaa, attachment), - Self::R16Uint => ( msaa, attachment), - Self::R16Sint => ( msaa, attachment), - Self::R16Float => (msaa_resolve, attachment), - Self::Rg8Unorm => (msaa_resolve, attachment), - Self::Rg8Snorm => ( none, basic), - Self::Rg8Uint => ( msaa, attachment), - Self::Rg8Sint => ( msaa, attachment), - Self::R32Uint => ( s_all, atomic), - Self::R32Sint => ( s_all, atomic), - Self::R32Float => (msaa | s_all, all_flags), - Self::Rg16Uint => ( msaa, attachment), - Self::Rg16Sint => ( msaa, attachment), - Self::Rg16Float => (msaa_resolve, attachment), - Self::Rgba8Unorm => (msaa_resolve | s_ro_wo, all_flags), - Self::Rgba8UnormSrgb => (msaa_resolve, attachment), - Self::Rgba8Snorm => ( s_ro_wo, storage), - Self::Rgba8Uint => ( msaa | s_ro_wo, all_flags), - Self::Rgba8Sint => ( msaa | s_ro_wo, all_flags), - Self::Bgra8Unorm => (bgra8unorm_f, bgra8unorm), - Self::Bgra8UnormSrgb => (msaa_resolve, attachment), - Self::Rgb10a2Uint => ( msaa, attachment), - Self::Rgb10a2Unorm => (msaa_resolve, attachment), - Self::Rg11b10Ufloat => ( rg11b10f_f, rg11b10f_u), - Self::R64Uint => ( s_ro_wo, atomic_64), - Self::Rg32Uint => ( s_ro_wo, all_flags), - Self::Rg32Sint => ( s_ro_wo, all_flags), - Self::Rg32Float => ( s_ro_wo, all_flags), - Self::Rgba16Uint => ( msaa | s_ro_wo, all_flags), - Self::Rgba16Sint => ( msaa | s_ro_wo, all_flags), - Self::Rgba16Float => (msaa_resolve | s_ro_wo, all_flags), - Self::Rgba32Uint => ( s_ro_wo, all_flags), - Self::Rgba32Sint => ( s_ro_wo, all_flags), - Self::Rgba32Float => ( s_ro_wo, all_flags), - - Self::Stencil8 => ( msaa, attachment), - Self::Depth16Unorm => ( msaa, attachment), - Self::Depth24Plus => ( msaa, attachment), - Self::Depth24PlusStencil8 => ( msaa, attachment), - Self::Depth32Float => ( msaa, attachment), - Self::Depth32FloatStencil8 => ( msaa, attachment), - - // We only support sampling nv12 and p010 textures until we - // implement transfer plane data. - Self::NV12 => ( none, binding), - Self::P010 => ( none, binding), - - Self::R16Unorm => ( msaa | s_ro_wo, storage), - Self::R16Snorm => ( msaa | s_ro_wo, storage), - Self::Rg16Unorm => ( msaa | s_ro_wo, storage), - Self::Rg16Snorm => ( msaa | s_ro_wo, storage), - Self::Rgba16Unorm => ( msaa | s_ro_wo, storage), - Self::Rgba16Snorm => ( msaa | s_ro_wo, storage), - - Self::Rgb9e5Ufloat => ( none, basic), - - Self::Bc1RgbaUnorm => ( none, basic), - Self::Bc1RgbaUnormSrgb => ( none, basic), - Self::Bc2RgbaUnorm => ( none, basic), - Self::Bc2RgbaUnormSrgb => ( none, basic), - Self::Bc3RgbaUnorm => ( none, basic), - Self::Bc3RgbaUnormSrgb => ( none, basic), - Self::Bc4RUnorm => ( none, basic), - Self::Bc4RSnorm => ( none, basic), - Self::Bc5RgUnorm => ( none, basic), - Self::Bc5RgSnorm => ( none, basic), - Self::Bc6hRgbUfloat => ( none, basic), - Self::Bc6hRgbFloat => ( none, basic), - Self::Bc7RgbaUnorm => ( none, basic), - Self::Bc7RgbaUnormSrgb => ( none, basic), - - Self::Etc2Rgb8Unorm => ( none, basic), - Self::Etc2Rgb8UnormSrgb => ( none, basic), - Self::Etc2Rgb8A1Unorm => ( none, basic), - Self::Etc2Rgb8A1UnormSrgb => ( none, basic), - Self::Etc2Rgba8Unorm => ( none, basic), - Self::Etc2Rgba8UnormSrgb => ( none, basic), - Self::EacR11Unorm => ( none, basic), - Self::EacR11Snorm => ( none, basic), - Self::EacRg11Unorm => ( none, basic), - Self::EacRg11Snorm => ( none, basic), - - Self::Astc { .. } => ( none, basic), - }; - - // Get whether the format is filterable, taking features into account - let sample_type1 = self.sample_type(None, Some(device_features)); - let is_filterable = sample_type1 == Some(TextureSampleType::Float { filterable: true }); - - // Features that enable filtering don't affect blendability - let sample_type2 = self.sample_type(None, None); - let is_blendable = sample_type2 == Some(TextureSampleType::Float { filterable: true }); - - flags.set(TextureFormatFeatureFlags::FILTERABLE, is_filterable); - flags.set(TextureFormatFeatureFlags::BLENDABLE, is_blendable); - flags.set( - TextureFormatFeatureFlags::STORAGE_ATOMIC, - allowed_usages.contains(TextureUsages::STORAGE_ATOMIC), - ); - - TextureFormatFeatures { - allowed_usages, - flags, - } - } - - /// Returns the sample type compatible with this format and aspect. - /// - /// Returns `None` only if this is a combined depth-stencil format or a multi-planar format - /// and `TextureAspect::All` or no `aspect` was provided. - #[must_use] - pub fn sample_type( - &self, - aspect: Option<TextureAspect>, - device_features: Option<Features>, - ) -> Option<TextureSampleType> { - let float = TextureSampleType::Float { filterable: true }; - let unfilterable_float = TextureSampleType::Float { filterable: false }; - let float32_sample_type = TextureSampleType::Float { - filterable: device_features - .unwrap_or(Features::empty()) - .contains(Features::FLOAT32_FILTERABLE), - }; - let depth = TextureSampleType::Depth; - let uint = TextureSampleType::Uint; - let sint = TextureSampleType::Sint; - - match *self { - Self::R8Unorm - | Self::R8Snorm - | Self::Rg8Unorm - | Self::Rg8Snorm - | Self::Rgba8Unorm - | Self::Rgba8UnormSrgb - | Self::Rgba8Snorm - | Self::Bgra8Unorm - | Self::Bgra8UnormSrgb - | Self::R16Float - | Self::Rg16Float - | Self::Rgba16Float - | Self::Rgb10a2Unorm - | Self::Rg11b10Ufloat => Some(float), - - Self::R32Float | Self::Rg32Float | Self::Rgba32Float => Some(float32_sample_type), - - Self::R8Uint - | Self::Rg8Uint - | Self::Rgba8Uint - | Self::R16Uint - | Self::Rg16Uint - | Self::Rgba16Uint - | Self::R32Uint - | Self::R64Uint - | Self::Rg32Uint - | Self::Rgba32Uint - | Self::Rgb10a2Uint => Some(uint), - - Self::R8Sint - | Self::Rg8Sint - | Self::Rgba8Sint - | Self::R16Sint - | Self::Rg16Sint - | Self::Rgba16Sint - | Self::R32Sint - | Self::Rg32Sint - | Self::Rgba32Sint => Some(sint), - - Self::Stencil8 => Some(uint), - Self::Depth16Unorm | Self::Depth24Plus | Self::Depth32Float => Some(depth), - Self::Depth24PlusStencil8 | Self::Depth32FloatStencil8 => match aspect { - Some(TextureAspect::DepthOnly) => Some(depth), - Some(TextureAspect::StencilOnly) => Some(uint), - _ => None, - }, - - Self::NV12 | Self::P010 => match aspect { - Some(TextureAspect::Plane0) | Some(TextureAspect::Plane1) => { - Some(unfilterable_float) - } - _ => None, - }, - - Self::R16Unorm - | Self::R16Snorm - | Self::Rg16Unorm - | Self::Rg16Snorm - | Self::Rgba16Unorm - | Self::Rgba16Snorm => Some(float), - - Self::Rgb9e5Ufloat => Some(float), - - Self::Bc1RgbaUnorm - | Self::Bc1RgbaUnormSrgb - | Self::Bc2RgbaUnorm - | Self::Bc2RgbaUnormSrgb - | Self::Bc3RgbaUnorm - | Self::Bc3RgbaUnormSrgb - | Self::Bc4RUnorm - | Self::Bc4RSnorm - | Self::Bc5RgUnorm - | Self::Bc5RgSnorm - | Self::Bc6hRgbUfloat - | Self::Bc6hRgbFloat - | Self::Bc7RgbaUnorm - | Self::Bc7RgbaUnormSrgb => Some(float), - - Self::Etc2Rgb8Unorm - | Self::Etc2Rgb8UnormSrgb - | Self::Etc2Rgb8A1Unorm - | Self::Etc2Rgb8A1UnormSrgb - | Self::Etc2Rgba8Unorm - | Self::Etc2Rgba8UnormSrgb - | Self::EacR11Unorm - | Self::EacR11Snorm - | Self::EacRg11Unorm - | Self::EacRg11Snorm => Some(float), - - Self::Astc { .. } => Some(float), - } - } - - /// The number of bytes one [texel block](https://gpuweb.github.io/gpuweb/#texel-block) occupies during an image copy, if applicable. - /// - /// Known as the [texel block copy footprint](https://gpuweb.github.io/gpuweb/#texel-block-copy-footprint). - /// - /// Note that for uncompressed formats this is the same as the size of a single texel, - /// since uncompressed formats have a block size of 1x1. - /// - /// Returns `None` if any of the following are true: - /// - the format is a combined depth-stencil and no `aspect` was provided - /// - the format is a multi-planar format and no `aspect` was provided - /// - the format is `Depth24Plus` - /// - the format is `Depth24PlusStencil8` and `aspect` is depth. - #[deprecated(since = "0.19.0", note = "Use `block_copy_size` instead.")] - #[must_use] - pub fn block_size(&self, aspect: Option<TextureAspect>) -> Option<u32> { - self.block_copy_size(aspect) - } - - /// The number of bytes one [texel block](https://gpuweb.github.io/gpuweb/#texel-block) occupies during an image copy, if applicable. - /// - /// Known as the [texel block copy footprint](https://gpuweb.github.io/gpuweb/#texel-block-copy-footprint). - /// - /// Note that for uncompressed formats this is the same as the size of a single texel, - /// since uncompressed formats have a block size of 1x1. - /// - /// Returns `None` if any of the following are true: - /// - the format is a combined depth-stencil and no `aspect` was provided - /// - the format is a multi-planar format and no `aspect` was provided - /// - the format is `Depth24Plus` - /// - the format is `Depth24PlusStencil8` and `aspect` is depth. - #[must_use] - pub fn block_copy_size(&self, aspect: Option<TextureAspect>) -> Option<u32> { - match *self { - Self::R8Unorm | Self::R8Snorm | Self::R8Uint | Self::R8Sint => Some(1), - - Self::Rg8Unorm | Self::Rg8Snorm | Self::Rg8Uint | Self::Rg8Sint => Some(2), - Self::R16Unorm | Self::R16Snorm | Self::R16Uint | Self::R16Sint | Self::R16Float => { - Some(2) - } - - Self::Rgba8Unorm - | Self::Rgba8UnormSrgb - | Self::Rgba8Snorm - | Self::Rgba8Uint - | Self::Rgba8Sint - | Self::Bgra8Unorm - | Self::Bgra8UnormSrgb => Some(4), - Self::Rg16Unorm - | Self::Rg16Snorm - | Self::Rg16Uint - | Self::Rg16Sint - | Self::Rg16Float => Some(4), - Self::R32Uint | Self::R32Sint | Self::R32Float => Some(4), - Self::Rgb9e5Ufloat | Self::Rgb10a2Uint | Self::Rgb10a2Unorm | Self::Rg11b10Ufloat => { - Some(4) - } - - Self::Rgba16Unorm - | Self::Rgba16Snorm - | Self::Rgba16Uint - | Self::Rgba16Sint - | Self::Rgba16Float => Some(8), - Self::R64Uint | Self::Rg32Uint | Self::Rg32Sint | Self::Rg32Float => Some(8), - - Self::Rgba32Uint | Self::Rgba32Sint | Self::Rgba32Float => Some(16), - - Self::Stencil8 => Some(1), - Self::Depth16Unorm => Some(2), - Self::Depth32Float => Some(4), - Self::Depth24Plus => None, - Self::Depth24PlusStencil8 => match aspect { - Some(TextureAspect::DepthOnly) => None, - Some(TextureAspect::StencilOnly) => Some(1), - _ => None, - }, - Self::Depth32FloatStencil8 => match aspect { - Some(TextureAspect::DepthOnly) => Some(4), - Some(TextureAspect::StencilOnly) => Some(1), - _ => None, - }, - - Self::NV12 => match aspect { - Some(TextureAspect::Plane0) => Some(1), - Some(TextureAspect::Plane1) => Some(2), - _ => None, - }, - - Self::P010 => match aspect { - Some(TextureAspect::Plane0) => Some(2), - Some(TextureAspect::Plane1) => Some(4), - _ => None, - }, - - Self::Bc1RgbaUnorm | Self::Bc1RgbaUnormSrgb | Self::Bc4RUnorm | Self::Bc4RSnorm => { - Some(8) - } - Self::Bc2RgbaUnorm - | Self::Bc2RgbaUnormSrgb - | Self::Bc3RgbaUnorm - | Self::Bc3RgbaUnormSrgb - | Self::Bc5RgUnorm - | Self::Bc5RgSnorm - | Self::Bc6hRgbUfloat - | Self::Bc6hRgbFloat - | Self::Bc7RgbaUnorm - | Self::Bc7RgbaUnormSrgb => Some(16), - - Self::Etc2Rgb8Unorm - | Self::Etc2Rgb8UnormSrgb - | Self::Etc2Rgb8A1Unorm - | Self::Etc2Rgb8A1UnormSrgb - | Self::EacR11Unorm - | Self::EacR11Snorm => Some(8), - Self::Etc2Rgba8Unorm - | Self::Etc2Rgba8UnormSrgb - | Self::EacRg11Unorm - | Self::EacRg11Snorm => Some(16), - - Self::Astc { .. } => Some(16), - } - } - - /// The largest number that can be returned by [`Self::target_pixel_byte_cost`]. - pub const MAX_TARGET_PIXEL_BYTE_COST: u32 = 16; - - /// The number of bytes occupied per pixel in a color attachment - /// <https://gpuweb.github.io/gpuweb/#render-target-pixel-byte-cost> - #[must_use] - pub fn target_pixel_byte_cost(&self) -> Option<u32> { - match *self { - Self::R8Unorm | Self::R8Snorm | Self::R8Uint | Self::R8Sint => Some(1), - Self::Rg8Unorm - | Self::Rg8Snorm - | Self::Rg8Uint - | Self::Rg8Sint - | Self::R16Uint - | Self::R16Sint - | Self::R16Unorm - | Self::R16Snorm - | Self::R16Float => Some(2), - Self::Rgba8Uint - | Self::Rgba8Sint - | Self::Rg16Uint - | Self::Rg16Sint - | Self::Rg16Unorm - | Self::Rg16Snorm - | Self::Rg16Float - | Self::R32Uint - | Self::R32Sint - | Self::R32Float => Some(4), - // Despite being 4 bytes per pixel, these are 8 bytes per pixel in the table - Self::Rgba8Unorm - | Self::Rgba8UnormSrgb - | Self::Rgba8Snorm - | Self::Bgra8Unorm - | Self::Bgra8UnormSrgb - // --- - | Self::Rgba16Uint - | Self::Rgba16Sint - | Self::Rgba16Unorm - | Self::Rgba16Snorm - | Self::Rgba16Float - | Self::R64Uint - | Self::Rg32Uint - | Self::Rg32Sint - | Self::Rg32Float - | Self::Rgb10a2Uint - | Self::Rgb10a2Unorm - | Self::Rg11b10Ufloat => Some(8), - Self::Rgba32Uint | Self::Rgba32Sint | Self::Rgba32Float => Some(16), - // ⚠️ If you add formats with larger sizes, make sure you change `MAX_TARGET_PIXEL_BYTE_COST`` ⚠️ - Self::Stencil8 - | Self::Depth16Unorm - | Self::Depth24Plus - | Self::Depth24PlusStencil8 - | Self::Depth32Float - | Self::Depth32FloatStencil8 - | Self::NV12 - | Self::P010 - | Self::Rgb9e5Ufloat - | Self::Bc1RgbaUnorm - | Self::Bc1RgbaUnormSrgb - | Self::Bc2RgbaUnorm - | Self::Bc2RgbaUnormSrgb - | Self::Bc3RgbaUnorm - | Self::Bc3RgbaUnormSrgb - | Self::Bc4RUnorm - | Self::Bc4RSnorm - | Self::Bc5RgUnorm - | Self::Bc5RgSnorm - | Self::Bc6hRgbUfloat - | Self::Bc6hRgbFloat - | Self::Bc7RgbaUnorm - | Self::Bc7RgbaUnormSrgb - | Self::Etc2Rgb8Unorm - | Self::Etc2Rgb8UnormSrgb - | Self::Etc2Rgb8A1Unorm - | Self::Etc2Rgb8A1UnormSrgb - | Self::Etc2Rgba8Unorm - | Self::Etc2Rgba8UnormSrgb - | Self::EacR11Unorm - | Self::EacR11Snorm - | Self::EacRg11Unorm - | Self::EacRg11Snorm - | Self::Astc { .. } => None, - } - } - - /// See <https://gpuweb.github.io/gpuweb/#render-target-component-alignment> - #[must_use] - pub fn target_component_alignment(&self) -> Option<u32> { - match *self { - Self::R8Unorm - | Self::R8Snorm - | Self::R8Uint - | Self::R8Sint - | Self::Rg8Unorm - | Self::Rg8Snorm - | Self::Rg8Uint - | Self::Rg8Sint - | Self::Rgba8Unorm - | Self::Rgba8UnormSrgb - | Self::Rgba8Snorm - | Self::Rgba8Uint - | Self::Rgba8Sint - | Self::Bgra8Unorm - | Self::Bgra8UnormSrgb => Some(1), - Self::R16Uint - | Self::R16Sint - | Self::R16Unorm - | Self::R16Snorm - | Self::R16Float - | Self::Rg16Uint - | Self::Rg16Sint - | Self::Rg16Unorm - | Self::Rg16Snorm - | Self::Rg16Float - | Self::Rgba16Uint - | Self::Rgba16Sint - | Self::Rgba16Unorm - | Self::Rgba16Snorm - | Self::Rgba16Float => Some(2), - Self::R32Uint - | Self::R32Sint - | Self::R32Float - | Self::R64Uint - | Self::Rg32Uint - | Self::Rg32Sint - | Self::Rg32Float - | Self::Rgba32Uint - | Self::Rgba32Sint - | Self::Rgba32Float - | Self::Rgb10a2Uint - | Self::Rgb10a2Unorm - | Self::Rg11b10Ufloat => Some(4), - Self::Stencil8 - | Self::Depth16Unorm - | Self::Depth24Plus - | Self::Depth24PlusStencil8 - | Self::Depth32Float - | Self::Depth32FloatStencil8 - | Self::NV12 - | Self::P010 - | Self::Rgb9e5Ufloat - | Self::Bc1RgbaUnorm - | Self::Bc1RgbaUnormSrgb - | Self::Bc2RgbaUnorm - | Self::Bc2RgbaUnormSrgb - | Self::Bc3RgbaUnorm - | Self::Bc3RgbaUnormSrgb - | Self::Bc4RUnorm - | Self::Bc4RSnorm - | Self::Bc5RgUnorm - | Self::Bc5RgSnorm - | Self::Bc6hRgbUfloat - | Self::Bc6hRgbFloat - | Self::Bc7RgbaUnorm - | Self::Bc7RgbaUnormSrgb - | Self::Etc2Rgb8Unorm - | Self::Etc2Rgb8UnormSrgb - | Self::Etc2Rgb8A1Unorm - | Self::Etc2Rgb8A1UnormSrgb - | Self::Etc2Rgba8Unorm - | Self::Etc2Rgba8UnormSrgb - | Self::EacR11Unorm - | Self::EacR11Snorm - | Self::EacRg11Unorm - | Self::EacRg11Snorm - | Self::Astc { .. } => None, - } - } - - /// Returns the number of components this format has. - #[must_use] - pub fn components(&self) -> u8 { - self.components_with_aspect(TextureAspect::All) - } - - /// Returns the number of components this format has taking into account the `aspect`. - /// - /// The `aspect` is only relevant for combined depth-stencil formats and multi-planar formats. - #[must_use] - pub fn components_with_aspect(&self, aspect: TextureAspect) -> u8 { - match *self { - Self::R8Unorm - | Self::R8Snorm - | Self::R8Uint - | Self::R8Sint - | Self::R16Unorm - | Self::R16Snorm - | Self::R16Uint - | Self::R16Sint - | Self::R16Float - | Self::R32Uint - | Self::R32Sint - | Self::R32Float - | Self::R64Uint => 1, - - Self::Rg8Unorm - | Self::Rg8Snorm - | Self::Rg8Uint - | Self::Rg8Sint - | Self::Rg16Unorm - | Self::Rg16Snorm - | Self::Rg16Uint - | Self::Rg16Sint - | Self::Rg16Float - | Self::Rg32Uint - | Self::Rg32Sint - | Self::Rg32Float => 2, - - Self::Rgba8Unorm - | Self::Rgba8UnormSrgb - | Self::Rgba8Snorm - | Self::Rgba8Uint - | Self::Rgba8Sint - | Self::Bgra8Unorm - | Self::Bgra8UnormSrgb - | Self::Rgba16Unorm - | Self::Rgba16Snorm - | Self::Rgba16Uint - | Self::Rgba16Sint - | Self::Rgba16Float - | Self::Rgba32Uint - | Self::Rgba32Sint - | Self::Rgba32Float => 4, - - Self::Rgb9e5Ufloat | Self::Rg11b10Ufloat => 3, - Self::Rgb10a2Uint | Self::Rgb10a2Unorm => 4, - - Self::Stencil8 | Self::Depth16Unorm | Self::Depth24Plus | Self::Depth32Float => 1, - - Self::Depth24PlusStencil8 | Self::Depth32FloatStencil8 => match aspect { - TextureAspect::DepthOnly | TextureAspect::StencilOnly => 1, - _ => 2, - }, - - Self::NV12 | Self::P010 => match aspect { - TextureAspect::Plane0 => 1, - TextureAspect::Plane1 => 2, - _ => 3, - }, - - Self::Bc4RUnorm | Self::Bc4RSnorm => 1, - Self::Bc5RgUnorm | Self::Bc5RgSnorm => 2, - Self::Bc6hRgbUfloat | Self::Bc6hRgbFloat => 3, - Self::Bc1RgbaUnorm - | Self::Bc1RgbaUnormSrgb - | Self::Bc2RgbaUnorm - | Self::Bc2RgbaUnormSrgb - | Self::Bc3RgbaUnorm - | Self::Bc3RgbaUnormSrgb - | Self::Bc7RgbaUnorm - | Self::Bc7RgbaUnormSrgb => 4, - - Self::EacR11Unorm | Self::EacR11Snorm => 1, - Self::EacRg11Unorm | Self::EacRg11Snorm => 2, - Self::Etc2Rgb8Unorm | Self::Etc2Rgb8UnormSrgb => 3, - Self::Etc2Rgb8A1Unorm - | Self::Etc2Rgb8A1UnormSrgb - | Self::Etc2Rgba8Unorm - | Self::Etc2Rgba8UnormSrgb => 4, - - Self::Astc { .. } => 4, - } - } - - /// Strips the `Srgb` suffix from the given texture format. - #[must_use] - pub fn remove_srgb_suffix(&self) -> TextureFormat { - match *self { - Self::Rgba8UnormSrgb => Self::Rgba8Unorm, - Self::Bgra8UnormSrgb => Self::Bgra8Unorm, - Self::Bc1RgbaUnormSrgb => Self::Bc1RgbaUnorm, - Self::Bc2RgbaUnormSrgb => Self::Bc2RgbaUnorm, - Self::Bc3RgbaUnormSrgb => Self::Bc3RgbaUnorm, - Self::Bc7RgbaUnormSrgb => Self::Bc7RgbaUnorm, - Self::Etc2Rgb8UnormSrgb => Self::Etc2Rgb8Unorm, - Self::Etc2Rgb8A1UnormSrgb => Self::Etc2Rgb8A1Unorm, - Self::Etc2Rgba8UnormSrgb => Self::Etc2Rgba8Unorm, - Self::Astc { - block, - channel: AstcChannel::UnormSrgb, - } => Self::Astc { - block, - channel: AstcChannel::Unorm, - }, - _ => *self, - } - } - - /// Adds an `Srgb` suffix to the given texture format, if the format supports it. - #[must_use] - pub fn add_srgb_suffix(&self) -> TextureFormat { - match *self { - Self::Rgba8Unorm => Self::Rgba8UnormSrgb, - Self::Bgra8Unorm => Self::Bgra8UnormSrgb, - Self::Bc1RgbaUnorm => Self::Bc1RgbaUnormSrgb, - Self::Bc2RgbaUnorm => Self::Bc2RgbaUnormSrgb, - Self::Bc3RgbaUnorm => Self::Bc3RgbaUnormSrgb, - Self::Bc7RgbaUnorm => Self::Bc7RgbaUnormSrgb, - Self::Etc2Rgb8Unorm => Self::Etc2Rgb8UnormSrgb, - Self::Etc2Rgb8A1Unorm => Self::Etc2Rgb8A1UnormSrgb, - Self::Etc2Rgba8Unorm => Self::Etc2Rgba8UnormSrgb, - Self::Astc { - block, - channel: AstcChannel::Unorm, - } => Self::Astc { - block, - channel: AstcChannel::UnormSrgb, - }, - _ => *self, - } - } - - /// Returns `true` for srgb formats. - #[must_use] - pub fn is_srgb(&self) -> bool { - *self != self.remove_srgb_suffix() - } - - /// Returns the theoretical memory footprint of a texture with the given format and dimensions. - /// - /// Actual memory usage may greatly exceed this value due to alignment and padding. - #[must_use] - pub fn theoretical_memory_footprint(&self, size: Extent3d) -> u64 { - let (block_width, block_height) = self.block_dimensions(); - - let block_size = self.block_copy_size(None); - - let approximate_block_size = match block_size { - Some(size) => size, - None => match self { - // One f16 per pixel - Self::Depth16Unorm => 2, - // One u24 per pixel, padded to 4 bytes - Self::Depth24Plus => 4, - // One u24 per pixel, plus one u8 per pixel - Self::Depth24PlusStencil8 => 4, - // One f32 per pixel - Self::Depth32Float => 4, - // One f32 per pixel, plus one u8 per pixel, with 3 bytes intermediary padding - Self::Depth32FloatStencil8 => 8, - // One u8 per pixel - Self::Stencil8 => 1, - // Two chroma bytes per block, one luma byte per block - Self::NV12 => 3, - // Two chroma u16s and one luma u16 per block - Self::P010 => 6, - f => { - log::warn!("Memory footprint for format {f:?} is not implemented"); - 0 - } - }, - }; - - let width_blocks = size.width.div_ceil(block_width) as u64; - let height_blocks = size.height.div_ceil(block_height) as u64; - - let total_blocks = width_blocks * height_blocks * size.depth_or_array_layers as u64; - - total_blocks * approximate_block_size as u64 - } -} - -#[test] -fn texture_format_serialize() { - use alloc::string::ToString; - - assert_eq!( - serde_json::to_string(&TextureFormat::R8Unorm).unwrap(), - "\"r8unorm\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::R8Snorm).unwrap(), - "\"r8snorm\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::R8Uint).unwrap(), - "\"r8uint\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::R8Sint).unwrap(), - "\"r8sint\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::R16Uint).unwrap(), - "\"r16uint\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::R16Sint).unwrap(), - "\"r16sint\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::R16Unorm).unwrap(), - "\"r16unorm\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::R16Snorm).unwrap(), - "\"r16snorm\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::R16Float).unwrap(), - "\"r16float\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Rg8Unorm).unwrap(), - "\"rg8unorm\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Rg8Snorm).unwrap(), - "\"rg8snorm\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Rg8Uint).unwrap(), - "\"rg8uint\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Rg8Sint).unwrap(), - "\"rg8sint\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::R32Uint).unwrap(), - "\"r32uint\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::R32Sint).unwrap(), - "\"r32sint\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::R32Float).unwrap(), - "\"r32float\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Rg16Uint).unwrap(), - "\"rg16uint\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Rg16Sint).unwrap(), - "\"rg16sint\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Rg16Unorm).unwrap(), - "\"rg16unorm\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Rg16Snorm).unwrap(), - "\"rg16snorm\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Rg16Float).unwrap(), - "\"rg16float\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Rgba8Unorm).unwrap(), - "\"rgba8unorm\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Rgba8UnormSrgb).unwrap(), - "\"rgba8unorm-srgb\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Rgba8Snorm).unwrap(), - "\"rgba8snorm\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Rgba8Uint).unwrap(), - "\"rgba8uint\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Rgba8Sint).unwrap(), - "\"rgba8sint\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Bgra8Unorm).unwrap(), - "\"bgra8unorm\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Bgra8UnormSrgb).unwrap(), - "\"bgra8unorm-srgb\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Rgb10a2Uint).unwrap(), - "\"rgb10a2uint\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Rgb10a2Unorm).unwrap(), - "\"rgb10a2unorm\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Rg11b10Ufloat).unwrap(), - "\"rg11b10ufloat\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::R64Uint).unwrap(), - "\"r64uint\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Rg32Uint).unwrap(), - "\"rg32uint\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Rg32Sint).unwrap(), - "\"rg32sint\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Rg32Float).unwrap(), - "\"rg32float\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Rgba16Uint).unwrap(), - "\"rgba16uint\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Rgba16Sint).unwrap(), - "\"rgba16sint\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Rgba16Unorm).unwrap(), - "\"rgba16unorm\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Rgba16Snorm).unwrap(), - "\"rgba16snorm\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Rgba16Float).unwrap(), - "\"rgba16float\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Rgba32Uint).unwrap(), - "\"rgba32uint\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Rgba32Sint).unwrap(), - "\"rgba32sint\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Rgba32Float).unwrap(), - "\"rgba32float\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Stencil8).unwrap(), - "\"stencil8\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Depth32Float).unwrap(), - "\"depth32float\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Depth16Unorm).unwrap(), - "\"depth16unorm\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Depth32FloatStencil8).unwrap(), - "\"depth32float-stencil8\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Depth24Plus).unwrap(), - "\"depth24plus\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Depth24PlusStencil8).unwrap(), - "\"depth24plus-stencil8\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Rgb9e5Ufloat).unwrap(), - "\"rgb9e5ufloat\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Bc1RgbaUnorm).unwrap(), - "\"bc1-rgba-unorm\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Bc1RgbaUnormSrgb).unwrap(), - "\"bc1-rgba-unorm-srgb\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Bc2RgbaUnorm).unwrap(), - "\"bc2-rgba-unorm\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Bc2RgbaUnormSrgb).unwrap(), - "\"bc2-rgba-unorm-srgb\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Bc3RgbaUnorm).unwrap(), - "\"bc3-rgba-unorm\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Bc3RgbaUnormSrgb).unwrap(), - "\"bc3-rgba-unorm-srgb\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Bc4RUnorm).unwrap(), - "\"bc4-r-unorm\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Bc4RSnorm).unwrap(), - "\"bc4-r-snorm\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Bc5RgUnorm).unwrap(), - "\"bc5-rg-unorm\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Bc5RgSnorm).unwrap(), - "\"bc5-rg-snorm\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Bc6hRgbUfloat).unwrap(), - "\"bc6h-rgb-ufloat\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Bc6hRgbFloat).unwrap(), - "\"bc6h-rgb-float\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Bc7RgbaUnorm).unwrap(), - "\"bc7-rgba-unorm\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Bc7RgbaUnormSrgb).unwrap(), - "\"bc7-rgba-unorm-srgb\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Etc2Rgb8Unorm).unwrap(), - "\"etc2-rgb8unorm\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Etc2Rgb8UnormSrgb).unwrap(), - "\"etc2-rgb8unorm-srgb\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Etc2Rgb8A1Unorm).unwrap(), - "\"etc2-rgb8a1unorm\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Etc2Rgb8A1UnormSrgb).unwrap(), - "\"etc2-rgb8a1unorm-srgb\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Etc2Rgba8Unorm).unwrap(), - "\"etc2-rgba8unorm\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::Etc2Rgba8UnormSrgb).unwrap(), - "\"etc2-rgba8unorm-srgb\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::EacR11Unorm).unwrap(), - "\"eac-r11unorm\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::EacR11Snorm).unwrap(), - "\"eac-r11snorm\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::EacRg11Unorm).unwrap(), - "\"eac-rg11unorm\"".to_string() - ); - assert_eq!( - serde_json::to_string(&TextureFormat::EacRg11Snorm).unwrap(), - "\"eac-rg11snorm\"".to_string() - ); -} - -#[test] -fn texture_format_deserialize() { - assert_eq!( - serde_json::from_str::<TextureFormat>("\"r8unorm\"").unwrap(), - TextureFormat::R8Unorm - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"r8snorm\"").unwrap(), - TextureFormat::R8Snorm - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"r8uint\"").unwrap(), - TextureFormat::R8Uint - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"r8sint\"").unwrap(), - TextureFormat::R8Sint - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"r16uint\"").unwrap(), - TextureFormat::R16Uint - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"r16sint\"").unwrap(), - TextureFormat::R16Sint - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"r16unorm\"").unwrap(), - TextureFormat::R16Unorm - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"r16snorm\"").unwrap(), - TextureFormat::R16Snorm - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"r16float\"").unwrap(), - TextureFormat::R16Float - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"rg8unorm\"").unwrap(), - TextureFormat::Rg8Unorm - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"rg8snorm\"").unwrap(), - TextureFormat::Rg8Snorm - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"rg8uint\"").unwrap(), - TextureFormat::Rg8Uint - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"rg8sint\"").unwrap(), - TextureFormat::Rg8Sint - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"r32uint\"").unwrap(), - TextureFormat::R32Uint - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"r32sint\"").unwrap(), - TextureFormat::R32Sint - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"r32float\"").unwrap(), - TextureFormat::R32Float - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"rg16uint\"").unwrap(), - TextureFormat::Rg16Uint - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"rg16sint\"").unwrap(), - TextureFormat::Rg16Sint - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"rg16unorm\"").unwrap(), - TextureFormat::Rg16Unorm - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"rg16snorm\"").unwrap(), - TextureFormat::Rg16Snorm - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"rg16float\"").unwrap(), - TextureFormat::Rg16Float - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"rgba8unorm\"").unwrap(), - TextureFormat::Rgba8Unorm - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"rgba8unorm-srgb\"").unwrap(), - TextureFormat::Rgba8UnormSrgb - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"rgba8snorm\"").unwrap(), - TextureFormat::Rgba8Snorm - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"rgba8uint\"").unwrap(), - TextureFormat::Rgba8Uint - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"rgba8sint\"").unwrap(), - TextureFormat::Rgba8Sint - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"bgra8unorm\"").unwrap(), - TextureFormat::Bgra8Unorm - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"bgra8unorm-srgb\"").unwrap(), - TextureFormat::Bgra8UnormSrgb - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"rgb10a2uint\"").unwrap(), - TextureFormat::Rgb10a2Uint - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"rgb10a2unorm\"").unwrap(), - TextureFormat::Rgb10a2Unorm - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"rg11b10ufloat\"").unwrap(), - TextureFormat::Rg11b10Ufloat - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"r64uint\"").unwrap(), - TextureFormat::R64Uint - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"rg32uint\"").unwrap(), - TextureFormat::Rg32Uint - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"rg32sint\"").unwrap(), - TextureFormat::Rg32Sint - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"rg32float\"").unwrap(), - TextureFormat::Rg32Float - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"rgba16uint\"").unwrap(), - TextureFormat::Rgba16Uint - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"rgba16sint\"").unwrap(), - TextureFormat::Rgba16Sint - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"rgba16unorm\"").unwrap(), - TextureFormat::Rgba16Unorm - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"rgba16snorm\"").unwrap(), - TextureFormat::Rgba16Snorm - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"rgba16float\"").unwrap(), - TextureFormat::Rgba16Float - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"rgba32uint\"").unwrap(), - TextureFormat::Rgba32Uint - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"rgba32sint\"").unwrap(), - TextureFormat::Rgba32Sint - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"rgba32float\"").unwrap(), - TextureFormat::Rgba32Float - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"stencil8\"").unwrap(), - TextureFormat::Stencil8 - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"depth32float\"").unwrap(), - TextureFormat::Depth32Float - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"depth16unorm\"").unwrap(), - TextureFormat::Depth16Unorm - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"depth32float-stencil8\"").unwrap(), - TextureFormat::Depth32FloatStencil8 - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"depth24plus\"").unwrap(), - TextureFormat::Depth24Plus - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"depth24plus-stencil8\"").unwrap(), - TextureFormat::Depth24PlusStencil8 - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"rgb9e5ufloat\"").unwrap(), - TextureFormat::Rgb9e5Ufloat - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"bc1-rgba-unorm\"").unwrap(), - TextureFormat::Bc1RgbaUnorm - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"bc1-rgba-unorm-srgb\"").unwrap(), - TextureFormat::Bc1RgbaUnormSrgb - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"bc2-rgba-unorm\"").unwrap(), - TextureFormat::Bc2RgbaUnorm - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"bc2-rgba-unorm-srgb\"").unwrap(), - TextureFormat::Bc2RgbaUnormSrgb - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"bc3-rgba-unorm\"").unwrap(), - TextureFormat::Bc3RgbaUnorm - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"bc3-rgba-unorm-srgb\"").unwrap(), - TextureFormat::Bc3RgbaUnormSrgb - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"bc4-r-unorm\"").unwrap(), - TextureFormat::Bc4RUnorm - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"bc4-r-snorm\"").unwrap(), - TextureFormat::Bc4RSnorm - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"bc5-rg-unorm\"").unwrap(), - TextureFormat::Bc5RgUnorm - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"bc5-rg-snorm\"").unwrap(), - TextureFormat::Bc5RgSnorm - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"bc6h-rgb-ufloat\"").unwrap(), - TextureFormat::Bc6hRgbUfloat - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"bc6h-rgb-float\"").unwrap(), - TextureFormat::Bc6hRgbFloat - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"bc7-rgba-unorm\"").unwrap(), - TextureFormat::Bc7RgbaUnorm - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"bc7-rgba-unorm-srgb\"").unwrap(), - TextureFormat::Bc7RgbaUnormSrgb - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"etc2-rgb8unorm\"").unwrap(), - TextureFormat::Etc2Rgb8Unorm - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"etc2-rgb8unorm-srgb\"").unwrap(), - TextureFormat::Etc2Rgb8UnormSrgb - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"etc2-rgb8a1unorm\"").unwrap(), - TextureFormat::Etc2Rgb8A1Unorm - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"etc2-rgb8a1unorm-srgb\"").unwrap(), - TextureFormat::Etc2Rgb8A1UnormSrgb - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"etc2-rgba8unorm\"").unwrap(), - TextureFormat::Etc2Rgba8Unorm - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"etc2-rgba8unorm-srgb\"").unwrap(), - TextureFormat::Etc2Rgba8UnormSrgb - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"eac-r11unorm\"").unwrap(), - TextureFormat::EacR11Unorm - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"eac-r11snorm\"").unwrap(), - TextureFormat::EacR11Snorm - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"eac-rg11unorm\"").unwrap(), - TextureFormat::EacRg11Unorm - ); - assert_eq!( - serde_json::from_str::<TextureFormat>("\"eac-rg11snorm\"").unwrap(), - TextureFormat::EacRg11Snorm - ); -} - -/// Color write mask. Disabled color channels will not be written to. -/// -/// Corresponds to [WebGPU `GPUColorWriteFlags`]( -/// https://gpuweb.github.io/gpuweb/#typedefdef-gpucolorwriteflags). -#[repr(transparent)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(transparent))] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub struct ColorWrites(u32); - -bitflags::bitflags! { - impl ColorWrites: u32 { - /// Enable red channel writes - const RED = 1 << 0; - /// Enable green channel writes - const GREEN = 1 << 1; - /// Enable blue channel writes - const BLUE = 1 << 2; - /// Enable alpha channel writes - const ALPHA = 1 << 3; - /// Enable red, green, and blue channel writes - const COLOR = Self::RED.bits() | Self::GREEN.bits() | Self::BLUE.bits(); - /// Enable writes to all channels. - const ALL = Self::RED.bits() | Self::GREEN.bits() | Self::BLUE.bits() | Self::ALPHA.bits(); - } -} - -impl Default for ColorWrites { - fn default() -> Self { - Self::ALL - } -} - -/// Passed to `Device::poll` to control how and if it should block. -#[derive(Clone, Debug)] -pub enum PollType<T> { - /// On wgpu-core based backends, block until the given submission has - /// completed execution, and any callbacks have been invoked. - /// - /// On WebGPU, this has no effect. Callbacks are invoked from the - /// window event loop. - Wait { - /// Submission index to wait for. - /// - /// If not specified, will wait for the most recent submission at the time of the poll. - /// By the time the method returns, more submissions may have taken place. - submission_index: Option<T>, - - /// Max time to wait for the submission to complete. - /// - /// If not specified, will wait indefinitely (or until an error is detected). - /// If waiting for the GPU device takes this long or longer, the poll will return [`PollError::Timeout`]. - timeout: Option<Duration>, - }, - - /// Check the device for a single time without blocking. - Poll, -} - -impl<T> PollType<T> { - /// Wait indefinitely until for the most recent submission to complete. - /// - /// This is a convenience function that creates a [`Self::Wait`] variant with - /// no timeout and no submission index. - #[must_use] - pub const fn wait_indefinitely() -> Self { - Self::Wait { - submission_index: None, - timeout: None, - } - } - - /// This `PollType` represents a wait of some kind. - #[must_use] - pub fn is_wait(&self) -> bool { - match *self { - Self::Wait { .. } => true, - Self::Poll => false, - } - } - - /// Map on the wait index type. - #[must_use] - pub fn map_index<U, F>(self, func: F) -> PollType<U> - where - F: FnOnce(T) -> U, - { - match self { - Self::Wait { - submission_index, - timeout, - } => PollType::Wait { - submission_index: submission_index.map(func), - timeout, - }, - Self::Poll => PollType::Poll, - } - } -} - -/// Error states after a device poll. -#[derive(Debug)] -pub enum PollError { - /// The requested Wait timed out before the submission was completed. - Timeout, - /// The requested Wait was given a wrong submission index. - WrongSubmissionIndex(u64, u64), -} - -// This impl could be derived by `thiserror`, but by not doing so, we can reduce the number of -// dependencies this early in the dependency graph, which may improve build parallelism. -impl fmt::Display for PollError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - PollError::Timeout => { - f.write_str("The requested Wait timed out before the submission was completed.") - } - PollError::WrongSubmissionIndex(requested, successful) => write!( - f, - "Tried to wait using a submission index ({requested}) \ - that has not been returned by a successful submission \ - (last successful submission: {successful}" - ), - } - } -} - -impl core::error::Error for PollError {} - -/// Status of device poll operation. -#[derive(Debug, PartialEq, Eq)] -pub enum PollStatus { - /// There are no active submissions in flight as of the beginning of the poll call. - /// Other submissions may have been queued on other threads during the call. - /// - /// This implies that the given Wait was satisfied before the timeout. - QueueEmpty, - - /// The requested Wait was satisfied before the timeout. - WaitSucceeded, - - /// This was a poll. - Poll, -} - -impl PollStatus { - /// Returns true if the result is [`Self::QueueEmpty`]. - #[must_use] - pub fn is_queue_empty(&self) -> bool { - matches!(self, Self::QueueEmpty) - } - - /// Returns true if the result is either [`Self::WaitSucceeded`] or [`Self::QueueEmpty`]. - #[must_use] - pub fn wait_finished(&self) -> bool { - matches!(self, Self::WaitSucceeded | Self::QueueEmpty) - } -} - -/// State of the stencil operation (fixed-pipeline stage). -/// -/// For use in [`DepthStencilState`]. -/// -/// Corresponds to a portion of [WebGPU `GPUDepthStencilState`]( -/// https://gpuweb.github.io/gpuweb/#dictdef-gpudepthstencilstate). -#[repr(C)] -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct StencilState { - /// Front face mode. - pub front: StencilFaceState, - /// Back face mode. - pub back: StencilFaceState, - /// Stencil values are AND'd with this mask when reading and writing from the stencil buffer. Only low 8 bits are used. - pub read_mask: u32, - /// Stencil values are AND'd with this mask when writing to the stencil buffer. Only low 8 bits are used. - pub write_mask: u32, -} - -impl StencilState { - /// Returns true if the stencil test is enabled. - #[must_use] - pub fn is_enabled(&self) -> bool { - (self.front != StencilFaceState::IGNORE || self.back != StencilFaceState::IGNORE) - && (self.read_mask != 0 || self.write_mask != 0) - } - /// Returns true if the state doesn't mutate the target values. - #[must_use] - pub fn is_read_only(&self, cull_mode: Option<Face>) -> bool { - // The rules are defined in step 7 of the "Device timeline initialization steps" - // subsection of the "Render Pipeline Creation" section of WebGPU - // (link to the section: https://gpuweb.github.io/gpuweb/#render-pipeline-creation) - - if self.write_mask == 0 { - return true; - } - - let front_ro = cull_mode == Some(Face::Front) || self.front.is_read_only(); - let back_ro = cull_mode == Some(Face::Back) || self.back.is_read_only(); - - front_ro && back_ro - } - /// Returns true if the stencil state uses the reference value for testing. - #[must_use] - pub fn needs_ref_value(&self) -> bool { - self.front.needs_ref_value() || self.back.needs_ref_value() - } -} - -/// Describes the biasing setting for the depth target. -/// -/// For use in [`DepthStencilState`]. -/// -/// Corresponds to a portion of [WebGPU `GPUDepthStencilState`]( -/// https://gpuweb.github.io/gpuweb/#dictdef-gpudepthstencilstate). -#[repr(C)] -#[derive(Clone, Copy, Debug, Default)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct DepthBiasState { - /// Constant depth biasing factor, in basic units of the depth format. - pub constant: i32, - /// Slope depth biasing factor. - pub slope_scale: f32, - /// Depth bias clamp value (absolute). - pub clamp: f32, -} - -impl DepthBiasState { - /// Returns true if the depth biasing is enabled. - #[must_use] - pub fn is_enabled(&self) -> bool { - self.constant != 0 || self.slope_scale != 0.0 - } -} - -impl Hash for DepthBiasState { - fn hash<H: Hasher>(&self, state: &mut H) { - self.constant.hash(state); - self.slope_scale.to_bits().hash(state); - self.clamp.to_bits().hash(state); - } -} - -impl PartialEq for DepthBiasState { - fn eq(&self, other: &Self) -> bool { - (self.constant == other.constant) - && (self.slope_scale.to_bits() == other.slope_scale.to_bits()) - && (self.clamp.to_bits() == other.clamp.to_bits()) - } -} - -impl Eq for DepthBiasState {} - -/// Operation to perform to the output attachment at the start of a render pass. -/// -/// Corresponds to [WebGPU `GPULoadOp`](https://gpuweb.github.io/gpuweb/#enumdef-gpuloadop), -/// plus the corresponding clearValue. -#[repr(u8)] -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] -pub enum LoadOp<V> { - /// Loads the specified value for this attachment into the render pass. - /// - /// On some GPU hardware (primarily mobile), "clear" is significantly cheaper - /// because it avoids loading data from main memory into tile-local memory. - /// - /// On other GPU hardware, there isn’t a significant difference. - /// - /// As a result, it is recommended to use "clear" rather than "load" in cases - /// where the initial value doesn’t matter - /// (e.g. the render target will be cleared using a skybox). - Clear(V) = 0, - /// Loads the existing value for this attachment into the render pass. - Load = 1, -} - -impl<V> LoadOp<V> { - /// Returns true if variants are same (ignoring clear value) - pub fn eq_variant<T>(&self, other: LoadOp<T>) -> bool { - matches!( - (self, other), - (LoadOp::Clear(_), LoadOp::Clear(_)) | (LoadOp::Load, LoadOp::Load) - ) - } -} - -impl<V: Default> Default for LoadOp<V> { - fn default() -> Self { - Self::Clear(Default::default()) - } -} - -/// Operation to perform to the output attachment at the end of a render pass. -/// -/// Corresponds to [WebGPU `GPUStoreOp`](https://gpuweb.github.io/gpuweb/#enumdef-gpustoreop). -#[repr(C)] -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Default)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] -pub enum StoreOp { - /// Stores the resulting value of the render pass for this attachment. - #[default] - Store = 0, - /// Discards the resulting value of the render pass for this attachment. - /// - /// The attachment will be treated as uninitialized afterwards. - /// (If only either Depth or Stencil texture-aspects is set to `Discard`, - /// the respective other texture-aspect will be preserved.) - /// - /// This can be significantly faster on tile-based render hardware. - /// - /// Prefer this if the attachment is not read by subsequent passes. - Discard = 1, -} - -/// Pair of load and store operations for an attachment aspect. -/// -/// This type is unique to the Rust API of `wgpu`. In the WebGPU specification, -/// separate `loadOp` and `storeOp` fields are used instead. -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Operations<V> { - /// How data should be read through this attachment. - pub load: LoadOp<V>, - /// Whether data will be written to through this attachment. - /// - /// Note that resolve textures (if specified) are always written to, - /// regardless of this setting. - pub store: StoreOp, -} - -impl<V: Default> Default for Operations<V> { - #[inline] - fn default() -> Self { - Self { - load: LoadOp::<V>::default(), - store: StoreOp::default(), - } - } -} - -/// Describes the depth/stencil state in a render pipeline. -/// -/// Corresponds to [WebGPU `GPUDepthStencilState`]( -/// https://gpuweb.github.io/gpuweb/#dictdef-gpudepthstencilstate). -#[repr(C)] -#[derive(Clone, Debug, Hash, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct DepthStencilState { - /// Format of the depth/stencil buffer, must be special depth format. Must match the format - /// of the depth/stencil attachment in [`CommandEncoder::begin_render_pass`][CEbrp]. - /// - /// [CEbrp]: ../wgpu/struct.CommandEncoder.html#method.begin_render_pass - pub format: TextureFormat, - /// If disabled, depth will not be written to. - pub depth_write_enabled: bool, - /// Comparison function used to compare depth values in the depth test. - pub depth_compare: CompareFunction, - /// Stencil state. - #[cfg_attr(feature = "serde", serde(default))] - pub stencil: StencilState, - /// Depth bias state. - #[cfg_attr(feature = "serde", serde(default))] - pub bias: DepthBiasState, -} - -impl DepthStencilState { - /// Returns true if the depth testing is enabled. - #[must_use] - pub fn is_depth_enabled(&self) -> bool { - self.depth_compare != CompareFunction::Always || self.depth_write_enabled - } - - /// Returns true if the state doesn't mutate the depth buffer. - #[must_use] - pub fn is_depth_read_only(&self) -> bool { - !self.depth_write_enabled - } - - /// Returns true if the state doesn't mutate the stencil. - #[must_use] - pub fn is_stencil_read_only(&self, cull_mode: Option<Face>) -> bool { - self.stencil.is_read_only(cull_mode) - } - - /// Returns true if the state doesn't mutate either depth or stencil of the target. - #[must_use] - pub fn is_read_only(&self, cull_mode: Option<Face>) -> bool { - self.is_depth_read_only() && self.is_stencil_read_only(cull_mode) - } -} - -/// Format of indices used with pipeline. -/// -/// Corresponds to [WebGPU `GPUIndexFormat`]( -/// https://gpuweb.github.io/gpuweb/#enumdef-gpuindexformat). -#[repr(C)] -#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] -pub enum IndexFormat { - /// Indices are 16 bit unsigned integers. - Uint16 = 0, - /// Indices are 32 bit unsigned integers. - #[default] - Uint32 = 1, -} - -impl IndexFormat { - /// Returns the size in bytes of the index format - pub fn byte_size(&self) -> usize { - match self { - IndexFormat::Uint16 => 2, - IndexFormat::Uint32 => 4, - } - } -} - -/// Operation to perform on the stencil value. -/// -/// Corresponds to [WebGPU `GPUStencilOperation`]( -/// https://gpuweb.github.io/gpuweb/#enumdef-gpustenciloperation). -#[repr(C)] -#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] -pub enum StencilOperation { - /// Keep stencil value unchanged. - #[default] - Keep = 0, - /// Set stencil value to zero. - Zero = 1, - /// Replace stencil value with value provided in most recent call to - /// [`RenderPass::set_stencil_reference`][RPssr]. - /// - /// [RPssr]: ../wgpu/struct.RenderPass.html#method.set_stencil_reference - Replace = 2, - /// Bitwise inverts stencil value. - Invert = 3, - /// Increments stencil value by one, clamping on overflow. - IncrementClamp = 4, - /// Decrements stencil value by one, clamping on underflow. - DecrementClamp = 5, - /// Increments stencil value by one, wrapping on overflow. - IncrementWrap = 6, - /// Decrements stencil value by one, wrapping on underflow. - DecrementWrap = 7, -} - -/// Describes stencil state in a render pipeline. -/// -/// If you are not using stencil state, set this to [`StencilFaceState::IGNORE`]. -/// -/// Corresponds to [WebGPU `GPUStencilFaceState`]( -/// https://gpuweb.github.io/gpuweb/#dictdef-gpustencilfacestate). -#[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct StencilFaceState { - /// Comparison function that determines if the fail_op or pass_op is used on the stencil buffer. - pub compare: CompareFunction, - /// Operation that is performed when stencil test fails. - pub fail_op: StencilOperation, - /// Operation that is performed when depth test fails but stencil test succeeds. - pub depth_fail_op: StencilOperation, - /// Operation that is performed when stencil test success. - pub pass_op: StencilOperation, -} - -impl StencilFaceState { - /// Ignore the stencil state for the face. - pub const IGNORE: Self = StencilFaceState { - compare: CompareFunction::Always, - fail_op: StencilOperation::Keep, - depth_fail_op: StencilOperation::Keep, - pass_op: StencilOperation::Keep, - }; - - /// Returns true if the face state uses the reference value for testing or operation. - #[must_use] - pub fn needs_ref_value(&self) -> bool { - self.compare.needs_ref_value() - || self.fail_op == StencilOperation::Replace - || self.depth_fail_op == StencilOperation::Replace - || self.pass_op == StencilOperation::Replace - } - - /// Returns true if the face state doesn't mutate the target values. - #[must_use] - pub fn is_read_only(&self) -> bool { - self.pass_op == StencilOperation::Keep - && self.depth_fail_op == StencilOperation::Keep - && self.fail_op == StencilOperation::Keep - } -} - -impl Default for StencilFaceState { - fn default() -> Self { - Self::IGNORE - } -} - -/// Comparison function used for depth and stencil operations. -/// -/// Corresponds to [WebGPU `GPUCompareFunction`]( -/// https://gpuweb.github.io/gpuweb/#enumdef-gpucomparefunction). -#[repr(C)] -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] -pub enum CompareFunction { - /// Function never passes - Never = 1, - /// Function passes if new value less than existing value - Less = 2, - /// Function passes if new value is equal to existing value. When using - /// this compare function, make sure to mark your Vertex Shader's `@builtin(position)` - /// output as `@invariant` to prevent artifacting. - Equal = 3, - /// Function passes if new value is less than or equal to existing value - LessEqual = 4, - /// Function passes if new value is greater than existing value - Greater = 5, - /// Function passes if new value is not equal to existing value. When using - /// this compare function, make sure to mark your Vertex Shader's `@builtin(position)` - /// output as `@invariant` to prevent artifacting. - NotEqual = 6, - /// Function passes if new value is greater than or equal to existing value - GreaterEqual = 7, - /// Function always passes - Always = 8, -} - -impl CompareFunction { - /// Returns true if the comparison depends on the reference value. - #[must_use] - pub fn needs_ref_value(self) -> bool { - match self { - Self::Never | Self::Always => false, - _ => true, - } - } -} - -/// Whether a vertex buffer is indexed by vertex or by instance. -/// -/// Consider a call to [`RenderPass::draw`] like this: -/// -/// ```ignore -/// render_pass.draw(vertices, instances) -/// ``` -/// -/// where `vertices` is a `Range<u32>` of vertex indices, and -/// `instances` is a `Range<u32>` of instance indices. -/// -/// For this call, `wgpu` invokes the vertex shader entry point once -/// for every possible `(v, i)` pair, where `v` is drawn from -/// `vertices` and `i` is drawn from `instances`. These invocations -/// may happen in any order, and will usually run in parallel. -/// -/// Each vertex buffer has a step mode, established by the -/// [`step_mode`] field of its [`VertexBufferLayout`], given when the -/// pipeline was created. Buffers whose step mode is [`Vertex`] use -/// `v` as the index into their contents, whereas buffers whose step -/// mode is [`Instance`] use `i`. The indicated buffer element then -/// contributes zero or more attribute values for the `(v, i)` vertex -/// shader invocation to use, based on the [`VertexBufferLayout`]'s -/// [`attributes`] list. -/// -/// You can visualize the results from all these vertex shader -/// invocations as a matrix with a row for each `i` from `instances`, -/// and with a column for each `v` from `vertices`. In one sense, `v` -/// and `i` are symmetrical: both are used to index vertex buffers and -/// provide attribute values. But the key difference between `v` and -/// `i` is that line and triangle primitives are built from the values -/// of each row, along which `i` is constant and `v` varies, not the -/// columns. -/// -/// An indexed draw call works similarly: -/// -/// ```ignore -/// render_pass.draw_indexed(indices, base_vertex, instances) -/// ``` -/// -/// The only difference is that `v` values are drawn from the contents -/// of the index buffer—specifically, the subrange of the index -/// buffer given by `indices`—instead of simply being sequential -/// integers, as they are in a `draw` call. -/// -/// A non-instanced call, where `instances` is `0..1`, is simply a -/// matrix with only one row. -/// -/// Corresponds to [WebGPU `GPUVertexStepMode`]( -/// https://gpuweb.github.io/gpuweb/#enumdef-gpuvertexstepmode). -/// -/// [`RenderPass::draw`]: ../wgpu/struct.RenderPass.html#method.draw -/// [`VertexBufferLayout`]: ../wgpu/struct.VertexBufferLayout.html -/// [`step_mode`]: ../wgpu/struct.VertexBufferLayout.html#structfield.step_mode -/// [`attributes`]: ../wgpu/struct.VertexBufferLayout.html#structfield.attributes -/// [`Vertex`]: VertexStepMode::Vertex -/// [`Instance`]: VertexStepMode::Instance -#[repr(C)] -#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] -pub enum VertexStepMode { - /// Vertex data is advanced every vertex. - #[default] - Vertex = 0, - /// Vertex data is advanced every instance. - Instance = 1, -} - -/// Vertex inputs (attributes) to shaders. -/// -/// These are used to specify the individual attributes within a [`VertexBufferLayout`]. -/// See its documentation for an example. -/// -/// The [`vertex_attr_array!`] macro can help create these with appropriate offsets. -/// -/// Corresponds to [WebGPU `GPUVertexAttribute`]( -/// https://gpuweb.github.io/gpuweb/#dictdef-gpuvertexattribute). -/// -/// [`vertex_attr_array!`]: ../wgpu/macro.vertex_attr_array.html -/// [`VertexBufferLayout`]: ../wgpu/struct.VertexBufferLayout.html -#[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct VertexAttribute { - /// Format of the input - pub format: VertexFormat, - /// Byte offset of the start of the input - pub offset: BufferAddress, - /// Location for this input. Must match the location in the shader. - pub shader_location: ShaderLocation, -} - -/// Vertex Format for a [`VertexAttribute`] (input). -/// -/// Corresponds to [WebGPU `GPUVertexFormat`]( -/// https://gpuweb.github.io/gpuweb/#enumdef-gpuvertexformat). -#[repr(C)] -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))] -pub enum VertexFormat { - /// One unsigned byte (u8). `u32` in shaders. - Uint8 = 0, - /// Two unsigned bytes (u8). `vec2<u32>` in shaders. - Uint8x2 = 1, - /// Four unsigned bytes (u8). `vec4<u32>` in shaders. - Uint8x4 = 2, - /// One signed byte (i8). `i32` in shaders. - Sint8 = 3, - /// Two signed bytes (i8). `vec2<i32>` in shaders. - Sint8x2 = 4, - /// Four signed bytes (i8). `vec4<i32>` in shaders. - Sint8x4 = 5, - /// One unsigned byte (u8). [0, 255] converted to float [0, 1] `f32` in shaders. - Unorm8 = 6, - /// Two unsigned bytes (u8). [0, 255] converted to float [0, 1] `vec2<f32>` in shaders. - Unorm8x2 = 7, - /// Four unsigned bytes (u8). [0, 255] converted to float [0, 1] `vec4<f32>` in shaders. - Unorm8x4 = 8, - /// One signed byte (i8). [−127, 127] converted to float [−1, 1] `f32` in shaders. - Snorm8 = 9, - /// Two signed bytes (i8). [−127, 127] converted to float [−1, 1] `vec2<f32>` in shaders. - Snorm8x2 = 10, - /// Four signed bytes (i8). [−127, 127] converted to float [−1, 1] `vec4<f32>` in shaders. - Snorm8x4 = 11, - /// One unsigned short (u16). `u32` in shaders. - Uint16 = 12, - /// Two unsigned shorts (u16). `vec2<u32>` in shaders. - Uint16x2 = 13, - /// Four unsigned shorts (u16). `vec4<u32>` in shaders. - Uint16x4 = 14, - /// One signed short (u16). `i32` in shaders. - Sint16 = 15, - /// Two signed shorts (i16). `vec2<i32>` in shaders. - Sint16x2 = 16, - /// Four signed shorts (i16). `vec4<i32>` in shaders. - Sint16x4 = 17, - /// One unsigned short (u16). [0, 65535] converted to float [0, 1] `f32` in shaders. - Unorm16 = 18, - /// Two unsigned shorts (u16). [0, 65535] converted to float [0, 1] `vec2<f32>` in shaders. - Unorm16x2 = 19, - /// Four unsigned shorts (u16). [0, 65535] converted to float [0, 1] `vec4<f32>` in shaders. - Unorm16x4 = 20, - /// One signed short (i16). [−32767, 32767] converted to float [−1, 1] `f32` in shaders. - Snorm16 = 21, - /// Two signed shorts (i16). [−32767, 32767] converted to float [−1, 1] `vec2<f32>` in shaders. - Snorm16x2 = 22, - /// Four signed shorts (i16). [−32767, 32767] converted to float [−1, 1] `vec4<f32>` in shaders. - Snorm16x4 = 23, - /// One half-precision float (no Rust equiv). `f32` in shaders. - Float16 = 24, - /// Two half-precision floats (no Rust equiv). `vec2<f32>` in shaders. - Float16x2 = 25, - /// Four half-precision floats (no Rust equiv). `vec4<f32>` in shaders. - Float16x4 = 26, - /// One single-precision float (f32). `f32` in shaders. - Float32 = 27, - /// Two single-precision floats (f32). `vec2<f32>` in shaders. - Float32x2 = 28, - /// Three single-precision floats (f32). `vec3<f32>` in shaders. - Float32x3 = 29, - /// Four single-precision floats (f32). `vec4<f32>` in shaders. - Float32x4 = 30, - /// One unsigned int (u32). `u32` in shaders. - Uint32 = 31, - /// Two unsigned ints (u32). `vec2<u32>` in shaders. - Uint32x2 = 32, - /// Three unsigned ints (u32). `vec3<u32>` in shaders. - Uint32x3 = 33, - /// Four unsigned ints (u32). `vec4<u32>` in shaders. - Uint32x4 = 34, - /// One signed int (i32). `i32` in shaders. - Sint32 = 35, - /// Two signed ints (i32). `vec2<i32>` in shaders. - Sint32x2 = 36, - /// Three signed ints (i32). `vec3<i32>` in shaders. - Sint32x3 = 37, - /// Four signed ints (i32). `vec4<i32>` in shaders. - Sint32x4 = 38, - /// One double-precision float (f64). `f32` in shaders. Requires [`Features::VERTEX_ATTRIBUTE_64BIT`]. - Float64 = 39, - /// Two double-precision floats (f64). `vec2<f32>` in shaders. Requires [`Features::VERTEX_ATTRIBUTE_64BIT`]. - Float64x2 = 40, - /// Three double-precision floats (f64). `vec3<f32>` in shaders. Requires [`Features::VERTEX_ATTRIBUTE_64BIT`]. - Float64x3 = 41, - /// Four double-precision floats (f64). `vec4<f32>` in shaders. Requires [`Features::VERTEX_ATTRIBUTE_64BIT`]. - Float64x4 = 42, - /// Three unsigned 10-bit integers and one 2-bit integer, packed into a 32-bit integer (u32). [0, 1024] converted to float [0, 1] `vec4<f32>` in shaders. - #[cfg_attr(feature = "serde", serde(rename = "unorm10-10-10-2"))] - Unorm10_10_10_2 = 43, - /// Four unsigned 8-bit integers, packed into a 32-bit integer (u32). [0, 255] converted to float [0, 1] `vec4<f32>` in shaders. - #[cfg_attr(feature = "serde", serde(rename = "unorm8x4-bgra"))] - Unorm8x4Bgra = 44, -} - -impl VertexFormat { - /// Returns the byte size of the format. - #[must_use] - pub const fn size(&self) -> u64 { - match self { - Self::Uint8 | Self::Sint8 | Self::Unorm8 | Self::Snorm8 => 1, - Self::Uint8x2 - | Self::Sint8x2 - | Self::Unorm8x2 - | Self::Snorm8x2 - | Self::Uint16 - | Self::Sint16 - | Self::Unorm16 - | Self::Snorm16 - | Self::Float16 => 2, - Self::Uint8x4 - | Self::Sint8x4 - | Self::Unorm8x4 - | Self::Snorm8x4 - | Self::Uint16x2 - | Self::Sint16x2 - | Self::Unorm16x2 - | Self::Snorm16x2 - | Self::Float16x2 - | Self::Float32 - | Self::Uint32 - | Self::Sint32 - | Self::Unorm10_10_10_2 - | Self::Unorm8x4Bgra => 4, - Self::Uint16x4 - | Self::Sint16x4 - | Self::Unorm16x4 - | Self::Snorm16x4 - | Self::Float16x4 - | Self::Float32x2 - | Self::Uint32x2 - | Self::Sint32x2 - | Self::Float64 => 8, - Self::Float32x3 | Self::Uint32x3 | Self::Sint32x3 => 12, - Self::Float32x4 | Self::Uint32x4 | Self::Sint32x4 | Self::Float64x2 => 16, - Self::Float64x3 => 24, - Self::Float64x4 => 32, - } - } - - /// Returns the size read by an acceleration structure build of the vertex format. This is - /// slightly different from [`Self::size`] because the alpha component of 4-component formats - /// are not read in an acceleration structure build, allowing for a smaller stride. - #[must_use] - pub const fn min_acceleration_structure_vertex_stride(&self) -> u64 { - match self { - Self::Float16x2 | Self::Snorm16x2 => 4, - Self::Float32x3 => 12, - Self::Float32x2 => 8, - // This is the minimum value from DirectX - // > A16 component is ignored, other data can be packed there, such as setting vertex stride to 6 bytes - // - // https://microsoft.github.io/DirectX-Specs/d3d/Raytracing.html#d3d12_raytracing_geometry_triangles_desc - // - // Vulkan does not express a minimum stride. - Self::Float16x4 | Self::Snorm16x4 => 6, - _ => unreachable!(), - } - } - - /// Returns the alignment required for `wgpu::BlasTriangleGeometry::vertex_stride` - #[must_use] - pub const fn acceleration_structure_stride_alignment(&self) -> u64 { - match self { - Self::Float16x4 | Self::Float16x2 | Self::Snorm16x4 | Self::Snorm16x2 => 2, - Self::Float32x2 | Self::Float32x3 => 4, - _ => unreachable!(), - } - } -} - -bitflags::bitflags! { - /// Different ways that you can use a buffer. - /// - /// The usages determine what kind of memory the buffer is allocated from and what - /// actions the buffer can partake in. - /// - /// Specifying only usages the application will actually perform may increase performance. - /// Additionally, on the WebGL backend, there are restrictions on [`BufferUsages::INDEX`]; - /// see [`DownlevelFlags::UNRESTRICTED_INDEX_BUFFER`] for more information. - /// - /// Corresponds to [WebGPU `GPUBufferUsageFlags`]( - /// https://gpuweb.github.io/gpuweb/#typedefdef-gpubufferusageflags). - #[repr(transparent)] - #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] - #[cfg_attr(feature = "serde", serde(transparent))] - #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] - pub struct BufferUsages: u32 { - /// Allow a buffer to be mapped for reading using [`Buffer::map_async`] + [`Buffer::get_mapped_range`]. - /// This does not include creating a buffer with [`BufferDescriptor::mapped_at_creation`] set. - /// - /// If [`Features::MAPPABLE_PRIMARY_BUFFERS`] isn't enabled, the only other usage a buffer - /// may have is COPY_DST. - const MAP_READ = 1 << 0; - /// Allow a buffer to be mapped for writing using [`Buffer::map_async`] + [`Buffer::get_mapped_range_mut`]. - /// This does not include creating a buffer with [`BufferDescriptor::mapped_at_creation`] set. - /// - /// If [`Features::MAPPABLE_PRIMARY_BUFFERS`] feature isn't enabled, the only other usage a buffer - /// may have is COPY_SRC. - const MAP_WRITE = 1 << 1; - /// Allow a buffer to be the source buffer for a [`CommandEncoder::copy_buffer_to_buffer`] or [`CommandEncoder::copy_buffer_to_texture`] - /// operation. - const COPY_SRC = 1 << 2; - /// Allow a buffer to be the destination buffer for a [`CommandEncoder::copy_buffer_to_buffer`], [`CommandEncoder::copy_texture_to_buffer`], - /// [`CommandEncoder::clear_buffer`] or [`Queue::write_buffer`] operation. - const COPY_DST = 1 << 3; - /// Allow a buffer to be the index buffer in a draw operation. - const INDEX = 1 << 4; - /// Allow a buffer to be the vertex buffer in a draw operation. - const VERTEX = 1 << 5; - /// Allow a buffer to be a [`BufferBindingType::Uniform`] inside a bind group. - const UNIFORM = 1 << 6; - /// Allow a buffer to be a [`BufferBindingType::Storage`] inside a bind group. - const STORAGE = 1 << 7; - /// Allow a buffer to be the indirect buffer in an indirect draw call. - const INDIRECT = 1 << 8; - /// Allow a buffer to be the destination buffer for a [`CommandEncoder::resolve_query_set`] operation. - const QUERY_RESOLVE = 1 << 9; - /// Allows a buffer to be used as input for a bottom level acceleration structure build - const BLAS_INPUT = 1 << 10; - /// Allows a buffer to be used as input for a top level acceleration structure build - const TLAS_INPUT = 1 << 11; - } -} - -bitflags::bitflags! { - /// Similar to `BufferUsages`, but used only for `CommandEncoder::transition_resources`. - #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] - #[cfg_attr(feature = "serde", serde(transparent))] - #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] - pub struct BufferUses: u16 { - /// The argument to a read-only mapping. - const MAP_READ = 1 << 0; - /// The argument to a write-only mapping. - const MAP_WRITE = 1 << 1; - /// The source of a hardware copy. - /// cbindgen:ignore - const COPY_SRC = 1 << 2; - /// The destination of a hardware copy. - /// cbindgen:ignore - const COPY_DST = 1 << 3; - /// The index buffer used for drawing. - const INDEX = 1 << 4; - /// A vertex buffer used for drawing. - const VERTEX = 1 << 5; - /// A uniform buffer bound in a bind group. - const UNIFORM = 1 << 6; - /// A read-only storage buffer used in a bind group. - /// cbindgen:ignore - const STORAGE_READ_ONLY = 1 << 7; - /// A read-write buffer used in a bind group. - /// cbindgen:ignore - const STORAGE_READ_WRITE = 1 << 8; - /// The indirect or count buffer in a indirect draw or dispatch. - const INDIRECT = 1 << 9; - /// A buffer used to store query results. - const QUERY_RESOLVE = 1 << 10; - /// Buffer used for acceleration structure building. - const ACCELERATION_STRUCTURE_SCRATCH = 1 << 11; - /// Buffer used for bottom level acceleration structure building. - const BOTTOM_LEVEL_ACCELERATION_STRUCTURE_INPUT = 1 << 12; - /// Buffer used for top level acceleration structure building. - const TOP_LEVEL_ACCELERATION_STRUCTURE_INPUT = 1 << 13; - /// A buffer used to store the compacted size of an acceleration structure - const ACCELERATION_STRUCTURE_QUERY = 1 << 14; - /// The combination of states that a buffer may be in _at the same time_. - const INCLUSIVE = Self::MAP_READ.bits() | Self::COPY_SRC.bits() | - Self::INDEX.bits() | Self::VERTEX.bits() | Self::UNIFORM.bits() | - Self::STORAGE_READ_ONLY.bits() | Self::INDIRECT.bits() | Self::BOTTOM_LEVEL_ACCELERATION_STRUCTURE_INPUT.bits() | Self::TOP_LEVEL_ACCELERATION_STRUCTURE_INPUT.bits(); - /// The combination of states that a buffer must exclusively be in. - const EXCLUSIVE = Self::MAP_WRITE.bits() | Self::COPY_DST.bits() | Self::STORAGE_READ_WRITE.bits() | Self::ACCELERATION_STRUCTURE_SCRATCH.bits(); - /// The combination of all usages that the are guaranteed to be be ordered by the hardware. - /// If a usage is ordered, then if the buffer state doesn't change between draw calls, there - /// are no barriers needed for synchronization. - const ORDERED = Self::INCLUSIVE.bits() | Self::MAP_WRITE.bits(); - } -} - -/// A buffer transition for use with `CommandEncoder::transition_resources`. -#[derive(Clone, Debug)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct BufferTransition<T> { - /// The buffer to transition. - pub buffer: T, - /// The new state to transition to. - pub state: BufferUses, -} - -/// Describes a [`Buffer`](../wgpu/struct.Buffer.html). -/// -/// Corresponds to [WebGPU `GPUBufferDescriptor`]( -/// https://gpuweb.github.io/gpuweb/#dictdef-gpubufferdescriptor). -#[repr(C)] -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct BufferDescriptor<L> { - /// Debug label of a buffer. This will show up in graphics debuggers for easy identification. - pub label: L, - /// Size of a buffer, in bytes. - pub size: BufferAddress, - /// Usages of a buffer. If the buffer is used in any way that isn't specified here, the operation - /// will panic. - /// - /// Specifying only usages the application will actually perform may increase performance. - /// Additionally, on the WebGL backend, there are restrictions on [`BufferUsages::INDEX`]; - /// see [`DownlevelFlags::UNRESTRICTED_INDEX_BUFFER`] for more information. - pub usage: BufferUsages, - /// Allows a buffer to be mapped immediately after they are made. It does not have to be [`BufferUsages::MAP_READ`] or - /// [`BufferUsages::MAP_WRITE`], all buffers are allowed to be mapped at creation. - /// - /// If this is `true`, [`size`](#structfield.size) must be a multiple of - /// [`COPY_BUFFER_ALIGNMENT`]. - pub mapped_at_creation: bool, -} - -impl<L> BufferDescriptor<L> { - /// Takes a closure and maps the label of the buffer descriptor into another. - #[must_use] - pub fn map_label<K>(&self, fun: impl FnOnce(&L) -> K) -> BufferDescriptor<K> { - BufferDescriptor { - label: fun(&self.label), - size: self.size, - usage: self.usage, - mapped_at_creation: self.mapped_at_creation, - } - } -} - -/// Describes a [`CommandEncoder`](../wgpu/struct.CommandEncoder.html). -/// -/// Corresponds to [WebGPU `GPUCommandEncoderDescriptor`]( -/// https://gpuweb.github.io/gpuweb/#dictdef-gpucommandencoderdescriptor). -#[repr(C)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct CommandEncoderDescriptor<L> { - /// Debug label for the command encoder. This will show up in graphics debuggers for easy identification. - pub label: L, -} - -impl<L> CommandEncoderDescriptor<L> { - /// Takes a closure and maps the label of the command encoder descriptor into another. - #[must_use] - pub fn map_label<K>(&self, fun: impl FnOnce(&L) -> K) -> CommandEncoderDescriptor<K> { - CommandEncoderDescriptor { - label: fun(&self.label), - } - } -} - -impl<T> Default for CommandEncoderDescriptor<Option<T>> { - fn default() -> Self { - Self { label: None } - } -} - -/// Timing and queueing with which frames are actually displayed to the user. -/// -/// Use this as part of a [`SurfaceConfiguration`] to control the behavior of -/// [`SurfaceTexture::present()`]. -/// -/// Some modes are only supported by some backends. -/// You can use one of the `Auto*` modes, [`Fifo`](Self::Fifo), -/// or choose one of the supported modes from [`SurfaceCapabilities::present_modes`]. -/// -/// [presented]: ../wgpu/struct.SurfaceTexture.html#method.present -/// [`SurfaceTexture::present()`]: ../wgpu/struct.SurfaceTexture.html#method.present -#[repr(C)] -#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum PresentMode { - /// Chooses the first supported mode out of: - /// - /// 1. [`FifoRelaxed`](Self::FifoRelaxed) - /// 2. [`Fifo`](Self::Fifo) - /// - /// Because of the fallback behavior, this is supported everywhere. - AutoVsync = 0, - - /// Chooses the first supported mode out of: - /// - /// 1. [`Immediate`](Self::Immediate) - /// 2. [`Mailbox`](Self::Mailbox) - /// 3. [`Fifo`](Self::Fifo) - /// - /// Because of the fallback behavior, this is supported everywhere. - AutoNoVsync = 1, - - /// Presentation frames are kept in a First-In-First-Out queue approximately 3 frames - /// long. Every vertical blanking period, the presentation engine will pop a frame - /// off the queue to display. If there is no frame to display, it will present the same - /// frame again until the next vblank. - /// - /// When a present command is executed on the GPU, the presented image is added on the queue. - /// - /// Calls to [`Surface::get_current_texture()`] will block until there is a spot in the queue. - /// - /// * **Tearing:** No tearing will be observed. - /// * **Supported on**: All platforms. - /// * **Also known as**: "Vsync On" - /// - /// This is the [default](Self::default) value for `PresentMode`. - /// If you don't know what mode to choose, choose this mode. - /// - /// [`Surface::get_current_texture()`]: ../wgpu/struct.Surface.html#method.get_current_texture - #[default] - Fifo = 2, - - /// Presentation frames are kept in a First-In-First-Out queue approximately 3 frames - /// long. Every vertical blanking period, the presentation engine will pop a frame - /// off the queue to display. If there is no frame to display, it will present the - /// same frame until there is a frame in the queue. The moment there is a frame in the - /// queue, it will immediately pop the frame off the queue. - /// - /// When a present command is executed on the GPU, the presented image is added on the queue. - /// - /// Calls to [`Surface::get_current_texture()`] will block until there is a spot in the queue. - /// - /// * **Tearing**: - /// Tearing will be observed if frames last more than one vblank as the front buffer. - /// * **Supported on**: AMD on Vulkan. - /// * **Also known as**: "Adaptive Vsync" - /// - /// [`Surface::get_current_texture()`]: ../wgpu/struct.Surface.html#method.get_current_texture - FifoRelaxed = 3, - - /// Presentation frames are not queued at all. The moment a present command - /// is executed on the GPU, the presented image is swapped onto the front buffer - /// immediately. - /// - /// * **Tearing**: Tearing can be observed. - /// * **Supported on**: Most platforms except older DX12 and Wayland. - /// * **Also known as**: "Vsync Off" - Immediate = 4, - - /// Presentation frames are kept in a single-frame queue. Every vertical blanking period, - /// the presentation engine will pop a frame from the queue. If there is no frame to display, - /// it will present the same frame again until the next vblank. - /// - /// When a present command is executed on the GPU, the frame will be put into the queue. - /// If there was already a frame in the queue, the new frame will _replace_ the old frame - /// on the queue. - /// - /// * **Tearing**: No tearing will be observed. - /// * **Supported on**: DX12 on Windows 10, NVidia on Vulkan and Wayland on Vulkan. - /// * **Also known as**: "Fast Vsync" - Mailbox = 5, -} - -/// Specifies how the alpha channel of the textures should be handled during -/// compositing. -#[repr(C)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))] -pub enum CompositeAlphaMode { - /// Chooses either `Opaque` or `Inherit` automatically,depending on the - /// `alpha_mode` that the current surface can support. - Auto = 0, - /// The alpha channel, if it exists, of the textures is ignored in the - /// compositing process. Instead, the textures is treated as if it has a - /// constant alpha of 1.0. - Opaque = 1, - /// The alpha channel, if it exists, of the textures is respected in the - /// compositing process. The non-alpha channels of the textures are - /// expected to already be multiplied by the alpha channel by the - /// application. - PreMultiplied = 2, - /// The alpha channel, if it exists, of the textures is respected in the - /// compositing process. The non-alpha channels of the textures are not - /// expected to already be multiplied by the alpha channel by the - /// application; instead, the compositor will multiply the non-alpha - /// channels of the texture by the alpha channel during compositing. - PostMultiplied = 3, - /// The alpha channel, if it exists, of the textures is unknown for processing - /// during compositing. Instead, the application is responsible for setting - /// the composite alpha blending mode using native WSI command. If not set, - /// then a platform-specific default will be used. - Inherit = 4, -} - -impl Default for CompositeAlphaMode { - fn default() -> Self { - Self::Auto - } -} - -bitflags::bitflags! { - /// Different ways that you can use a texture. - /// - /// The usages determine what kind of memory the texture is allocated from and what - /// actions the texture can partake in. - /// - /// Corresponds to [WebGPU `GPUTextureUsageFlags`]( - /// https://gpuweb.github.io/gpuweb/#typedefdef-gputextureusageflags). - #[repr(transparent)] - #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] - #[cfg_attr(feature = "serde", serde(transparent))] - #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] - pub struct TextureUsages: u32 { - // - // ---- Start numbering at 1 << 0 ---- - // - // WebGPU features: - // - /// Allows a texture to be the source in a [`CommandEncoder::copy_texture_to_buffer`] or - /// [`CommandEncoder::copy_texture_to_texture`] operation. - const COPY_SRC = 1 << 0; - /// Allows a texture to be the destination in a [`CommandEncoder::copy_buffer_to_texture`], - /// [`CommandEncoder::copy_texture_to_texture`], or [`Queue::write_texture`] operation. - const COPY_DST = 1 << 1; - /// Allows a texture to be a [`BindingType::Texture`] in a bind group. - const TEXTURE_BINDING = 1 << 2; - /// Allows a texture to be a [`BindingType::StorageTexture`] in a bind group. - const STORAGE_BINDING = 1 << 3; - /// Allows a texture to be an output attachment of a render pass. - /// - /// Consider adding [`TextureUsages::TRANSIENT`] if the contents are not reused. - const RENDER_ATTACHMENT = 1 << 4; - - // - // ---- Restart Numbering for Native Features --- - // - // Native Features: - // - /// Allows a texture to be used with image atomics. Requires [`Features::TEXTURE_ATOMIC`]. - const STORAGE_ATOMIC = 1 << 16; - /// Specifies the contents of this texture will not be used in another pass to potentially reduce memory usage and bandwidth. - /// - /// No-op on platforms on platforms that do not benefit from transient textures. - /// Generally mobile and Apple chips care about this. - /// - /// Incompatible with ALL other usages except [`TextureUsages::RENDER_ATTACHMENT`] and requires it. - /// - /// Requires [`StoreOp::Discard`]. - const TRANSIENT = 1 << 17; - } -} - -bitflags::bitflags! { - /// Similar to `TextureUsages`, but used only for `CommandEncoder::transition_resources`. - #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] - #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] - #[cfg_attr(feature = "serde", serde(transparent))] - pub struct TextureUses: u16 { - /// The texture is in unknown state. - const UNINITIALIZED = 1 << 0; - /// Ready to present image to the surface. - const PRESENT = 1 << 1; - /// The source of a hardware copy. - /// cbindgen:ignore - const COPY_SRC = 1 << 2; - /// The destination of a hardware copy. - /// cbindgen:ignore - const COPY_DST = 1 << 3; - /// Read-only sampled or fetched resource. - const RESOURCE = 1 << 4; - /// The color target of a renderpass. - const COLOR_TARGET = 1 << 5; - /// Read-only depth stencil usage. - const DEPTH_STENCIL_READ = 1 << 6; - /// Read-write depth stencil usage - const DEPTH_STENCIL_WRITE = 1 << 7; - /// Read-only storage texture usage. Corresponds to a UAV in d3d, so is exclusive, despite being read only. - /// cbindgen:ignore - const STORAGE_READ_ONLY = 1 << 8; - /// Write-only storage texture usage. - /// cbindgen:ignore - const STORAGE_WRITE_ONLY = 1 << 9; - /// Read-write storage texture usage. - /// cbindgen:ignore - const STORAGE_READ_WRITE = 1 << 10; - /// Image atomic enabled storage. - /// cbindgen:ignore - const STORAGE_ATOMIC = 1 << 11; - /// Transient texture that may not have any backing memory. Not a resource state stored in the trackers, only used for passing down usages to create_texture. - const TRANSIENT = 1 << 12; - /// The combination of states that a texture may be in _at the same time_. - /// cbindgen:ignore - const INCLUSIVE = Self::COPY_SRC.bits() | Self::RESOURCE.bits() | Self::DEPTH_STENCIL_READ.bits() | Self::STORAGE_READ_ONLY.bits(); - /// The combination of states that a texture must exclusively be in. - /// cbindgen:ignore - const EXCLUSIVE = Self::COPY_DST.bits() | Self::COLOR_TARGET.bits() | Self::DEPTH_STENCIL_WRITE.bits() | Self::STORAGE_WRITE_ONLY.bits() | Self::STORAGE_READ_WRITE.bits() | Self::STORAGE_ATOMIC.bits() | Self::PRESENT.bits(); - /// The combination of all usages that the are guaranteed to be be ordered by the hardware. - /// If a usage is ordered, then if the texture state doesn't change between draw calls, there - /// are no barriers needed for synchronization. - /// cbindgen:ignore - const ORDERED = Self::INCLUSIVE.bits() | Self::COLOR_TARGET.bits() | Self::DEPTH_STENCIL_WRITE.bits() | Self::STORAGE_READ_ONLY.bits(); - - /// Flag used by the wgpu-core texture tracker to say a texture is in different states for every sub-resource - const COMPLEX = 1 << 13; - /// Flag used by the wgpu-core texture tracker to say that the tracker does not know the state of the sub-resource. - /// This is different from UNINITIALIZED as that says the tracker does know, but the texture has not been initialized. - const UNKNOWN = 1 << 14; - } -} - -/// A texture transition for use with `CommandEncoder::transition_resources`. -#[derive(Clone, Debug)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct TextureTransition<T> { - /// The texture to transition. - pub texture: T, - /// An optional selector to transition only part of the texture. - /// - /// If None, the entire texture will be transitioned. - pub selector: Option<TextureSelector>, - /// The new state to transition to. - pub state: TextureUses, -} - -/// Specifies a particular set of subresources in a texture. -#[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct TextureSelector { - /// Range of mips to use. - pub mips: Range<u32>, - /// Range of layers to use. - pub layers: Range<u32>, -} - -/// Defines the capabilities of a given surface and adapter. -#[derive(Debug)] -pub struct SurfaceCapabilities { - /// List of supported formats to use with the given adapter. The first format in the vector is preferred. - /// - /// Returns an empty vector if the surface is incompatible with the adapter. - pub formats: Vec<TextureFormat>, - /// List of supported presentation modes to use with the given adapter. - /// - /// Returns an empty vector if the surface is incompatible with the adapter. - pub present_modes: Vec<PresentMode>, - /// List of supported alpha modes to use with the given adapter. - /// - /// Will return at least one element, [`CompositeAlphaMode::Opaque`] or [`CompositeAlphaMode::Inherit`]. - pub alpha_modes: Vec<CompositeAlphaMode>, - /// Bitflag of supported texture usages for the surface to use with the given adapter. - /// - /// The usage [`TextureUsages::RENDER_ATTACHMENT`] is guaranteed. - pub usages: TextureUsages, -} - -impl Default for SurfaceCapabilities { - fn default() -> Self { - Self { - formats: Vec::new(), - present_modes: Vec::new(), - alpha_modes: vec![CompositeAlphaMode::Opaque], - usages: TextureUsages::RENDER_ATTACHMENT, - } - } -} - -/// Configures a [`Surface`] for presentation. -/// -/// [`Surface`]: ../wgpu/struct.Surface.html -#[repr(C)] -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct SurfaceConfiguration<V> { - /// The usage of the swap chain. The only usage guaranteed to be supported is [`TextureUsages::RENDER_ATTACHMENT`]. - pub usage: TextureUsages, - /// The texture format of the swap chain. The only formats that are guaranteed are - /// [`TextureFormat::Bgra8Unorm`] and [`TextureFormat::Bgra8UnormSrgb`]. - pub format: TextureFormat, - /// Width of the swap chain. Must be the same size as the surface, and nonzero. - /// - /// If this is not the same size as the underlying surface (e.g. if it is - /// set once, and the window is later resized), the behaviour is defined - /// but platform-specific, and may change in the future (currently macOS - /// scales the surface, other platforms may do something else). - pub width: u32, - /// Height of the swap chain. Must be the same size as the surface, and nonzero. - /// - /// If this is not the same size as the underlying surface (e.g. if it is - /// set once, and the window is later resized), the behaviour is defined - /// but platform-specific, and may change in the future (currently macOS - /// scales the surface, other platforms may do something else). - pub height: u32, - /// Presentation mode of the swap chain. Fifo is the only mode guaranteed to be supported. - /// `FifoRelaxed`, `Immediate`, and `Mailbox` will crash if unsupported, while `AutoVsync` and - /// `AutoNoVsync` will gracefully do a designed sets of fallbacks if their primary modes are - /// unsupported. - pub present_mode: PresentMode, - /// Desired maximum number of monitor refreshes between a [`Surface::get_current_texture`] call and the - /// texture being presented to the screen. This is sometimes called "Frames in Flight". - /// - /// Defaults to `2` when created via [`Surface::get_default_config`] as this is a reasonable default. - /// - /// This is ultimately a hint to the backend implementation and will always be clamped - /// to the supported range. - /// - /// Typical values are `1` to `3`, but higher values are valid, though likely to be clamped. - /// * Choose `1` to minimize latency above all else. This only gives a single monitor refresh for all of - /// the CPU and GPU work to complete. ⚠️ As a result of these short swapchains, the CPU and GPU - /// cannot run in parallel, prioritizing latency over throughput. For applications like GUIs doing - /// a small amount of GPU work each frame that need low latency, this is a reasonable choice. - /// * Choose `2` for a balance between latency and throughput. The CPU and GPU both can each use - /// a full monitor refresh to do their computations. This is a reasonable default for most applications. - /// * Choose `3` or higher to maximize throughput, sacrificing latency when the the CPU and GPU - /// are using less than a full monitor refresh each. For applications that use CPU-side pipelining - /// of frames this may be a reasonable choice. ⚠️ On 60hz displays the latency can be very noticeable. - /// - /// This maps to the backend in the following ways: - /// - Vulkan: Number of frames in the swapchain is `desired_maximum_frame_latency + 1`, - /// clamped to the supported range. - /// - DX12: Calls [`IDXGISwapChain2::SetMaximumFrameLatency(desired_maximum_frame_latency)`][SMFL]. - /// - Metal: Sets the `maximumDrawableCount` of the underlying `CAMetalLayer` to - /// `desired_maximum_frame_latency + 1`, clamped to the supported range. - /// - OpenGL: Ignored - /// - /// It also has various subtle interactions with various present modes and APIs. - /// - DX12 + Mailbox: Limits framerate to `desired_maximum_frame_latency * Monitor Hz` fps. - /// - Vulkan/Metal + Mailbox: If this is set to `2`, limits framerate to `2 * Monitor Hz` fps. `3` or higher is unlimited. - /// - /// [`Surface::get_current_texture`]: ../wgpu/struct.Surface.html#method.get_current_texture - /// [`Surface::get_default_config`]: ../wgpu/struct.Surface.html#method.get_default_config - /// [SMFL]: https://learn.microsoft.com/en-us/windows/win32/api/dxgi1_3/nf-dxgi1_3-idxgiswapchain2-setmaximumframelatency - pub desired_maximum_frame_latency: u32, - /// Specifies how the alpha channel of the textures should be handled during compositing. - pub alpha_mode: CompositeAlphaMode, - /// Specifies what view formats will be allowed when calling `Texture::create_view` on the texture returned by `Surface::get_current_texture`. - /// - /// View formats of the same format as the texture are always allowed. - /// - /// Note: currently, only the srgb-ness is allowed to change. (ex: `Rgba8Unorm` texture + `Rgba8UnormSrgb` view) - pub view_formats: V, -} - -impl<V: Clone> SurfaceConfiguration<V> { - /// Map `view_formats` of the texture descriptor into another. - pub fn map_view_formats<M>(&self, fun: impl FnOnce(V) -> M) -> SurfaceConfiguration<M> { - SurfaceConfiguration { - usage: self.usage, - format: self.format, - width: self.width, - height: self.height, - present_mode: self.present_mode, - desired_maximum_frame_latency: self.desired_maximum_frame_latency, - alpha_mode: self.alpha_mode, - view_formats: fun(self.view_formats.clone()), - } - } -} - -/// Status of the received surface image. -#[repr(C)] -#[derive(Debug)] -pub enum SurfaceStatus { - /// No issues. - Good, - /// The swap chain is operational, but it does no longer perfectly - /// match the surface. A re-configuration is needed. - Suboptimal, - /// Unable to get the next frame, timed out. - Timeout, - /// The surface under the swap chain has changed. - Outdated, - /// The surface under the swap chain is lost. - Lost, - /// The surface status is not known since `Surface::get_current_texture` previously failed. - Unknown, -} - -/// Nanosecond timestamp used by the presentation engine. -/// -/// The specific clock depends on the window system integration (WSI) API used. -/// -/// <table> -/// <tr> -/// <td>WSI</td> -/// <td>Clock</td> -/// </tr> -/// <tr> -/// <td>IDXGISwapchain</td> -/// <td><a href="https://docs.microsoft.com/en-us/windows/win32/api/profileapi/nf-profileapi-queryperformancecounter">QueryPerformanceCounter</a></td> -/// </tr> -/// <tr> -/// <td>IPresentationManager</td> -/// <td><a href="https://docs.microsoft.com/en-us/windows/win32/api/realtimeapiset/nf-realtimeapiset-queryinterrupttimeprecise">QueryInterruptTimePrecise</a></td> -/// </tr> -/// <tr> -/// <td>CAMetalLayer</td> -/// <td><a href="https://developer.apple.com/documentation/kernel/1462446-mach_absolute_time">mach_absolute_time</a></td> -/// </tr> -/// <tr> -/// <td>VK_GOOGLE_display_timing</td> -/// <td><a href="https://linux.die.net/man/3/clock_gettime">clock_gettime(CLOCK_MONOTONIC)</a></td> -/// </tr> -/// </table> -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct PresentationTimestamp( - /// Timestamp in nanoseconds. - pub u128, -); - -impl PresentationTimestamp { - /// A timestamp that is invalid due to the platform not having a timestamp system. - pub const INVALID_TIMESTAMP: Self = Self(u128::MAX); - - /// Returns true if this timestamp is the invalid timestamp. - #[must_use] - pub fn is_invalid(self) -> bool { - self == Self::INVALID_TIMESTAMP - } -} - -/// RGBA double precision color. -/// -/// This is not to be used as a generic color type, only for specific wgpu interfaces. -#[repr(C)] -#[derive(Clone, Copy, Debug, Default, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct Color { - /// Red component of the color - pub r: f64, - /// Green component of the color - pub g: f64, - /// Blue component of the color - pub b: f64, - /// Alpha component of the color - pub a: f64, -} - -#[allow(missing_docs)] -impl Color { - pub const TRANSPARENT: Self = Self { - r: 0.0, - g: 0.0, - b: 0.0, - a: 0.0, - }; - pub const BLACK: Self = Self { - r: 0.0, - g: 0.0, - b: 0.0, - a: 1.0, - }; - pub const WHITE: Self = Self { - r: 1.0, - g: 1.0, - b: 1.0, - a: 1.0, - }; - pub const RED: Self = Self { - r: 1.0, - g: 0.0, - b: 0.0, - a: 1.0, - }; - pub const GREEN: Self = Self { - r: 0.0, - g: 1.0, - b: 0.0, - a: 1.0, - }; - pub const BLUE: Self = Self { - r: 0.0, - g: 0.0, - b: 1.0, - a: 1.0, - }; -} - -/// Dimensionality of a texture. -/// -/// Corresponds to [WebGPU `GPUTextureDimension`]( -/// https://gpuweb.github.io/gpuweb/#enumdef-gputexturedimension). -#[repr(C)] -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum TextureDimension { - /// 1D texture - #[cfg_attr(feature = "serde", serde(rename = "1d"))] - D1, - /// 2D texture - #[cfg_attr(feature = "serde", serde(rename = "2d"))] - D2, - /// 3D texture - #[cfg_attr(feature = "serde", serde(rename = "3d"))] - D3, -} - -/// Origin of a copy from a 2D image. -/// -/// Corresponds to [WebGPU `GPUOrigin2D`]( -/// https://gpuweb.github.io/gpuweb/#dictdef-gpuorigin2ddict). -#[repr(C)] -#[derive(Clone, Copy, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct Origin2d { - #[allow(missing_docs)] - pub x: u32, - #[allow(missing_docs)] - pub y: u32, -} - -impl Origin2d { - /// Zero origin. - pub const ZERO: Self = Self { x: 0, y: 0 }; - - /// Adds the third dimension to this origin - #[must_use] - pub fn to_3d(self, z: u32) -> Origin3d { - Origin3d { - x: self.x, - y: self.y, - z, - } - } -} - -impl core::fmt::Debug for Origin2d { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - (self.x, self.y).fmt(f) - } -} - -/// Origin of a copy to/from a texture. -/// -/// Corresponds to [WebGPU `GPUOrigin3D`]( -/// https://gpuweb.github.io/gpuweb/#dictdef-gpuorigin3ddict). -#[repr(C)] -#[derive(Clone, Copy, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct Origin3d { - /// X position of the origin - pub x: u32, - /// Y position of the origin - pub y: u32, - /// Z position of the origin - pub z: u32, -} - -impl Origin3d { - /// Zero origin. - pub const ZERO: Self = Self { x: 0, y: 0, z: 0 }; - - /// Removes the third dimension from this origin - #[must_use] - pub fn to_2d(self) -> Origin2d { - Origin2d { - x: self.x, - y: self.y, - } - } -} - -impl Default for Origin3d { - fn default() -> Self { - Self::ZERO - } -} - -impl core::fmt::Debug for Origin3d { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - (self.x, self.y, self.z).fmt(f) - } -} - -/// Extent of a texture related operation. -/// -/// Corresponds to [WebGPU `GPUExtent3D`]( -/// https://gpuweb.github.io/gpuweb/#dictdef-gpuextent3ddict). -#[repr(C)] -#[derive(Clone, Copy, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct Extent3d { - /// Width of the extent - pub width: u32, - /// Height of the extent - pub height: u32, - /// The depth of the extent or the number of array layers - #[cfg_attr(feature = "serde", serde(default = "default_depth"))] - pub depth_or_array_layers: u32, -} - -impl core::fmt::Debug for Extent3d { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - (self.width, self.height, self.depth_or_array_layers).fmt(f) - } -} - -#[cfg(feature = "serde")] -fn default_depth() -> u32 { - 1 -} - -impl Default for Extent3d { - fn default() -> Self { - Self { - width: 1, - height: 1, - depth_or_array_layers: 1, - } - } -} - -impl Extent3d { - /// Calculates the [physical size] backing a texture of the given - /// format and extent. This includes padding to the block width - /// and height of the format. - /// - /// This is the texture extent that you must upload at when uploading to _mipmaps_ of compressed textures. - /// - /// [physical size]: https://gpuweb.github.io/gpuweb/#physical-miplevel-specific-texture-extent - #[must_use] - pub fn physical_size(&self, format: TextureFormat) -> Self { - let (block_width, block_height) = format.block_dimensions(); - - let width = self.width.div_ceil(block_width) * block_width; - let height = self.height.div_ceil(block_height) * block_height; - - Self { - width, - height, - depth_or_array_layers: self.depth_or_array_layers, - } - } - - /// Calculates the maximum possible count of mipmaps. - /// - /// Treats the depth as part of the mipmaps. If calculating - /// for a 2DArray texture, which does not mipmap depth, set depth to 1. - #[must_use] - pub fn max_mips(&self, dim: TextureDimension) -> u32 { - match dim { - TextureDimension::D1 => 1, - TextureDimension::D2 => { - let max_dim = self.width.max(self.height); - 32 - max_dim.leading_zeros() - } - TextureDimension::D3 => { - let max_dim = self.width.max(self.height.max(self.depth_or_array_layers)); - 32 - max_dim.leading_zeros() - } - } - } - - /// Calculates the extent at a given mip level. - /// - /// This is a low-level helper for internal use. - /// - /// It does *not* account for memory size being a multiple of block size. - /// - /// TODO(<https://github.com/gfx-rs/wgpu/issues/8491>): It also does not - /// consider whether an even dimension is required due to chroma - /// subsampling, but it probably should. - /// - /// <https://gpuweb.github.io/gpuweb/#logical-miplevel-specific-texture-extent> - #[doc(hidden)] - #[must_use] - pub fn mip_level_size(&self, level: u32, dim: TextureDimension) -> Self { - Self { - width: u32::max(1, self.width >> level), - height: match dim { - TextureDimension::D1 => 1, - _ => u32::max(1, self.height >> level), - }, - depth_or_array_layers: match dim { - TextureDimension::D1 => 1, - TextureDimension::D2 => self.depth_or_array_layers, - TextureDimension::D3 => u32::max(1, self.depth_or_array_layers >> level), - }, - } - } -} - -#[test] -fn test_physical_size() { - let format = TextureFormat::Bc1RgbaUnormSrgb; // 4x4 blocks - assert_eq!( - Extent3d { - width: 7, - height: 7, - depth_or_array_layers: 1 - } - .physical_size(format), - Extent3d { - width: 8, - height: 8, - depth_or_array_layers: 1 - } - ); - // Doesn't change, already aligned - assert_eq!( - Extent3d { - width: 8, - height: 8, - depth_or_array_layers: 1 - } - .physical_size(format), - Extent3d { - width: 8, - height: 8, - depth_or_array_layers: 1 - } - ); - let format = TextureFormat::Astc { - block: AstcBlock::B8x5, - channel: AstcChannel::Unorm, - }; // 8x5 blocks - assert_eq!( - Extent3d { - width: 7, - height: 7, - depth_or_array_layers: 1 - } - .physical_size(format), - Extent3d { - width: 8, - height: 10, - depth_or_array_layers: 1 - } - ); -} - -#[test] -fn test_max_mips() { - // 1D - assert_eq!( - Extent3d { - width: 240, - height: 1, - depth_or_array_layers: 1 - } - .max_mips(TextureDimension::D1), - 1 - ); - // 2D - assert_eq!( - Extent3d { - width: 1, - height: 1, - depth_or_array_layers: 1 - } - .max_mips(TextureDimension::D2), - 1 - ); - assert_eq!( - Extent3d { - width: 60, - height: 60, - depth_or_array_layers: 1 - } - .max_mips(TextureDimension::D2), - 6 - ); - assert_eq!( - Extent3d { - width: 240, - height: 1, - depth_or_array_layers: 1000 - } - .max_mips(TextureDimension::D2), - 8 - ); - // 3D - assert_eq!( - Extent3d { - width: 16, - height: 30, - depth_or_array_layers: 60 - } - .max_mips(TextureDimension::D3), - 6 - ); -} - -/// Describes a [`TextureView`]. -/// -/// For use with [`Texture::create_view()`]. -/// -/// Corresponds to [WebGPU `GPUTextureViewDescriptor`]( -/// https://gpuweb.github.io/gpuweb/#dictdef-gputextureviewdescriptor). -/// -/// [`TextureView`]: ../wgpu/struct.TextureView.html -/// [`Texture::create_view()`]: ../wgpu/struct.Texture.html#method.create_view -#[derive(Clone, Debug, Default, Eq, PartialEq)] -pub struct TextureViewDescriptor<L> { - /// Debug label of the texture view. This will show up in graphics debuggers for easy identification. - pub label: L, - /// Format of the texture view. Either must be the same as the texture format or in the list - /// of `view_formats` in the texture's descriptor. - pub format: Option<TextureFormat>, - /// The dimension of the texture view. For 1D textures, this must be `D1`. For 2D textures it must be one of - /// `D2`, `D2Array`, `Cube`, and `CubeArray`. For 3D textures it must be `D3` - pub dimension: Option<TextureViewDimension>, - /// The allowed usage(s) for the texture view. Must be a subset of the usage flags of the texture. - /// If not provided, defaults to the full set of usage flags of the texture. - pub usage: Option<TextureUsages>, - /// Aspect of the texture. Color textures must be [`TextureAspect::All`]. - pub aspect: TextureAspect, - /// Base mip level. - pub base_mip_level: u32, - /// Mip level count. - /// If `Some(count)`, `base_mip_level + count` must be less or equal to underlying texture mip count. - /// If `None`, considered to include the rest of the mipmap levels, but at least 1 in total. - pub mip_level_count: Option<u32>, - /// Base array layer. - pub base_array_layer: u32, - /// Layer count. - /// If `Some(count)`, `base_array_layer + count` must be less or equal to the underlying array count. - /// If `None`, considered to include the rest of the array layers, but at least 1 in total. - pub array_layer_count: Option<u32>, -} - -/// Describes a [`Texture`](../wgpu/struct.Texture.html). -/// -/// Corresponds to [WebGPU `GPUTextureDescriptor`]( -/// https://gpuweb.github.io/gpuweb/#dictdef-gputexturedescriptor). -#[repr(C)] -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct TextureDescriptor<L, V> { - /// Debug label of the texture. This will show up in graphics debuggers for easy identification. - pub label: L, - /// Size of the texture. All components must be greater than zero. For a - /// regular 1D/2D texture, the unused sizes will be 1. For 2DArray textures, - /// Z is the number of 2D textures in that array. - pub size: Extent3d, - /// Mip count of texture. For a texture with no extra mips, this must be 1. - pub mip_level_count: u32, - /// Sample count of texture. If this is not 1, texture must have [`BindingType::Texture::multisampled`] set to true. - pub sample_count: u32, - /// Dimensions of the texture. - pub dimension: TextureDimension, - /// Format of the texture. - pub format: TextureFormat, - /// Allowed usages of the texture. If used in other ways, the operation will panic. - pub usage: TextureUsages, - /// Specifies what view formats will be allowed when calling `Texture::create_view` on this texture. - /// - /// View formats of the same format as the texture are always allowed. - /// - /// Note: currently, only the srgb-ness is allowed to change. (ex: `Rgba8Unorm` texture + `Rgba8UnormSrgb` view) - pub view_formats: V, -} - -impl<L, V> TextureDescriptor<L, V> { - /// Takes a closure and maps the label of the texture descriptor into another. - #[must_use] - pub fn map_label<K>(&self, fun: impl FnOnce(&L) -> K) -> TextureDescriptor<K, V> - where - V: Clone, - { - TextureDescriptor { - label: fun(&self.label), - size: self.size, - mip_level_count: self.mip_level_count, - sample_count: self.sample_count, - dimension: self.dimension, - format: self.format, - usage: self.usage, - view_formats: self.view_formats.clone(), - } - } - - /// Maps the label and view formats of the texture descriptor into another. - #[must_use] - pub fn map_label_and_view_formats<K, M>( - &self, - l_fun: impl FnOnce(&L) -> K, - v_fun: impl FnOnce(V) -> M, - ) -> TextureDescriptor<K, M> - where - V: Clone, - { - TextureDescriptor { - label: l_fun(&self.label), - size: self.size, - mip_level_count: self.mip_level_count, - sample_count: self.sample_count, - dimension: self.dimension, - format: self.format, - usage: self.usage, - view_formats: v_fun(self.view_formats.clone()), - } - } - - /// Calculates the extent at a given mip level. - /// - /// If the given mip level is larger than possible, returns None. - /// - /// Treats the depth as part of the mipmaps. If calculating - /// for a 2DArray texture, which does not mipmap depth, set depth to 1. - /// - /// ```rust - /// # use wgpu_types as wgpu; - /// # type TextureDescriptor<'a> = wgpu::TextureDescriptor<(), &'a [wgpu::TextureFormat]>; - /// let desc = TextureDescriptor { - /// label: (), - /// size: wgpu::Extent3d { width: 100, height: 60, depth_or_array_layers: 1 }, - /// mip_level_count: 7, - /// sample_count: 1, - /// dimension: wgpu::TextureDimension::D3, - /// format: wgpu::TextureFormat::Rgba8Sint, - /// usage: wgpu::TextureUsages::empty(), - /// view_formats: &[], - /// }; - /// - /// assert_eq!(desc.mip_level_size(0), Some(wgpu::Extent3d { width: 100, height: 60, depth_or_array_layers: 1 })); - /// assert_eq!(desc.mip_level_size(1), Some(wgpu::Extent3d { width: 50, height: 30, depth_or_array_layers: 1 })); - /// assert_eq!(desc.mip_level_size(2), Some(wgpu::Extent3d { width: 25, height: 15, depth_or_array_layers: 1 })); - /// assert_eq!(desc.mip_level_size(3), Some(wgpu::Extent3d { width: 12, height: 7, depth_or_array_layers: 1 })); - /// assert_eq!(desc.mip_level_size(4), Some(wgpu::Extent3d { width: 6, height: 3, depth_or_array_layers: 1 })); - /// assert_eq!(desc.mip_level_size(5), Some(wgpu::Extent3d { width: 3, height: 1, depth_or_array_layers: 1 })); - /// assert_eq!(desc.mip_level_size(6), Some(wgpu::Extent3d { width: 1, height: 1, depth_or_array_layers: 1 })); - /// assert_eq!(desc.mip_level_size(7), None); - /// ``` - #[must_use] - pub fn mip_level_size(&self, level: u32) -> Option<Extent3d> { - if level >= self.mip_level_count { - return None; - } - - Some(self.size.mip_level_size(level, self.dimension)) - } - - /// Computes the render extent of this texture. - /// - /// This is a low-level helper exported for use by wgpu-core. - /// - /// <https://gpuweb.github.io/gpuweb/#abstract-opdef-compute-render-extent> - /// - /// # Panics - /// - /// If the mip level is out of range. - #[doc(hidden)] - #[must_use] - pub fn compute_render_extent(&self, mip_level: u32, plane: Option<u32>) -> Extent3d { - let Extent3d { - width, - height, - depth_or_array_layers: _, - } = self.mip_level_size(mip_level).expect("invalid mip level"); - - let (w_subsampling, h_subsampling) = self.format.subsampling_factors(plane); - - let width = width / w_subsampling; - let height = height / h_subsampling; - - Extent3d { - width, - height, - depth_or_array_layers: 1, - } - } - - /// Returns the number of array layers. - /// - /// <https://gpuweb.github.io/gpuweb/#abstract-opdef-array-layer-count> - #[must_use] - pub fn array_layer_count(&self) -> u32 { - match self.dimension { - TextureDimension::D1 | TextureDimension::D3 => 1, - TextureDimension::D2 => self.size.depth_or_array_layers, - } - } -} - -/// Format of an `ExternalTexture`. This indicates the number of underlying -/// planes used by the `ExternalTexture` as well as each plane's format. -#[repr(C)] -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum ExternalTextureFormat { - /// Single [`TextureFormat::Rgba8Unorm`] or [`TextureFormat::Bgra8Unorm`] format plane. - Rgba, - /// [`TextureFormat::R8Unorm`] Y plane, and [`TextureFormat::Rg8Unorm`] - /// interleaved CbCr plane. - Nv12, - /// Separate [`TextureFormat::R8Unorm`] Y, Cb, and Cr planes. - Yu12, -} - -/// Parameters describing a gamma encoding transfer function in the form -/// tf = { k * linear | linear < b -/// { a * pow(linear, 1/g) - (a-1) | linear >= b -#[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq, bytemuck::Zeroable, bytemuck::Pod)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[allow(missing_docs)] -pub struct ExternalTextureTransferFunction { - pub a: f32, - pub b: f32, - pub g: f32, - pub k: f32, -} - -impl Default for ExternalTextureTransferFunction { - fn default() -> Self { - Self { - a: 1.0, - b: 1.0, - g: 1.0, - k: 1.0, - } - } -} - -/// Describes an [`ExternalTexture`](../wgpu/struct.ExternalTexture.html). -/// -/// Note that [`width`] and [`height`] are the values that should be returned by -/// size queries in shader code; they do not necessarily match the dimensions of -/// the underlying plane texture(s). As a special case, if `(width, height)` is -/// `(0, 0)`, the actual size of the first underlying plane should be used instead. -/// -/// The size given by [`width`] and [`height`] must be consistent with -/// [`sample_transform`]: they should be the size in texels of the rectangle -/// covered by the square (0,0)..(1,1) after [`sample_transform`] has been applied -/// to it. -/// -/// [`width`]: Self::width -/// [`height`]: Self::height -/// [`sample_transform`]: Self::sample_transform -/// -/// Corresponds to [WebGPU `GPUExternalTextureDescriptor`]( -/// https://gpuweb.github.io/gpuweb/#dictdef-gpuexternaltexturedescriptor). -#[repr(C)] -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct ExternalTextureDescriptor<L> { - /// Debug label of the external texture. This will show up in graphics - /// debuggers for easy identification. - pub label: L, - - /// Width of the external texture. - pub width: u32, - - /// Height of the external texture. - pub height: u32, - - /// Format of the external texture. - pub format: ExternalTextureFormat, - - /// 4x4 column-major matrix with which to convert sampled YCbCr values - /// to RGBA. - /// This is ignored when `format` is [`ExternalTextureFormat::Rgba`]. - pub yuv_conversion_matrix: [f32; 16], - - /// 3x3 column-major matrix to transform linear RGB values in the source - /// color space to linear RGB values in the destination color space. In - /// combination with [`Self::src_transfer_function`] and - /// [`Self::dst_transfer_function`] this can be used to ensure that - /// [`ImageSample`] and [`ImageLoad`] operations return values in the - /// desired destination color space rather than the source color space of - /// the underlying planes. - /// - /// [`ImageSample`]: https://docs.rs/naga/latest/naga/ir/enum.Expression.html#variant.ImageSample - /// [`ImageLoad`]: https://docs.rs/naga/latest/naga/ir/enum.Expression.html#variant.ImageLoad - pub gamut_conversion_matrix: [f32; 9], - - /// Transfer function for the source color space. The *inverse* of this - /// will be applied to decode non-linear RGB to linear RGB in the source - /// color space. - pub src_transfer_function: ExternalTextureTransferFunction, - - /// Transfer function for the destination color space. This will be applied - /// to encode linear RGB to non-linear RGB in the destination color space. - pub dst_transfer_function: ExternalTextureTransferFunction, - - /// Transform to apply to [`ImageSample`] coordinates. - /// - /// This is a 3x2 column-major matrix representing an affine transform from - /// normalized texture coordinates to the normalized coordinates that should - /// be sampled from the external texture's underlying plane(s). - /// - /// This transform may scale, translate, flip, and rotate in 90-degree - /// increments, but the result of transforming the rectangle (0,0)..(1,1) - /// must be an axis-aligned rectangle that falls within the bounds of - /// (0,0)..(1,1). - /// - /// [`ImageSample`]: https://docs.rs/naga/latest/naga/ir/enum.Expression.html#variant.ImageSample - pub sample_transform: [f32; 6], - - /// Transform to apply to [`ImageLoad`] coordinates. - /// - /// This is a 3x2 column-major matrix representing an affine transform from - /// non-normalized texel coordinates to the non-normalized coordinates of - /// the texel that should be loaded from the external texture's underlying - /// plane 0. For planes 1 and 2, if present, plane 0's coordinates are - /// scaled according to the textures' relative sizes. - /// - /// This transform may scale, translate, flip, and rotate in 90-degree - /// increments, but the result of transforming the rectangle (0,0)..([`width`], - /// [`height`]) must be an axis-aligned rectangle that falls within the bounds - /// of (0,0)..([`width`], [`height`]). - /// - /// [`ImageLoad`]: https://docs.rs/naga/latest/naga/ir/enum.Expression.html#variant.ImageLoad - /// [`width`]: Self::width - /// [`height`]: Self::height - pub load_transform: [f32; 6], -} - -impl<L> ExternalTextureDescriptor<L> { - /// Takes a closure and maps the label of the external texture descriptor into another. - #[must_use] - pub fn map_label<K>(&self, fun: impl FnOnce(&L) -> K) -> ExternalTextureDescriptor<K> { - ExternalTextureDescriptor { - label: fun(&self.label), - width: self.width, - height: self.height, - format: self.format, - yuv_conversion_matrix: self.yuv_conversion_matrix, - sample_transform: self.sample_transform, - load_transform: self.load_transform, - gamut_conversion_matrix: self.gamut_conversion_matrix, - src_transfer_function: self.src_transfer_function, - dst_transfer_function: self.dst_transfer_function, - } - } - - /// The number of underlying planes used by the external texture. - pub fn num_planes(&self) -> usize { - match self.format { - ExternalTextureFormat::Rgba => 1, - ExternalTextureFormat::Nv12 => 2, - ExternalTextureFormat::Yu12 => 3, - } - } -} - -/// Describes a `Sampler`. -/// -/// For use with `Device::create_sampler`. -/// -/// Corresponds to [WebGPU `GPUSamplerDescriptor`]( -/// https://gpuweb.github.io/gpuweb/#dictdef-gpusamplerdescriptor). -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct SamplerDescriptor<L> { - /// Debug label of the sampler. This will show up in graphics debuggers for easy identification. - pub label: L, - /// How to deal with out of bounds accesses in the u (i.e. x) direction - pub address_mode_u: AddressMode, - /// How to deal with out of bounds accesses in the v (i.e. y) direction - pub address_mode_v: AddressMode, - /// How to deal with out of bounds accesses in the w (i.e. z) direction - pub address_mode_w: AddressMode, - /// How to filter the texture when it needs to be magnified (made larger) - pub mag_filter: FilterMode, - /// How to filter the texture when it needs to be minified (made smaller) - pub min_filter: FilterMode, - /// How to filter between mip map levels - pub mipmap_filter: MipmapFilterMode, - /// Minimum level of detail (i.e. mip level) to use - pub lod_min_clamp: f32, - /// Maximum level of detail (i.e. mip level) to use - pub lod_max_clamp: f32, - /// If this is enabled, this is a comparison sampler using the given comparison function. - pub compare: Option<CompareFunction>, - /// Must be at least 1. If this is not 1, all filter modes must be linear. - pub anisotropy_clamp: u16, - /// Border color to use when `address_mode` is [`AddressMode::ClampToBorder`] - pub border_color: Option<SamplerBorderColor>, -} - -impl<L: Default> Default for SamplerDescriptor<L> { - fn default() -> Self { - Self { - label: Default::default(), - address_mode_u: Default::default(), - address_mode_v: Default::default(), - address_mode_w: Default::default(), - mag_filter: Default::default(), - min_filter: Default::default(), - mipmap_filter: Default::default(), - lod_min_clamp: 0.0, - lod_max_clamp: 32.0, - compare: None, - anisotropy_clamp: 1, - border_color: None, - } - } -} - -/// Selects a subset of the data a [`Texture`] holds. -/// -/// Used in [texture views](TextureViewDescriptor) and -/// [texture copy operations](TexelCopyTextureInfo). -/// -/// Corresponds to [WebGPU `GPUTextureAspect`]( -/// https://gpuweb.github.io/gpuweb/#enumdef-gputextureaspect). -/// -/// [`Texture`]: ../wgpu/struct.Texture.html -#[repr(C)] -#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] -pub enum TextureAspect { - /// Depth, Stencil, and Color. - #[default] - All, - /// Stencil. - StencilOnly, - /// Depth. - DepthOnly, - /// Plane 0. - Plane0, - /// Plane 1. - Plane1, - /// Plane 2. - Plane2, -} - -/// How edges should be handled in texture addressing. -/// -/// Corresponds to [WebGPU `GPUAddressMode`]( -/// https://gpuweb.github.io/gpuweb/#enumdef-gpuaddressmode). -#[repr(C)] -#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] -pub enum AddressMode { - /// Clamp the value to the edge of the texture - /// - /// -0.25 -> 0.0 - /// 1.25 -> 1.0 - #[default] - ClampToEdge = 0, - /// Repeat the texture in a tiling fashion - /// - /// -0.25 -> 0.75 - /// 1.25 -> 0.25 - Repeat = 1, - /// Repeat the texture, mirroring it every repeat - /// - /// -0.25 -> 0.25 - /// 1.25 -> 0.75 - MirrorRepeat = 2, - /// Clamp the value to the border of the texture - /// Requires feature [`Features::ADDRESS_MODE_CLAMP_TO_BORDER`] - /// - /// -0.25 -> border - /// 1.25 -> border - ClampToBorder = 3, -} - -/// Texel mixing mode when sampling between texels. -/// -/// Corresponds to [WebGPU `GPUFilterMode`]( -/// https://gpuweb.github.io/gpuweb/#enumdef-gpufiltermode). -#[repr(C)] -#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] -pub enum FilterMode { - /// Nearest neighbor sampling. - /// - /// This creates a pixelated effect. - #[default] - Nearest = 0, - /// Linear Interpolation - /// - /// This makes textures smooth but blurry. - Linear = 1, -} - -/// Texel mixing mode when sampling between texels. -/// -/// Corresponds to [WebGPU `GPUMipmapFilterMode`]( -/// https://gpuweb.github.io/gpuweb/#enumdef-gpumipmapfiltermode). -#[repr(C)] -#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] -pub enum MipmapFilterMode { - /// Nearest neighbor sampling. - /// - /// Return the value of the texel nearest to the texture coordinates. - #[default] - Nearest = 0, - /// Linear Interpolation - /// - /// Select two texels in each dimension and return a linear interpolation between their values. - Linear = 1, -} - -/// A range of push constant memory to pass to a shader stage. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct PushConstantRange { - /// Stage push constant range is visible from. Each stage can only be served by at most one range. - /// One range can serve multiple stages however. - pub stages: ShaderStages, - /// Range in push constant memory to use for the stage. Must be less than [`Limits::max_push_constant_size`]. - /// Start and end must be aligned to the 4s. - pub range: Range<u32>, -} - -/// Describes a [`CommandBuffer`](../wgpu/struct.CommandBuffer.html). -/// -/// Corresponds to [WebGPU `GPUCommandBufferDescriptor`]( -/// https://gpuweb.github.io/gpuweb/#dictdef-gpucommandbufferdescriptor). -#[repr(C)] -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct CommandBufferDescriptor<L> { - /// Debug label of this command buffer. - pub label: L, -} - -impl<L> CommandBufferDescriptor<L> { - /// Takes a closure and maps the label of the command buffer descriptor into another. - #[must_use] - pub fn map_label<K>(&self, fun: impl FnOnce(&L) -> K) -> CommandBufferDescriptor<K> { - CommandBufferDescriptor { - label: fun(&self.label), - } - } -} - -/// Describes the depth/stencil attachment for render bundles. -/// -/// Corresponds to a portion of [WebGPU `GPURenderBundleEncoderDescriptor`]( -/// https://gpuweb.github.io/gpuweb/#dictdef-gpurenderbundleencoderdescriptor). -#[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct RenderBundleDepthStencil { - /// Format of the attachment. - pub format: TextureFormat, - /// If the depth aspect of the depth stencil attachment is going to be written to. - /// - /// This must match the [`RenderPassDepthStencilAttachment::depth_ops`] of the renderpass this render bundle is executed in. - /// If `depth_ops` is `Some(..)` this must be false. If it is `None` this must be true. - /// - /// [`RenderPassDepthStencilAttachment::depth_ops`]: ../wgpu/struct.RenderPassDepthStencilAttachment.html#structfield.depth_ops - pub depth_read_only: bool, - - /// If the stencil aspect of the depth stencil attachment is going to be written to. - /// - /// This must match the [`RenderPassDepthStencilAttachment::stencil_ops`] of the renderpass this render bundle is executed in. - /// If `depth_ops` is `Some(..)` this must be false. If it is `None` this must be true. - /// - /// [`RenderPassDepthStencilAttachment::stencil_ops`]: ../wgpu/struct.RenderPassDepthStencilAttachment.html#structfield.stencil_ops - pub stencil_read_only: bool, -} - -/// Describes a [`RenderBundle`](../wgpu/struct.RenderBundle.html). -/// -/// Corresponds to [WebGPU `GPURenderBundleDescriptor`]( -/// https://gpuweb.github.io/gpuweb/#dictdef-gpurenderbundledescriptor). -#[repr(C)] -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct RenderBundleDescriptor<L> { - /// Debug label of the render bundle encoder. This will show up in graphics debuggers for easy identification. - pub label: L, -} - -impl<L> RenderBundleDescriptor<L> { - /// Takes a closure and maps the label of the render bundle descriptor into another. - #[must_use] - pub fn map_label<K>(&self, fun: impl FnOnce(&L) -> K) -> RenderBundleDescriptor<K> { - RenderBundleDescriptor { - label: fun(&self.label), - } - } -} - -impl<T> Default for RenderBundleDescriptor<Option<T>> { - fn default() -> Self { - Self { label: None } - } -} - -/// Layout of a texture in a buffer's memory. -/// -/// The bytes per row and rows per image can be hard to figure out so here are some examples: -/// -/// | Resolution | Format | Bytes per block | Pixels per block | Bytes per row | Rows per image | -/// |------------|--------|-----------------|------------------|----------------------------------------|------------------------------| -/// | 256x256 | RGBA8 | 4 | 1 * 1 * 1 | 256 * 4 = Some(1024) | None | -/// | 32x16x8 | RGBA8 | 4 | 1 * 1 * 1 | 32 * 4 = 128 padded to 256 = Some(256) | None | -/// | 256x256 | BC3 | 16 | 4 * 4 * 1 | 16 * (256 / 4) = 1024 = Some(1024) | None | -/// | 64x64x8 | BC3 | 16 | 4 * 4 * 1 | 16 * (64 / 4) = 256 = Some(256) | 64 / 4 = 16 = Some(16) | -/// -/// Corresponds to [WebGPU `GPUTexelCopyBufferLayout`]( -/// https://gpuweb.github.io/gpuweb/#dictdef-gpuimagedatalayout). -#[repr(C)] -#[derive(Clone, Copy, Debug, Default)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct TexelCopyBufferLayout { - /// Offset into the buffer that is the start of the texture. Must be a multiple of texture block size. - /// For non-compressed textures, this is 1. - pub offset: BufferAddress, - /// Bytes per "row" in an image. - /// - /// A row is one row of pixels or of compressed blocks in the x direction. - /// - /// This value is required if there are multiple rows (i.e. height or depth is more than one pixel or pixel block for compressed textures) - /// - /// Must be a multiple of 256 for [`CommandEncoder::copy_buffer_to_texture`][CEcbtt] - /// and [`CommandEncoder::copy_texture_to_buffer`][CEcttb]. You must manually pad the - /// image such that this is a multiple of 256. It will not affect the image data. - /// - /// [`Queue::write_texture`][Qwt] does not have this requirement. - /// - /// Must be a multiple of the texture block size. For non-compressed textures, this is 1. - /// - /// [CEcbtt]: ../wgpu/struct.CommandEncoder.html#method.copy_buffer_to_texture - /// [CEcttb]: ../wgpu/struct.CommandEncoder.html#method.copy_texture_to_buffer - /// [Qwt]: ../wgpu/struct.Queue.html#method.write_texture - pub bytes_per_row: Option<u32>, - /// "Rows" that make up a single "image". - /// - /// A row is one row of pixels or of compressed blocks in the x direction. - /// - /// An image is one layer in the z direction of a 3D image or 2DArray texture. - /// - /// The amount of rows per image may be larger than the actual amount of rows of data. - /// - /// Required if there are multiple images (i.e. the depth is more than one). - pub rows_per_image: Option<u32>, -} - -/// Specific type of a buffer binding. -/// -/// Corresponds to [WebGPU `GPUBufferBindingType`]( -/// https://gpuweb.github.io/gpuweb/#enumdef-gpubufferbindingtype). -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum BufferBindingType { - /// A buffer for uniform values. - /// - /// Example WGSL syntax: - /// ```rust,ignore - /// struct Globals { - /// a_uniform: vec2<f32>, - /// another_uniform: vec2<f32>, - /// } - /// @group(0) @binding(0) - /// var<uniform> globals: Globals; - /// ``` - /// - /// Example GLSL syntax: - /// ```cpp,ignore - /// layout(std140, binding = 0) - /// uniform Globals { - /// vec2 aUniform; - /// vec2 anotherUniform; - /// }; - /// ``` - #[default] - Uniform, - /// A storage buffer. - /// - /// Example WGSL syntax: - /// ```rust,ignore - /// @group(0) @binding(0) - /// var<storage, read_write> my_element: array<vec4<f32>>; - /// ``` - /// - /// Example GLSL syntax: - /// ```cpp,ignore - /// layout (set=0, binding=0) buffer myStorageBuffer { - /// vec4 myElement[]; - /// }; - /// ``` - Storage { - /// If `true`, the buffer can only be read in the shader, - /// and it: - /// - may or may not be annotated with `read` (WGSL). - /// - must be annotated with `readonly` (GLSL). - /// - /// Example WGSL syntax: - /// ```rust,ignore - /// @group(0) @binding(0) - /// var<storage, read> my_element: array<vec4<f32>>; - /// ``` - /// - /// Example GLSL syntax: - /// ```cpp,ignore - /// layout (set=0, binding=0) readonly buffer myStorageBuffer { - /// vec4 myElement[]; - /// }; - /// ``` - read_only: bool, - }, -} - -/// Specific type of a sample in a texture binding. -/// -/// Corresponds to [WebGPU `GPUTextureSampleType`]( -/// https://gpuweb.github.io/gpuweb/#enumdef-gputexturesampletype). -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum TextureSampleType { - /// Sampling returns floats. - /// - /// Example WGSL syntax: - /// ```rust,ignore - /// @group(0) @binding(0) - /// var t: texture_2d<f32>; - /// ``` - /// - /// Example GLSL syntax: - /// ```cpp,ignore - /// layout(binding = 0) - /// uniform texture2D t; - /// ``` - Float { - /// If this is `false`, the texture can't be sampled with - /// a filtering sampler. - /// - /// Even if this is `true`, it's possible to sample with - /// a **non-filtering** sampler. - filterable: bool, - }, - /// Sampling does the depth reference comparison. - /// - /// This is also compatible with a non-filtering sampler. - /// - /// Example WGSL syntax: - /// ```rust,ignore - /// @group(0) @binding(0) - /// var t: texture_depth_2d; - /// ``` - /// - /// Example GLSL syntax: - /// ```cpp,ignore - /// layout(binding = 0) - /// uniform texture2DShadow t; - /// ``` - Depth, - /// Sampling returns signed integers. - /// - /// Example WGSL syntax: - /// ```rust,ignore - /// @group(0) @binding(0) - /// var t: texture_2d<i32>; - /// ``` - /// - /// Example GLSL syntax: - /// ```cpp,ignore - /// layout(binding = 0) - /// uniform itexture2D t; - /// ``` - Sint, - /// Sampling returns unsigned integers. - /// - /// Example WGSL syntax: - /// ```rust,ignore - /// @group(0) @binding(0) - /// var t: texture_2d<u32>; - /// ``` - /// - /// Example GLSL syntax: - /// ```cpp,ignore - /// layout(binding = 0) - /// uniform utexture2D t; - /// ``` - Uint, -} - -impl Default for TextureSampleType { - fn default() -> Self { - Self::Float { filterable: true } - } -} - -/// Specific type of a sample in a texture binding. -/// -/// For use in [`BindingType::StorageTexture`]. -/// -/// Corresponds to [WebGPU `GPUStorageTextureAccess`]( -/// https://gpuweb.github.io/gpuweb/#enumdef-gpustoragetextureaccess). -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] -pub enum StorageTextureAccess { - /// The texture can only be written in the shader and it: - /// - may or may not be annotated with `write` (WGSL). - /// - must be annotated with `writeonly` (GLSL). - /// - /// Example WGSL syntax: - /// ```rust,ignore - /// @group(0) @binding(0) - /// var my_storage_image: texture_storage_2d<r32float, write>; - /// ``` - /// - /// Example GLSL syntax: - /// ```cpp,ignore - /// layout(set=0, binding=0, r32f) writeonly uniform image2D myStorageImage; - /// ``` - WriteOnly, - /// The texture can only be read in the shader and it must be annotated with `read` (WGSL) or - /// `readonly` (GLSL). - /// - /// [`Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES`] must be enabled to use this access - /// mode. This is a native-only extension. - /// - /// Example WGSL syntax: - /// ```rust,ignore - /// @group(0) @binding(0) - /// var my_storage_image: texture_storage_2d<r32float, read>; - /// ``` - /// - /// Example GLSL syntax: - /// ```cpp,ignore - /// layout(set=0, binding=0, r32f) readonly uniform image2D myStorageImage; - /// ``` - ReadOnly, - /// The texture can be both read and written in the shader and must be annotated with - /// `read_write` in WGSL. - /// - /// [`Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES`] must be enabled to use this access - /// mode. This is a nonstandard, native-only extension. - /// - /// Example WGSL syntax: - /// ```rust,ignore - /// @group(0) @binding(0) - /// var my_storage_image: texture_storage_2d<r32float, read_write>; - /// ``` - /// - /// Example GLSL syntax: - /// ```cpp,ignore - /// layout(set=0, binding=0, r32f) uniform image2D myStorageImage; - /// ``` - ReadWrite, - /// The texture can be both read and written in the shader via atomics and must be annotated - /// with `read_write` in WGSL. - /// - /// [`Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES`] must be enabled to use this access - /// mode. This is a nonstandard, native-only extension. - /// - /// Example WGSL syntax: - /// ```rust,ignore - /// @group(0) @binding(0) - /// var my_storage_image: texture_storage_2d<r32uint, atomic>; - /// ``` - Atomic, -} - -/// Specific type of a sampler binding. -/// -/// For use in [`BindingType::Sampler`]. -/// -/// Corresponds to [WebGPU `GPUSamplerBindingType`]( -/// https://gpuweb.github.io/gpuweb/#enumdef-gpusamplerbindingtype). -#[repr(C)] -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] -pub enum SamplerBindingType { - /// The sampling result is produced based on more than a single color sample from a texture, - /// e.g. when bilinear interpolation is enabled. - Filtering, - /// The sampling result is produced based on a single color sample from a texture. - NonFiltering, - /// Use as a comparison sampler instead of a normal sampler. - /// For more info take a look at the analogous functionality in OpenGL: <https://www.khronos.org/opengl/wiki/Sampler_Object#Comparison_mode>. - Comparison, -} - -/// Type of a binding in a [bind group layout][`BindGroupLayoutEntry`]. -/// -/// For each binding in a layout, a [`BindGroup`] must provide a [`BindingResource`] of the -/// corresponding type. -/// -/// Corresponds to WebGPU's mutually exclusive fields within [`GPUBindGroupLayoutEntry`]( -/// https://gpuweb.github.io/gpuweb/#dictdef-gpubindgrouplayoutentry). -/// -/// [`BindingResource`]: ../wgpu/enum.BindingResource.html -/// [`BindGroup`]: ../wgpu/struct.BindGroup.html -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum BindingType { - /// A buffer binding. - /// - /// Corresponds to [WebGPU `GPUBufferBindingLayout`]( - /// https://gpuweb.github.io/gpuweb/#dictdef-gpubufferbindinglayout). - Buffer { - /// Sub-type of the buffer binding. - ty: BufferBindingType, - - /// Indicates that the binding has a dynamic offset. - /// - /// One offset must be passed to [`RenderPass::set_bind_group`][RPsbg] - /// for each dynamic binding in increasing order of binding number. - /// - /// [RPsbg]: ../wgpu/struct.RenderPass.html#method.set_bind_group - #[cfg_attr(feature = "serde", serde(default))] - has_dynamic_offset: bool, - - /// The minimum size for a [`BufferBinding`] matching this entry, in bytes. - /// - /// If this is `Some(size)`: - /// - /// - When calling [`create_bind_group`], the resource at this bind point - /// must be a [`BindingResource::Buffer`] whose effective size is at - /// least `size`. - /// - /// - When calling [`create_render_pipeline`] or [`create_compute_pipeline`], - /// `size` must be at least the [minimum buffer binding size] for the - /// shader module global at this bind point: large enough to hold the - /// global's value, along with one element of a trailing runtime-sized - /// array, if present. - /// - /// If this is `None`: - /// - /// - Each draw or dispatch command checks that the buffer range at this - /// bind point satisfies the [minimum buffer binding size]. - /// - /// [`BufferBinding`]: ../wgpu/struct.BufferBinding.html - /// [`create_bind_group`]: ../wgpu/struct.Device.html#method.create_bind_group - /// [`BindingResource::Buffer`]: ../wgpu/enum.BindingResource.html#variant.Buffer - /// [minimum buffer binding size]: https://www.w3.org/TR/webgpu/#minimum-buffer-binding-size - /// [`create_render_pipeline`]: ../wgpu/struct.Device.html#method.create_render_pipeline - /// [`create_compute_pipeline`]: ../wgpu/struct.Device.html#method.create_compute_pipeline - #[cfg_attr(feature = "serde", serde(default))] - min_binding_size: Option<BufferSize>, - }, - /// A sampler that can be used to sample a texture. - /// - /// Example WGSL syntax: - /// ```rust,ignore - /// @group(0) @binding(0) - /// var s: sampler; - /// ``` - /// - /// Example GLSL syntax: - /// ```cpp,ignore - /// layout(binding = 0) - /// uniform sampler s; - /// ``` - /// - /// Corresponds to [WebGPU `GPUSamplerBindingLayout`]( - /// https://gpuweb.github.io/gpuweb/#dictdef-gpusamplerbindinglayout). - Sampler(SamplerBindingType), - /// A texture binding. - /// - /// Example WGSL syntax: - /// ```rust,ignore - /// @group(0) @binding(0) - /// var t: texture_2d<f32>; - /// ``` - /// - /// Example GLSL syntax: - /// ```cpp,ignore - /// layout(binding = 0) - /// uniform texture2D t; - /// ``` - /// - /// Corresponds to [WebGPU `GPUTextureBindingLayout`]( - /// https://gpuweb.github.io/gpuweb/#dictdef-gputexturebindinglayout). - Texture { - /// Sample type of the texture binding. - sample_type: TextureSampleType, - /// Dimension of the texture view that is going to be sampled. - view_dimension: TextureViewDimension, - /// True if the texture has a sample count greater than 1. If this is true, - /// the texture must be declared as `texture_multisampled_2d` or - /// `texture_depth_multisampled_2d` in the shader, and read using `textureLoad`. - multisampled: bool, - }, - /// A storage texture. - /// - /// Example WGSL syntax: - /// ```rust,ignore - /// @group(0) @binding(0) - /// var my_storage_image: texture_storage_2d<r32float, write>; - /// ``` - /// - /// Example GLSL syntax: - /// ```cpp,ignore - /// layout(set=0, binding=0, r32f) writeonly uniform image2D myStorageImage; - /// ``` - /// Note that the texture format must be specified in the shader, along with the - /// access mode. For WGSL, the format must be one of the enumerants in the list - /// of [storage texel formats](https://gpuweb.github.io/gpuweb/wgsl/#storage-texel-formats). - /// - /// Corresponds to [WebGPU `GPUStorageTextureBindingLayout`]( - /// https://gpuweb.github.io/gpuweb/#dictdef-gpustoragetexturebindinglayout). - StorageTexture { - /// Allowed access to this texture. - access: StorageTextureAccess, - /// Format of the texture. - format: TextureFormat, - /// Dimension of the texture view that is going to be sampled. - view_dimension: TextureViewDimension, - }, - - /// A ray-tracing acceleration structure binding. - /// - /// Example WGSL syntax: - /// ```rust,ignore - /// @group(0) @binding(0) - /// var as: acceleration_structure; - /// ``` - /// - /// or with vertex return enabled - /// ```rust,ignore - /// @group(0) @binding(0) - /// var as: acceleration_structure<vertex_return>; - /// ``` - /// - /// Example GLSL syntax: - /// ```cpp,ignore - /// layout(binding = 0) - /// uniform accelerationStructureEXT as; - /// ``` - AccelerationStructure { - /// Whether this acceleration structure can be used to - /// create a ray query that has flag vertex return in the shader - /// - /// If enabled requires [`Features::EXPERIMENTAL_RAY_HIT_VERTEX_RETURN`] - vertex_return: bool, - }, - - /// An external texture binding. - /// - /// Example WGSL syntax: - /// ```rust,ignore - /// @group(0) @binding(0) - /// var t: texture_external; - /// ``` - /// - /// Corresponds to [WebGPU `GPUExternalTextureBindingLayout`]( - /// https://gpuweb.github.io/gpuweb/#dictdef-gpuexternaltexturebindinglayout). - /// - /// Requires [`Features::EXTERNAL_TEXTURE`] - ExternalTexture, -} - -impl BindingType { - /// Returns true for buffer bindings with dynamic offset enabled. - #[must_use] - pub fn has_dynamic_offset(&self) -> bool { - match *self { - Self::Buffer { - has_dynamic_offset, .. - } => has_dynamic_offset, - _ => false, - } - } -} - -/// Describes a single binding inside a bind group. -/// -/// Corresponds to [WebGPU `GPUBindGroupLayoutEntry`]( -/// https://gpuweb.github.io/gpuweb/#dictdef-gpubindgrouplayoutentry). -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct BindGroupLayoutEntry { - /// Binding index. Must match shader index and be unique inside a `BindGroupLayout`. A binding - /// of index 1, would be described as `@group(0) @binding(1)` in shaders. - pub binding: u32, - /// Which shader stages can see this binding. - pub visibility: ShaderStages, - /// The type of the binding - pub ty: BindingType, - /// If the binding is an array of multiple resources. Corresponds to `binding_array<T>` in the shader. - /// - /// When this is `Some` the following validation applies: - /// - Size must be of value 1 or greater. - /// - When `ty == BindingType::Texture`, [`Features::TEXTURE_BINDING_ARRAY`] must be supported. - /// - When `ty == BindingType::Sampler`, [`Features::TEXTURE_BINDING_ARRAY`] must be supported. - /// - When `ty == BindingType::Buffer`, [`Features::BUFFER_BINDING_ARRAY`] must be supported. - /// - When `ty == BindingType::Buffer` and `ty.ty == BufferBindingType::Storage`, [`Features::STORAGE_RESOURCE_BINDING_ARRAY`] must be supported. - /// - When `ty == BindingType::StorageTexture`, [`Features::STORAGE_RESOURCE_BINDING_ARRAY`] must be supported. - /// - When any binding in the group is an array, no `BindingType::Buffer` in the group may have `has_dynamic_offset == true` - /// - When any binding in the group is an array, no `BindingType::Buffer` in the group may have `ty.ty == BufferBindingType::Uniform`. - /// - #[cfg_attr(feature = "serde", serde(default))] - pub count: Option<NonZeroU32>, -} - -/// View of a buffer which can be used to copy to/from a texture. -/// -/// Corresponds to [WebGPU `GPUTexelCopyBufferInfo`]( -/// https://gpuweb.github.io/gpuweb/#dictdef-gpuimagecopybuffer). -#[repr(C)] -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct TexelCopyBufferInfo<B> { - /// The buffer to be copied to/from. - pub buffer: B, - /// The layout of the texture data in this buffer. - pub layout: TexelCopyBufferLayout, -} - -/// View of a texture which can be used to copy to/from a buffer/texture. -/// -/// Corresponds to [WebGPU `GPUTexelCopyTextureInfo`]( -/// https://gpuweb.github.io/gpuweb/#dictdef-gpuimagecopytexture). -#[repr(C)] -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct TexelCopyTextureInfo<T> { - /// The texture to be copied to/from. - pub texture: T, - /// The target mip level of the texture. - pub mip_level: u32, - /// The base texel of the texture in the selected `mip_level`. Together - /// with the `copy_size` argument to copy functions, defines the - /// sub-region of the texture to copy. - #[cfg_attr(feature = "serde", serde(default))] - pub origin: Origin3d, - /// The copy aspect. - #[cfg_attr(feature = "serde", serde(default))] - pub aspect: TextureAspect, -} - -impl<T> TexelCopyTextureInfo<T> { - /// Adds color space and premultiplied alpha information to make this - /// descriptor tagged. - pub fn to_tagged( - self, - color_space: PredefinedColorSpace, - premultiplied_alpha: bool, - ) -> CopyExternalImageDestInfo<T> { - CopyExternalImageDestInfo { - texture: self.texture, - mip_level: self.mip_level, - origin: self.origin, - aspect: self.aspect, - color_space, - premultiplied_alpha, - } - } -} - -/// View of an external texture that can be used to copy to a texture. -/// -/// Corresponds to [WebGPU `GPUCopyExternalImageSourceInfo`]( -/// https://gpuweb.github.io/gpuweb/#dictdef-gpuimagecopyexternalimage). -#[cfg(all(target_arch = "wasm32", feature = "web"))] -#[derive(Clone, Debug)] -pub struct CopyExternalImageSourceInfo { - /// The texture to be copied from. The copy source data is captured at the moment - /// the copy is issued. - pub source: ExternalImageSource, - /// The base texel used for copying from the external image. Together - /// with the `copy_size` argument to copy functions, defines the - /// sub-region of the image to copy. - /// - /// Relative to the top left of the image. - /// - /// Must be [`Origin2d::ZERO`] if [`DownlevelFlags::UNRESTRICTED_EXTERNAL_TEXTURE_COPIES`] is not supported. - pub origin: Origin2d, - /// If the Y coordinate of the image should be flipped. Even if this is - /// true, `origin` is still relative to the top left. - pub flip_y: bool, -} - -/// Source of an external texture copy. -/// -/// Corresponds to the [implicit union type on WebGPU `GPUCopyExternalImageSourceInfo.source`]( -/// https://gpuweb.github.io/gpuweb/#dom-gpuimagecopyexternalimage-source). -#[cfg(all(target_arch = "wasm32", feature = "web"))] -#[derive(Clone, Debug)] -pub enum ExternalImageSource { - /// Copy from a previously-decoded image bitmap. - ImageBitmap(web_sys::ImageBitmap), - /// Copy from an image element. - HTMLImageElement(web_sys::HtmlImageElement), - /// Copy from a current frame of a video element. - HTMLVideoElement(web_sys::HtmlVideoElement), - /// Copy from an image. - ImageData(web_sys::ImageData), - /// Copy from a on-screen canvas. - HTMLCanvasElement(web_sys::HtmlCanvasElement), - /// Copy from a off-screen canvas. - /// - /// Requires [`DownlevelFlags::UNRESTRICTED_EXTERNAL_TEXTURE_COPIES`] - OffscreenCanvas(web_sys::OffscreenCanvas), - /// Copy from a video frame. - #[cfg(web_sys_unstable_apis)] - VideoFrame(web_sys::VideoFrame), -} - -#[cfg(all(target_arch = "wasm32", feature = "web"))] -impl ExternalImageSource { - /// Gets the pixel, not css, width of the source. - pub fn width(&self) -> u32 { - match self { - ExternalImageSource::ImageBitmap(b) => b.width(), - ExternalImageSource::HTMLImageElement(i) => i.width(), - ExternalImageSource::HTMLVideoElement(v) => v.video_width(), - ExternalImageSource::ImageData(i) => i.width(), - ExternalImageSource::HTMLCanvasElement(c) => c.width(), - ExternalImageSource::OffscreenCanvas(c) => c.width(), - #[cfg(web_sys_unstable_apis)] - ExternalImageSource::VideoFrame(v) => v.display_width(), - } - } - - /// Gets the pixel, not css, height of the source. - pub fn height(&self) -> u32 { - match self { - ExternalImageSource::ImageBitmap(b) => b.height(), - ExternalImageSource::HTMLImageElement(i) => i.height(), - ExternalImageSource::HTMLVideoElement(v) => v.video_height(), - ExternalImageSource::ImageData(i) => i.height(), - ExternalImageSource::HTMLCanvasElement(c) => c.height(), - ExternalImageSource::OffscreenCanvas(c) => c.height(), - #[cfg(web_sys_unstable_apis)] - ExternalImageSource::VideoFrame(v) => v.display_height(), - } - } -} - -#[cfg(all(target_arch = "wasm32", feature = "web"))] -impl core::ops::Deref for ExternalImageSource { - type Target = js_sys::Object; - - fn deref(&self) -> &Self::Target { - match self { - Self::ImageBitmap(b) => b, - Self::HTMLImageElement(i) => i, - Self::HTMLVideoElement(v) => v, - Self::ImageData(i) => i, - Self::HTMLCanvasElement(c) => c, - Self::OffscreenCanvas(c) => c, - #[cfg(web_sys_unstable_apis)] - Self::VideoFrame(v) => v, - } - } -} - -#[cfg(all( - target_arch = "wasm32", - feature = "web", - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") -))] -unsafe impl Send for ExternalImageSource {} -#[cfg(all( - target_arch = "wasm32", - feature = "web", - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") -))] -unsafe impl Sync for ExternalImageSource {} - -/// Color spaces supported on the web. -/// -/// Corresponds to [HTML Canvas `PredefinedColorSpace`]( -/// https://html.spec.whatwg.org/multipage/canvas.html#predefinedcolorspace). -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] -pub enum PredefinedColorSpace { - /// sRGB color space - Srgb, - /// Display-P3 color space - DisplayP3, -} - -/// View of a texture which can be used to copy to a texture, including -/// color space and alpha premultiplication information. +/// RGBA double precision color. /// -/// Corresponds to [WebGPU `GPUCopyExternalImageDestInfo`]( -/// https://gpuweb.github.io/gpuweb/#dictdef-gpuimagecopytexturetagged). -#[derive(Copy, Clone, Debug)] +/// This is not to be used as a generic color type, only for specific wgpu interfaces. +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct CopyExternalImageDestInfo<T> { - /// The texture to be copied to/from. - pub texture: T, - /// The target mip level of the texture. - pub mip_level: u32, - /// The base texel of the texture in the selected `mip_level`. - pub origin: Origin3d, - /// The copy aspect. - pub aspect: TextureAspect, - /// The color space of this texture. - pub color_space: PredefinedColorSpace, - /// The premultiplication of this texture - pub premultiplied_alpha: bool, +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct Color { + /// Red component of the color + pub r: f64, + /// Green component of the color + pub g: f64, + /// Blue component of the color + pub b: f64, + /// Alpha component of the color + pub a: f64, } -impl<T> CopyExternalImageDestInfo<T> { - /// Removes the colorspace information from the type. - pub fn to_untagged(self) -> TexelCopyTextureInfo<T> { - TexelCopyTextureInfo { - texture: self.texture, - mip_level: self.mip_level, - origin: self.origin, - aspect: self.aspect, - } - } +#[allow(missing_docs)] +impl Color { + pub const TRANSPARENT: Self = Self { + r: 0.0, + g: 0.0, + b: 0.0, + a: 0.0, + }; + pub const BLACK: Self = Self { + r: 0.0, + g: 0.0, + b: 0.0, + a: 1.0, + }; + pub const WHITE: Self = Self { + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + }; + pub const RED: Self = Self { + r: 1.0, + g: 0.0, + b: 0.0, + a: 1.0, + }; + pub const GREEN: Self = Self { + r: 0.0, + g: 1.0, + b: 0.0, + a: 1.0, + }; + pub const BLUE: Self = Self { + r: 0.0, + g: 0.0, + b: 1.0, + a: 1.0, + }; } -/// Subresource range within an image +/// Describes a [`CommandBuffer`](../wgpu/struct.CommandBuffer.html). +/// +/// Corresponds to [WebGPU `GPUCommandBufferDescriptor`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gpucommandbufferdescriptor). #[repr(C)] -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct ImageSubresourceRange { - /// Aspect of the texture. Color textures must be [`TextureAspect::All`][TAA]. - /// - /// [TAA]: ../wgpu/enum.TextureAspect.html#variant.All - pub aspect: TextureAspect, - /// Base mip level. - pub base_mip_level: u32, - /// Mip level count. - /// If `Some(count)`, `base_mip_level + count` must be less or equal to underlying texture mip count. - /// If `None`, considered to include the rest of the mipmap levels, but at least 1 in total. - pub mip_level_count: Option<u32>, - /// Base array layer. - pub base_array_layer: u32, - /// Layer count. - /// If `Some(count)`, `base_array_layer + count` must be less or equal to the underlying array count. - /// If `None`, considered to include the rest of the array layers, but at least 1 in total. - pub array_layer_count: Option<u32>, +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct CommandBufferDescriptor<L> { + /// Debug label of this command buffer. + pub label: L, } -impl ImageSubresourceRange { - /// Returns if the given range represents a full resource, with a texture of the given - /// layer count and mip count. - /// - /// ```rust - /// # use wgpu_types as wgpu; - /// - /// let range_none = wgpu::ImageSubresourceRange { - /// aspect: wgpu::TextureAspect::All, - /// base_mip_level: 0, - /// mip_level_count: None, - /// base_array_layer: 0, - /// array_layer_count: None, - /// }; - /// assert_eq!(range_none.is_full_resource(wgpu::TextureFormat::Stencil8, 5, 10), true); - /// - /// let range_some = wgpu::ImageSubresourceRange { - /// aspect: wgpu::TextureAspect::All, - /// base_mip_level: 0, - /// mip_level_count: Some(5), - /// base_array_layer: 0, - /// array_layer_count: Some(10), - /// }; - /// assert_eq!(range_some.is_full_resource(wgpu::TextureFormat::Stencil8, 5, 10), true); - /// - /// let range_mixed = wgpu::ImageSubresourceRange { - /// aspect: wgpu::TextureAspect::StencilOnly, - /// base_mip_level: 0, - /// // Only partial resource - /// mip_level_count: Some(3), - /// base_array_layer: 0, - /// array_layer_count: None, - /// }; - /// assert_eq!(range_mixed.is_full_resource(wgpu::TextureFormat::Stencil8, 5, 10), false); - /// ``` - #[must_use] - pub fn is_full_resource( - &self, - format: TextureFormat, - mip_levels: u32, - array_layers: u32, - ) -> bool { - // Mip level count and array layer count need to deal with both the None and Some(count) case. - let mip_level_count = self.mip_level_count.unwrap_or(mip_levels); - let array_layer_count = self.array_layer_count.unwrap_or(array_layers); - - let aspect_eq = Some(format) == format.aspect_specific_format(self.aspect); - - let base_mip_level_eq = self.base_mip_level == 0; - let mip_level_count_eq = mip_level_count == mip_levels; - - let base_array_layer_eq = self.base_array_layer == 0; - let array_layer_count_eq = array_layer_count == array_layers; - - aspect_eq - && base_mip_level_eq - && mip_level_count_eq - && base_array_layer_eq - && array_layer_count_eq - } - - /// Returns the mip level range of a subresource range describes for a specific texture. - #[must_use] - pub fn mip_range(&self, mip_level_count: u32) -> Range<u32> { - self.base_mip_level..match self.mip_level_count { - Some(mip_level_count) => self.base_mip_level + mip_level_count, - None => mip_level_count, - } - } - - /// Returns the layer range of a subresource range describes for a specific texture. +impl<L> CommandBufferDescriptor<L> { + /// Takes a closure and maps the label of the command buffer descriptor into another. #[must_use] - pub fn layer_range(&self, array_layer_count: u32) -> Range<u32> { - self.base_array_layer..match self.array_layer_count { - Some(array_layer_count) => self.base_array_layer + array_layer_count, - None => array_layer_count, + pub fn map_label<K>(&self, fun: impl FnOnce(&L) -> K) -> CommandBufferDescriptor<K> { + CommandBufferDescriptor { + label: fun(&self.label), } } } -/// Color variation to use when sampler addressing mode is [`AddressMode::ClampToBorder`] -#[repr(C)] -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum SamplerBorderColor { - /// [0, 0, 0, 0] - TransparentBlack, - /// [0, 0, 0, 1] - OpaqueBlack, - /// [1, 1, 1, 1] - OpaqueWhite, - - /// On the Metal backend, this is equivalent to `TransparentBlack` for - /// textures that have an alpha component, and equivalent to `OpaqueBlack` - /// for textures that do not have an alpha component. On other backends, - /// this is equivalent to `TransparentBlack`. Requires - /// [`Features::ADDRESS_MODE_CLAMP_TO_ZERO`]. Not supported on the web. - Zero, -} - /// Describes how to create a `QuerySet`. /// /// Corresponds to [WebGPU `GPUQuerySetDescriptor`]( @@ -7810,7 +489,7 @@ impl<L> QuerySetDescriptor<L> { /// Corresponds to [WebGPU `GPUQueryType`]( /// https://gpuweb.github.io/gpuweb/#enumdef-gpuquerytype). /// -/// [`QuerySet`]: ../wgpu/struct.QuerySet.html +#[doc = link_to_wgpu_item!(struct QuerySet)] #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum QueryType { @@ -7833,7 +512,7 @@ pub enum QueryType { /// /// [`Features::TIMESTAMP_QUERY`] must be enabled to use this query type. /// - /// [Qgtp]: ../wgpu/struct.Queue.html#method.get_timestamp_period + #[doc = link_to_wgpu_docs!(["Qgtp"]: "struct.Queue.html#method.get_timestamp_period")] Timestamp, } @@ -7876,405 +555,6 @@ bitflags::bitflags! { } } -/// Argument buffer layout for `draw_indirect` commands. -#[repr(C)] -#[derive(Copy, Clone, Debug, Default, Pod, Zeroable)] -pub struct DrawIndirectArgs { - /// The number of vertices to draw. - pub vertex_count: u32, - /// The number of instances to draw. - pub instance_count: u32, - /// The Index of the first vertex to draw. - pub first_vertex: u32, - /// The instance ID of the first instance to draw. - /// - /// Has to be 0, unless [`Features::INDIRECT_FIRST_INSTANCE`](crate::Features::INDIRECT_FIRST_INSTANCE) is enabled. - pub first_instance: u32, -} - -impl DrawIndirectArgs { - /// Returns the bytes representation of the struct, ready to be written in a buffer. - #[must_use] - pub fn as_bytes(&self) -> &[u8] { - bytemuck::bytes_of(self) - } -} - -/// Argument buffer layout for `draw_indexed_indirect` commands. -#[repr(C)] -#[derive(Copy, Clone, Debug, Default, Pod, Zeroable)] -pub struct DrawIndexedIndirectArgs { - /// The number of indices to draw. - pub index_count: u32, - /// The number of instances to draw. - pub instance_count: u32, - /// The first index within the index buffer. - pub first_index: u32, - /// The value added to the vertex index before indexing into the vertex buffer. - pub base_vertex: i32, - /// The instance ID of the first instance to draw. - /// - /// Has to be 0, unless [`Features::INDIRECT_FIRST_INSTANCE`](crate::Features::INDIRECT_FIRST_INSTANCE) is enabled. - pub first_instance: u32, -} - -impl DrawIndexedIndirectArgs { - /// Returns the bytes representation of the struct, ready to be written in a buffer. - #[must_use] - pub fn as_bytes(&self) -> &[u8] { - bytemuck::bytes_of(self) - } -} - -/// Argument buffer layout for `dispatch_indirect` commands. -#[repr(C)] -#[derive(Copy, Clone, Debug, Default, Pod, Zeroable)] -pub struct DispatchIndirectArgs { - /// The number of work groups in X dimension. - pub x: u32, - /// The number of work groups in Y dimension. - pub y: u32, - /// The number of work groups in Z dimension. - pub z: u32, -} - -impl DispatchIndirectArgs { - /// Returns the bytes representation of the struct, ready to be written into a buffer. - #[must_use] - pub fn as_bytes(&self) -> &[u8] { - bytemuck::bytes_of(self) - } -} - -/// Describes how shader bound checks should be performed. -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct ShaderRuntimeChecks { - /// Enforce bounds checks in shaders, even if the underlying driver doesn't - /// support doing so natively. - /// - /// When this is `true`, `wgpu` promises that shaders can only read or - /// write the accessible region of a bindgroup's buffer bindings. If - /// the underlying graphics platform cannot implement these bounds checks - /// itself, `wgpu` will inject bounds checks before presenting the - /// shader to the platform. - /// - /// When this is `false`, `wgpu` only enforces such bounds checks if the - /// underlying platform provides a way to do so itself. `wgpu` does not - /// itself add any bounds checks to generated shader code. - /// - /// Note that `wgpu` users may try to initialize only those portions of - /// buffers that they anticipate might be read from. Passing `false` here - /// may allow shaders to see wider regions of the buffers than expected, - /// making such deferred initialization visible to the application. - pub bounds_checks: bool, - /// - /// If false, the caller MUST ensure that all passed shaders do not contain any infinite loops. - /// - /// If it does, backend compilers MAY treat such a loop as unreachable code and draw - /// conclusions about other safety-critical code paths. This option SHOULD NOT be disabled - /// when running untrusted code. - pub force_loop_bounding: bool, - /// If false, the caller **MUST** ensure that in all passed shaders every function operating - /// on a ray query must obey these rules (functions using wgsl naming) - /// - `rayQueryInitialize` must have called before `rayQueryProceed` - /// - `rayQueryProceed` must have been called, returned true and have hit an AABB before - /// `rayQueryGenerateIntersection` is called - /// - `rayQueryProceed` must have been called, returned true and have hit a triangle before - /// `rayQueryConfirmIntersection` is called - /// - `rayQueryProceed` must have been called and have returned true before `rayQueryTerminate`, - /// `getCandidateHitVertexPositions` or `rayQueryGetCandidateIntersection` is called - /// - `rayQueryProceed` must have been called and have returned false before `rayQueryGetCommittedIntersection` - /// or `getCommittedHitVertexPositions` are called - /// - /// It is the aim that these cases will not cause UB if this is set to true, but currently this will still happen on DX12 and Metal. - pub ray_query_initialization_tracking: bool, -} - -impl ShaderRuntimeChecks { - /// Creates a new configuration where the shader is fully checked. - #[must_use] - pub const fn checked() -> Self { - unsafe { Self::all(true) } - } - - /// Creates a new configuration where none of the checks are performed. - /// - /// # Safety - /// - /// See the documentation for the `set_*` methods for the safety requirements - /// of each sub-configuration. - #[must_use] - pub const fn unchecked() -> Self { - unsafe { Self::all(false) } - } - - /// Creates a new configuration where all checks are enabled or disabled. To safely - /// create a configuration with all checks enabled, use [`ShaderRuntimeChecks::checked`]. - /// - /// # Safety - /// - /// See the documentation for the `set_*` methods for the safety requirements - /// of each sub-configuration. - #[must_use] - pub const unsafe fn all(all_checks: bool) -> Self { - Self { - bounds_checks: all_checks, - force_loop_bounding: all_checks, - ray_query_initialization_tracking: all_checks, - } - } -} - -impl Default for ShaderRuntimeChecks { - fn default() -> Self { - Self::checked() - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -/// Descriptor for all size defining attributes of a single triangle geometry inside a bottom level acceleration structure. -pub struct BlasTriangleGeometrySizeDescriptor { - /// Format of a vertex position, must be [`VertexFormat::Float32x3`] - /// with just [`Features::EXPERIMENTAL_RAY_QUERY`] - /// but [`Features::EXTENDED_ACCELERATION_STRUCTURE_VERTEX_FORMATS`] adds more. - pub vertex_format: VertexFormat, - /// Number of vertices. - pub vertex_count: u32, - /// Format of an index. Only needed if an index buffer is used. - /// If `index_format` is provided `index_count` is required. - pub index_format: Option<IndexFormat>, - /// Number of indices. Only needed if an index buffer is used. - /// If `index_count` is provided `index_format` is required. - pub index_count: Option<u32>, - /// Flags for the geometry. - pub flags: AccelerationStructureGeometryFlags, -} - -#[derive(Clone, Debug)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -/// Descriptor for all size defining attributes of all geometries inside a bottom level acceleration structure. -pub enum BlasGeometrySizeDescriptors { - /// Triangle geometry version. - Triangles { - /// Descriptor for each triangle geometry. - descriptors: Vec<BlasTriangleGeometrySizeDescriptor>, - }, -} - -#[repr(u8)] -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -/// Update mode for acceleration structure builds. -pub enum AccelerationStructureUpdateMode { - /// Always perform a full build. - Build, - /// If possible, perform an incremental update. - /// - /// Not advised for major topology changes. - /// (Useful for e.g. skinning) - PreferUpdate, -} - -#[repr(C)] -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -/// Descriptor for creating a bottom level acceleration structure. -pub struct CreateBlasDescriptor<L> { - /// Label for the bottom level acceleration structure. - pub label: L, - /// Flags for the bottom level acceleration structure. - pub flags: AccelerationStructureFlags, - /// Update mode for the bottom level acceleration structure. - pub update_mode: AccelerationStructureUpdateMode, -} - -impl<L> CreateBlasDescriptor<L> { - /// Takes a closure and maps the label of the blas descriptor into another. - pub fn map_label<K>(&self, fun: impl FnOnce(&L) -> K) -> CreateBlasDescriptor<K> { - CreateBlasDescriptor { - label: fun(&self.label), - flags: self.flags, - update_mode: self.update_mode, - } - } -} - -#[repr(C)] -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -/// Descriptor for creating a top level acceleration structure. -pub struct CreateTlasDescriptor<L> { - /// Label for the top level acceleration structure. - pub label: L, - /// Number of instances that can be stored in the acceleration structure. - pub max_instances: u32, - /// Flags for the bottom level acceleration structure. - pub flags: AccelerationStructureFlags, - /// Update mode for the bottom level acceleration structure. - pub update_mode: AccelerationStructureUpdateMode, -} - -impl<L> CreateTlasDescriptor<L> { - /// Takes a closure and maps the label of the blas descriptor into another. - pub fn map_label<K>(&self, fun: impl FnOnce(&L) -> K) -> CreateTlasDescriptor<K> { - CreateTlasDescriptor { - label: fun(&self.label), - flags: self.flags, - update_mode: self.update_mode, - max_instances: self.max_instances, - } - } -} - -bitflags::bitflags!( - /// Flags for acceleration structures - #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] - #[cfg_attr(feature = "serde", serde(transparent))] - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] - pub struct AccelerationStructureFlags: u8 { - /// Allow for incremental updates (no change in size), currently this is unimplemented - /// and will build as normal (this is fine, update vs build should be unnoticeable) - const ALLOW_UPDATE = 1 << 0; - /// Allow the acceleration structure to be compacted in a copy operation - /// (`Blas::prepare_for_compaction`, `CommandEncoder::compact_blas`). - const ALLOW_COMPACTION = 1 << 1; - /// Optimize for fast ray tracing performance, recommended if the geometry is unlikely - /// to change (e.g. in a game: non-interactive scene geometry) - const PREFER_FAST_TRACE = 1 << 2; - /// Optimize for fast build time, recommended if geometry is likely to change frequently - /// (e.g. in a game: player model). - const PREFER_FAST_BUILD = 1 << 3; - /// Optimize for low memory footprint (both while building and in the output BLAS). - const LOW_MEMORY = 1 << 4; - /// Use `BlasTriangleGeometry::transform_buffer` when building a BLAS (only allowed in - /// BLAS creation) - const USE_TRANSFORM = 1 << 5; - /// Allow retrieval of the vertices of the triangle hit by a ray. - const ALLOW_RAY_HIT_VERTEX_RETURN = 1 << 6; - } -); - -bitflags::bitflags!( - /// Flags for acceleration structure geometries - #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] - #[cfg_attr(feature = "serde", serde(transparent))] - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] - pub struct AccelerationStructureGeometryFlags: u8 { - /// Is OPAQUE (is there no alpha test) recommended as currently in naga there is no - /// candidate intersections yet so currently BLASes without this flag will not have hits. - /// Not enabling this makes the BLAS unable to be interacted with in WGSL. - const OPAQUE = 1 << 0; - /// NO_DUPLICATE_ANY_HIT_INVOCATION, not useful unless using hal with wgpu, ray-tracing - /// pipelines are not supported in wgpu so any-hit shaders do not exist. For when any-hit - /// shaders are implemented (or experienced users who combine this with an underlying library: - /// for any primitive (triangle or AABB) multiple any-hit shaders sometimes may be invoked - /// (especially in AABBs like a sphere), if this flag in present only one hit on a primitive may - /// invoke an any-hit shader. - const NO_DUPLICATE_ANY_HIT_INVOCATION = 1 << 1; - } -); - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -/// What a copy between acceleration structures should do -pub enum AccelerationStructureCopy { - /// Directly duplicate an acceleration structure to another - Clone, - /// Duplicate and compact an acceleration structure - Compact, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -/// What type the data of an acceleration structure is -pub enum AccelerationStructureType { - /// The types of the acceleration structure are triangles - Triangles, - /// The types of the acceleration structure are axis aligned bounding boxes - AABBs, - /// The types of the acceleration structure are instances - Instances, -} - -/// Alignment requirement for transform buffers used in acceleration structure builds -pub const TRANSFORM_BUFFER_ALIGNMENT: BufferAddress = 16; - -/// Alignment requirement for instance buffers used in acceleration structure builds (`build_acceleration_structures_unsafe_tlas`) -pub const INSTANCE_BUFFER_ALIGNMENT: BufferAddress = 16; - -pub use send_sync::*; - -#[doc(hidden)] -mod send_sync { - pub trait WasmNotSendSync: WasmNotSend + WasmNotSync {} - impl<T: WasmNotSend + WasmNotSync> WasmNotSendSync for T {} - #[cfg(any( - not(target_arch = "wasm32"), - all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") - ) - ))] - pub trait WasmNotSend: Send {} - #[cfg(any( - not(target_arch = "wasm32"), - all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") - ) - ))] - impl<T: Send> WasmNotSend for T {} - #[cfg(not(any( - not(target_arch = "wasm32"), - all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") - ) - )))] - pub trait WasmNotSend {} - #[cfg(not(any( - not(target_arch = "wasm32"), - all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") - ) - )))] - impl<T> WasmNotSend for T {} - - #[cfg(any( - not(target_arch = "wasm32"), - all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") - ) - ))] - pub trait WasmNotSync: Sync {} - #[cfg(any( - not(target_arch = "wasm32"), - all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") - ) - ))] - impl<T: Sync> WasmNotSync for T {} - #[cfg(not(any( - not(target_arch = "wasm32"), - all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") - ) - )))] - pub trait WasmNotSync {} - #[cfg(not(any( - not(target_arch = "wasm32"), - all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") - ) - )))] - impl<T> WasmNotSync for T {} -} - /// Corresponds to a [`GPUDeviceLostReason`]. /// /// [`GPUDeviceLostReason`]: https://www.w3.org/TR/webgpu/#enumdef-gpudevicelostreason @@ -8287,100 +567,3 @@ pub enum DeviceLostReason { /// The device's `destroy` method was called. Destroyed = 1, } - -/// Descriptor for a shader module given by any of several sources. -/// These shaders are passed through directly to the underlying api. -/// At least one shader type that may be used by the backend must be `Some` or a panic is raised. -#[derive(Debug, Clone)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct CreateShaderModuleDescriptorPassthrough<'a, L> { - /// Entrypoint. Unused for Spir-V. - pub entry_point: String, - /// Debug label of the shader module. This will show up in graphics debuggers for easy identification. - pub label: L, - /// Number of workgroups in each dimension x, y and z. Unused for Spir-V. - pub num_workgroups: (u32, u32, u32), - /// Runtime checks that should be enabled. - pub runtime_checks: ShaderRuntimeChecks, - - /// Binary SPIR-V data, in 4-byte words. - pub spirv: Option<Cow<'a, [u32]>>, - /// Shader DXIL source. - pub dxil: Option<Cow<'a, [u8]>>, - /// Shader MSL source. - pub msl: Option<Cow<'a, str>>, - /// Shader HLSL source. - pub hlsl: Option<Cow<'a, str>>, - /// Shader GLSL source (currently unused). - pub glsl: Option<Cow<'a, str>>, - /// Shader WGSL source. - pub wgsl: Option<Cow<'a, str>>, -} - -// This is so people don't have to fill in fields they don't use, like num_workgroups, -// entry_point, or other shader languages they didn't compile for -impl<'a, L: Default> Default for CreateShaderModuleDescriptorPassthrough<'a, L> { - fn default() -> Self { - Self { - entry_point: "".into(), - label: Default::default(), - num_workgroups: (0, 0, 0), - runtime_checks: ShaderRuntimeChecks::unchecked(), - spirv: None, - dxil: None, - msl: None, - hlsl: None, - glsl: None, - wgsl: None, - } - } -} - -impl<'a, L> CreateShaderModuleDescriptorPassthrough<'a, L> { - /// Takes a closure and maps the label of the shader module descriptor into another. - pub fn map_label<K>( - &self, - fun: impl FnOnce(&L) -> K, - ) -> CreateShaderModuleDescriptorPassthrough<'a, K> { - CreateShaderModuleDescriptorPassthrough { - entry_point: self.entry_point.clone(), - label: fun(&self.label), - num_workgroups: self.num_workgroups, - runtime_checks: self.runtime_checks, - spirv: self.spirv.clone(), - dxil: self.dxil.clone(), - msl: self.msl.clone(), - hlsl: self.hlsl.clone(), - glsl: self.glsl.clone(), - wgsl: self.wgsl.clone(), - } - } - - #[cfg(feature = "trace")] - /// Returns the source data for tracing purpose. - pub fn trace_data(&self) -> &[u8] { - if let Some(spirv) = &self.spirv { - bytemuck::cast_slice(spirv) - } else if let Some(msl) = &self.msl { - msl.as_bytes() - } else if let Some(dxil) = &self.dxil { - dxil - } else { - panic!("No binary data provided to `ShaderModuleDescriptorGeneric`") - } - } - - #[cfg(feature = "trace")] - /// Returns the binary file extension for tracing purpose. - pub fn trace_binary_ext(&self) -> &'static str { - if self.spirv.is_some() { - "spv" - } else if self.msl.is_some() { - "msl" - } else if self.dxil.is_some() { - "dxil" - } else { - panic!("No binary data provided to `ShaderModuleDescriptorGeneric`") - } - } -} diff --git a/third_party/rust/wgpu-types/src/limits.rs b/third_party/rust/wgpu-types/src/limits.rs @@ -0,0 +1,956 @@ +//! [`Limits`] and downlevel-related types. + +use core::cmp::Ordering; + +#[cfg(any(feature = "serde", test))] +use serde::{Deserialize, Serialize}; + +#[cfg(doc)] +use crate::{Features, TextureFormat}; + +/// Invoke a macro for each of the limits. +/// +/// The supplied macro should take two arguments. The first is a limit name, as +/// an identifier, typically used to access a member of `struct Limits`. The +/// second is `Ordering::Less` if valid values are less than the limit (the +/// common case), or `Ordering::Greater` if valid values are more than the limit +/// (for limits like alignments, which are minima instead of maxima). +macro_rules! with_limits { + ($macro_name:ident) => { + $macro_name!(max_texture_dimension_1d, Ordering::Less); + $macro_name!(max_texture_dimension_1d, Ordering::Less); + $macro_name!(max_texture_dimension_2d, Ordering::Less); + $macro_name!(max_texture_dimension_3d, Ordering::Less); + $macro_name!(max_texture_array_layers, Ordering::Less); + $macro_name!(max_bind_groups, Ordering::Less); + $macro_name!(max_bindings_per_bind_group, Ordering::Less); + $macro_name!( + max_dynamic_uniform_buffers_per_pipeline_layout, + Ordering::Less + ); + $macro_name!( + max_dynamic_storage_buffers_per_pipeline_layout, + Ordering::Less + ); + $macro_name!(max_sampled_textures_per_shader_stage, Ordering::Less); + $macro_name!(max_samplers_per_shader_stage, Ordering::Less); + $macro_name!(max_storage_buffers_per_shader_stage, Ordering::Less); + $macro_name!(max_storage_textures_per_shader_stage, Ordering::Less); + $macro_name!(max_uniform_buffers_per_shader_stage, Ordering::Less); + $macro_name!(max_binding_array_elements_per_shader_stage, Ordering::Less); + $macro_name!(max_uniform_buffer_binding_size, Ordering::Less); + $macro_name!(max_storage_buffer_binding_size, Ordering::Less); + $macro_name!(max_vertex_buffers, Ordering::Less); + $macro_name!(max_buffer_size, Ordering::Less); + $macro_name!(max_vertex_attributes, Ordering::Less); + $macro_name!(max_vertex_buffer_array_stride, Ordering::Less); + $macro_name!(min_uniform_buffer_offset_alignment, Ordering::Greater); + $macro_name!(min_storage_buffer_offset_alignment, Ordering::Greater); + $macro_name!(max_inter_stage_shader_components, Ordering::Less); + $macro_name!(max_color_attachments, Ordering::Less); + $macro_name!(max_color_attachment_bytes_per_sample, Ordering::Less); + $macro_name!(max_compute_workgroup_storage_size, Ordering::Less); + $macro_name!(max_compute_invocations_per_workgroup, Ordering::Less); + $macro_name!(max_compute_workgroup_size_x, Ordering::Less); + $macro_name!(max_compute_workgroup_size_y, Ordering::Less); + $macro_name!(max_compute_workgroup_size_z, Ordering::Less); + $macro_name!(max_compute_workgroups_per_dimension, Ordering::Less); + + $macro_name!(max_immediate_size, Ordering::Less); + $macro_name!(max_non_sampler_bindings, Ordering::Less); + + $macro_name!(max_task_mesh_workgroup_total_count, Ordering::Less); + $macro_name!(max_task_mesh_workgroups_per_dimension, Ordering::Less); + $macro_name!(max_task_invocations_per_workgroup, Ordering::Less); + $macro_name!(max_task_invocations_per_dimension, Ordering::Less); + $macro_name!(max_mesh_invocations_per_workgroup, Ordering::Less); + $macro_name!(max_mesh_invocations_per_dimension, Ordering::Less); + + $macro_name!(max_task_payload_size, Ordering::Less); + $macro_name!(max_mesh_output_vertices, Ordering::Less); + $macro_name!(max_mesh_output_primitives, Ordering::Less); + $macro_name!(max_mesh_output_layers, Ordering::Less); + $macro_name!(max_mesh_multiview_view_count, Ordering::Less); + + $macro_name!(max_blas_primitive_count, Ordering::Less); + $macro_name!(max_blas_geometry_count, Ordering::Less); + $macro_name!(max_tlas_instance_count, Ordering::Less); + + $macro_name!(max_multiview_view_count, Ordering::Less); + }; +} + +/// Represents the sets of limits an adapter/device supports. +/// +/// We provide three different defaults. +/// - [`Limits::downlevel_defaults()`]. This is a set of limits that is guaranteed to work on almost +/// all backends, including "downlevel" backends such as OpenGL and D3D11, other than WebGL. For +/// most applications we recommend using these limits, assuming they are high enough for your +/// application, and you do not intent to support WebGL. +/// - [`Limits::downlevel_webgl2_defaults()`] This is a set of limits that is lower even than the +/// [`downlevel_defaults()`], configured to be low enough to support running in the browser using +/// WebGL2. +/// - [`Limits::default()`]. This is the set of limits that is guaranteed to work on all modern +/// backends and is guaranteed to be supported by WebGPU. Applications needing more modern +/// features can use this as a reasonable set of limits if they are targeting only desktop and +/// modern mobile devices. +/// +/// We recommend starting with the most restrictive limits you can and manually increasing the +/// limits you need boosted. This will let you stay running on all hardware that supports the limits +/// you need. +/// +/// Limits "better" than the default must be supported by the adapter and requested when requesting +/// a device. If limits "better" than the adapter supports are requested, requesting a device will +/// panic. Once a device is requested, you may only use resources up to the limits requested _even_ +/// if the adapter supports "better" limits. +/// +/// Requesting limits that are "better" than you need may cause performance to decrease because the +/// implementation needs to support more than is needed. You should ideally only request exactly +/// what you need. +/// +/// Corresponds to [WebGPU `GPUSupportedLimits`]( +/// https://gpuweb.github.io/gpuweb/#gpusupportedlimits). +/// +/// [`downlevel_defaults()`]: Limits::downlevel_defaults +#[repr(C)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase", default))] +pub struct Limits { + /// Maximum allowed value for the `size.width` of a texture created with `TextureDimension::D1`. + /// Defaults to 8192. Higher is "better". + #[cfg_attr(feature = "serde", serde(rename = "maxTextureDimension1D"))] + pub max_texture_dimension_1d: u32, + /// Maximum allowed value for the `size.width` and `size.height` of a texture created with `TextureDimension::D2`. + /// Defaults to 8192. Higher is "better". + #[cfg_attr(feature = "serde", serde(rename = "maxTextureDimension2D"))] + pub max_texture_dimension_2d: u32, + /// Maximum allowed value for the `size.width`, `size.height`, and `size.depth_or_array_layers` + /// of a texture created with `TextureDimension::D3`. + /// Defaults to 2048. Higher is "better". + #[cfg_attr(feature = "serde", serde(rename = "maxTextureDimension3D"))] + pub max_texture_dimension_3d: u32, + /// Maximum allowed value for the `size.depth_or_array_layers` of a texture created with `TextureDimension::D2`. + /// Defaults to 256. Higher is "better". + pub max_texture_array_layers: u32, + /// Amount of bind groups that can be attached to a pipeline at the same time. Defaults to 4. Higher is "better". + pub max_bind_groups: u32, + /// Maximum binding index allowed in `create_bind_group_layout`. Defaults to 1000. Higher is "better". + pub max_bindings_per_bind_group: u32, + /// Amount of uniform buffer bindings that can be dynamic in a single pipeline. Defaults to 8. Higher is "better". + pub max_dynamic_uniform_buffers_per_pipeline_layout: u32, + /// Amount of storage buffer bindings that can be dynamic in a single pipeline. Defaults to 4. Higher is "better". + pub max_dynamic_storage_buffers_per_pipeline_layout: u32, + /// Amount of sampled textures visible in a single shader stage. Defaults to 16. Higher is "better". + pub max_sampled_textures_per_shader_stage: u32, + /// Amount of samplers visible in a single shader stage. Defaults to 16. Higher is "better". + pub max_samplers_per_shader_stage: u32, + /// Amount of storage buffers visible in a single shader stage. Defaults to 8. Higher is "better". + pub max_storage_buffers_per_shader_stage: u32, + /// Amount of storage textures visible in a single shader stage. Defaults to 4. Higher is "better". + pub max_storage_textures_per_shader_stage: u32, + /// Amount of uniform buffers visible in a single shader stage. Defaults to 12. Higher is "better". + pub max_uniform_buffers_per_shader_stage: u32, + /// Amount of individual resources within binding arrays that can be accessed in a single shader stage. Applies + /// to all types of bindings except samplers. + /// + /// This "defaults" to 0. However if binding arrays are supported, all devices can support 500,000. Higher is "better". + pub max_binding_array_elements_per_shader_stage: u32, + /// Amount of individual samplers within binding arrays that can be accessed in a single shader stage. + /// + /// This "defaults" to 0. However if binding arrays are supported, all devices can support 1,000. Higher is "better". + pub max_binding_array_sampler_elements_per_shader_stage: u32, + /// Maximum size in bytes of a binding to a uniform buffer. Defaults to 64 KiB. Higher is "better". + pub max_uniform_buffer_binding_size: u32, + /// Maximum size in bytes of a binding to a storage buffer. Defaults to 128 MiB. Higher is "better". + pub max_storage_buffer_binding_size: u32, + /// Maximum length of `VertexState::buffers` when creating a `RenderPipeline`. + /// Defaults to 8. Higher is "better". + pub max_vertex_buffers: u32, + /// A limit above which buffer allocations are guaranteed to fail. + /// Defaults to 256 MiB. Higher is "better". + /// + /// Buffer allocations below the maximum buffer size may not succeed depending on available memory, + /// fragmentation and other factors. + pub max_buffer_size: u64, + /// Maximum length of `VertexBufferLayout::attributes`, summed over all `VertexState::buffers`, + /// when creating a `RenderPipeline`. + /// Defaults to 16. Higher is "better". + pub max_vertex_attributes: u32, + /// Maximum value for `VertexBufferLayout::array_stride` when creating a `RenderPipeline`. + /// Defaults to 2048. Higher is "better". + pub max_vertex_buffer_array_stride: u32, + /// Required `BufferBindingType::Uniform` alignment for `BufferBinding::offset` + /// when creating a `BindGroup`, or for `set_bind_group` `dynamicOffsets`. + /// Defaults to 256. Lower is "better". + pub min_uniform_buffer_offset_alignment: u32, + /// Required `BufferBindingType::Storage` alignment for `BufferBinding::offset` + /// when creating a `BindGroup`, or for `set_bind_group` `dynamicOffsets`. + /// Defaults to 256. Lower is "better". + pub min_storage_buffer_offset_alignment: u32, + /// Maximum allowed number of components (scalars) of input or output locations for + /// inter-stage communication (vertex outputs to fragment inputs). Defaults to 60. + /// Higher is "better". + pub max_inter_stage_shader_components: u32, + /// The maximum allowed number of color attachments. + pub max_color_attachments: u32, + /// The maximum number of bytes necessary to hold one sample (pixel or subpixel) of render + /// pipeline output data, across all color attachments as described by [`TextureFormat::target_pixel_byte_cost`] + /// and [`TextureFormat::target_component_alignment`]. Defaults to 32. Higher is "better". + /// + /// ⚠️ `Rgba8Unorm`/`Rgba8Snorm`/`Bgra8Unorm`/`Bgra8Snorm` are deceptively 8 bytes per sample. ⚠️ + pub max_color_attachment_bytes_per_sample: u32, + /// Maximum number of bytes used for workgroup memory in a compute entry point. Defaults to + /// 16384. Higher is "better". + pub max_compute_workgroup_storage_size: u32, + /// Maximum value of the product of the `workgroup_size` dimensions for a compute entry-point. + /// Defaults to 256. Higher is "better". + pub max_compute_invocations_per_workgroup: u32, + /// The maximum value of the `workgroup_size` X dimension for a compute stage `ShaderModule` entry-point. + /// Defaults to 256. Higher is "better". + pub max_compute_workgroup_size_x: u32, + /// The maximum value of the `workgroup_size` Y dimension for a compute stage `ShaderModule` entry-point. + /// Defaults to 256. Higher is "better". + pub max_compute_workgroup_size_y: u32, + /// The maximum value of the `workgroup_size` Z dimension for a compute stage `ShaderModule` entry-point. + /// Defaults to 64. Higher is "better". + pub max_compute_workgroup_size_z: u32, + /// The maximum value for each dimension of a `ComputePass::dispatch(x, y, z)` operation. + /// Defaults to 65535. Higher is "better". + pub max_compute_workgroups_per_dimension: u32, + + /// Amount of storage available for immediates in bytes. Defaults to 0. Higher is "better". + /// Requesting more than 0 during device creation requires [`Features::IMMEDIATES`] to be enabled. + /// + /// Expect the size to be: + /// - Vulkan: 128-256 bytes + /// - DX12: 256 bytes + /// - Metal: 4096 bytes + /// - OpenGL doesn't natively support immediates, and are emulated with uniforms, + /// so this number is less useful but likely 256. + pub max_immediate_size: u32, + /// Maximum number of live non-sampler bindings. + /// + /// <div class="warning"> + /// The default value is **1_000_000**, On systems with integrated GPUs (iGPUs)—particularly on Windows using the D3D12 + /// backend—this can lead to significant system RAM consumption since iGPUs share system memory directly with the CPU. + /// </div> + /// + /// This limit only affects the d3d12 backend. Using a large number will allow the device + /// to create many bind groups at the cost of a large up-front allocation at device creation. + pub max_non_sampler_bindings: u32, + + /// The maximum total value for a `RenderPass::draw_mesh_tasks(x, y, z)` operation or the + /// `@builtin(mesh_task_size)` returned from a task shader. Higher is "better". + pub max_task_mesh_workgroup_total_count: u32, + /// The maximum value for each dimension of a `RenderPass::draw_mesh_tasks(x, y, z)` operation. + /// Also for task shader outputs. Higher is "better". + pub max_task_mesh_workgroups_per_dimension: u32, + // These are fundamentally different. It is very common for limits on mesh shaders to be much lower. + /// Maximum total number of invocations, or threads, per task shader workgroup. Higher is "better". + pub max_task_invocations_per_workgroup: u32, + /// The maximum value for each dimension of a task shader's workgroup size. Higher is "better". + pub max_task_invocations_per_dimension: u32, + /// Maximum total number of invocations, or threads, per mesh shader workgroup. Higher is "better". + pub max_mesh_invocations_per_workgroup: u32, + /// The maximum value for each dimension of a mesh shader's workgroup size. Higher is "better". + pub max_mesh_invocations_per_dimension: u32, + + /// The maximum size of the payload passed from task to mesh shader. Higher is "better". + pub max_task_payload_size: u32, + /// The maximum number of vertices that a mesh shader may output. Higher is "better". + pub max_mesh_output_vertices: u32, + /// The maximum number of primitives that a mesh shader may output. Higher is "better". + pub max_mesh_output_primitives: u32, + /// The maximum number of layers that can be output from a mesh shader. Higher is "better". + /// See [#8509](https://github.com/gfx-rs/wgpu/issues/8509). + pub max_mesh_output_layers: u32, + /// The maximum number of views that can be used by a mesh shader in multiview rendering. + /// Higher is "better". + pub max_mesh_multiview_view_count: u32, + + /// The maximum number of primitive (ex: triangles, aabbs) a BLAS is allowed to have. Requesting + /// more than 0 during device creation only makes sense if [`Features::EXPERIMENTAL_RAY_QUERY`] + /// is enabled. + pub max_blas_primitive_count: u32, + /// The maximum number of geometry descriptors a BLAS is allowed to have. Requesting + /// more than 0 during device creation only makes sense if [`Features::EXPERIMENTAL_RAY_QUERY`] + /// is enabled. + pub max_blas_geometry_count: u32, + /// The maximum number of instances a TLAS is allowed to have. Requesting more than 0 during + /// device creation only makes sense if [`Features::EXPERIMENTAL_RAY_QUERY`] + /// is enabled. + pub max_tlas_instance_count: u32, + /// The maximum number of acceleration structures allowed to be used in a shader stage. + /// Requesting more than 0 during device creation only makes sense if [`Features::EXPERIMENTAL_RAY_QUERY`] + /// is enabled. + pub max_acceleration_structures_per_shader_stage: u32, + + /// The maximum number of views that can be used in multiview rendering + pub max_multiview_view_count: u32, +} + +impl Default for Limits { + fn default() -> Self { + Self::defaults() + } +} + +impl Limits { + /// These default limits are guaranteed to to work on all modern + /// backends and guaranteed to be supported by WebGPU + /// + /// Those limits are as follows: + /// ```rust + /// # use wgpu_types::Limits; + /// assert_eq!(Limits::defaults(), Limits { + /// max_texture_dimension_1d: 8192, + /// max_texture_dimension_2d: 8192, + /// max_texture_dimension_3d: 2048, + /// max_texture_array_layers: 256, + /// max_bind_groups: 4, + /// max_bindings_per_bind_group: 1000, + /// max_dynamic_uniform_buffers_per_pipeline_layout: 8, + /// max_dynamic_storage_buffers_per_pipeline_layout: 4, + /// max_sampled_textures_per_shader_stage: 16, + /// max_samplers_per_shader_stage: 16, + /// max_storage_buffers_per_shader_stage: 8, + /// max_storage_textures_per_shader_stage: 4, + /// max_uniform_buffers_per_shader_stage: 12, + /// max_binding_array_elements_per_shader_stage: 0, + /// max_binding_array_sampler_elements_per_shader_stage: 0, + /// max_uniform_buffer_binding_size: 64 << 10, // (64 KiB) + /// max_storage_buffer_binding_size: 128 << 20, // (128 MiB) + /// max_vertex_buffers: 8, + /// max_buffer_size: 256 << 20, // (256 MiB) + /// max_vertex_attributes: 16, + /// max_vertex_buffer_array_stride: 2048, + /// min_uniform_buffer_offset_alignment: 256, + /// min_storage_buffer_offset_alignment: 256, + /// max_inter_stage_shader_components: 60, + /// max_color_attachments: 8, + /// max_color_attachment_bytes_per_sample: 32, + /// max_compute_workgroup_storage_size: 16384, + /// max_compute_invocations_per_workgroup: 256, + /// max_compute_workgroup_size_x: 256, + /// max_compute_workgroup_size_y: 256, + /// max_compute_workgroup_size_z: 64, + /// max_compute_workgroups_per_dimension: 65535, + /// max_immediate_size: 0, + /// max_non_sampler_bindings: 1_000_000, + /// max_task_mesh_workgroup_total_count: 0, + /// max_task_mesh_workgroups_per_dimension: 0, + /// max_task_invocations_per_workgroup: 0, + /// max_task_invocations_per_dimension: 0, + /// max_mesh_invocations_per_workgroup: 0, + /// max_mesh_invocations_per_dimension: 0, + /// max_task_payload_size: 0, + /// max_mesh_output_vertices: 0, + /// max_mesh_output_primitives: 0, + /// max_mesh_output_layers: 0, + /// max_mesh_multiview_view_count: 0, + /// max_blas_primitive_count: 0, + /// max_blas_geometry_count: 0, + /// max_tlas_instance_count: 0, + /// max_acceleration_structures_per_shader_stage: 0, + /// max_multiview_view_count: 0, + /// }); + /// ``` + /// + /// Rust doesn't allow const in trait implementations, so we break this out + /// to allow reusing these defaults in const contexts + #[must_use] + pub const fn defaults() -> Self { + Self { + max_texture_dimension_1d: 8192, + max_texture_dimension_2d: 8192, + max_texture_dimension_3d: 2048, + max_texture_array_layers: 256, + max_bind_groups: 4, + max_bindings_per_bind_group: 1000, + max_dynamic_uniform_buffers_per_pipeline_layout: 8, + max_dynamic_storage_buffers_per_pipeline_layout: 4, + max_sampled_textures_per_shader_stage: 16, + max_samplers_per_shader_stage: 16, + max_storage_buffers_per_shader_stage: 8, + max_storage_textures_per_shader_stage: 4, + max_uniform_buffers_per_shader_stage: 12, + max_binding_array_elements_per_shader_stage: 0, + max_binding_array_sampler_elements_per_shader_stage: 0, + max_uniform_buffer_binding_size: 64 << 10, // (64 KiB) + max_storage_buffer_binding_size: 128 << 20, // (128 MiB) + max_vertex_buffers: 8, + max_buffer_size: 256 << 20, // (256 MiB) + max_vertex_attributes: 16, + max_vertex_buffer_array_stride: 2048, + min_uniform_buffer_offset_alignment: 256, + min_storage_buffer_offset_alignment: 256, + max_inter_stage_shader_components: 60, + max_color_attachments: 8, + max_color_attachment_bytes_per_sample: 32, + max_compute_workgroup_storage_size: 16384, + max_compute_invocations_per_workgroup: 256, + max_compute_workgroup_size_x: 256, + max_compute_workgroup_size_y: 256, + max_compute_workgroup_size_z: 64, + max_compute_workgroups_per_dimension: 65535, + max_immediate_size: 0, + max_non_sampler_bindings: 1_000_000, + + max_task_mesh_workgroup_total_count: 0, + max_task_mesh_workgroups_per_dimension: 0, + max_task_invocations_per_workgroup: 0, + max_task_invocations_per_dimension: 0, + max_mesh_invocations_per_workgroup: 0, + max_mesh_invocations_per_dimension: 0, + max_task_payload_size: 0, + max_mesh_output_vertices: 0, + max_mesh_output_primitives: 0, + max_mesh_output_layers: 0, + max_mesh_multiview_view_count: 0, + + max_blas_primitive_count: 0, + max_blas_geometry_count: 0, + max_tlas_instance_count: 0, + max_acceleration_structures_per_shader_stage: 0, + + max_multiview_view_count: 0, + } + } + + /// These default limits are guaranteed to be compatible with GLES-3.1, and D3D11 + /// + /// Those limits are as follows (different from default are marked with *): + /// ```rust + /// # use wgpu_types::Limits; + /// assert_eq!(Limits::downlevel_defaults(), Limits { + /// max_texture_dimension_1d: 2048, // * + /// max_texture_dimension_2d: 2048, // * + /// max_texture_dimension_3d: 256, // * + /// max_texture_array_layers: 256, + /// max_bind_groups: 4, + /// max_bindings_per_bind_group: 1000, + /// max_dynamic_uniform_buffers_per_pipeline_layout: 8, + /// max_dynamic_storage_buffers_per_pipeline_layout: 4, + /// max_sampled_textures_per_shader_stage: 16, + /// max_samplers_per_shader_stage: 16, + /// max_storage_buffers_per_shader_stage: 4, // * + /// max_storage_textures_per_shader_stage: 4, + /// max_uniform_buffers_per_shader_stage: 12, + /// max_binding_array_elements_per_shader_stage: 0, + /// max_binding_array_sampler_elements_per_shader_stage: 0, + /// max_uniform_buffer_binding_size: 16 << 10, // * (16 KiB) + /// max_storage_buffer_binding_size: 128 << 20, // (128 MiB) + /// max_vertex_buffers: 8, + /// max_vertex_attributes: 16, + /// max_vertex_buffer_array_stride: 2048, + /// max_immediate_size: 0, + /// min_uniform_buffer_offset_alignment: 256, + /// min_storage_buffer_offset_alignment: 256, + /// max_inter_stage_shader_components: 60, + /// max_color_attachments: 4, + /// max_color_attachment_bytes_per_sample: 32, + /// max_compute_workgroup_storage_size: 16352, // * + /// max_compute_invocations_per_workgroup: 256, + /// max_compute_workgroup_size_x: 256, + /// max_compute_workgroup_size_y: 256, + /// max_compute_workgroup_size_z: 64, + /// max_compute_workgroups_per_dimension: 65535, + /// max_buffer_size: 256 << 20, // (256 MiB) + /// max_non_sampler_bindings: 1_000_000, + /// + /// max_task_mesh_workgroup_total_count: 0, + /// max_task_mesh_workgroups_per_dimension: 0, + /// max_task_invocations_per_workgroup: 0, + /// max_task_invocations_per_dimension: 0, + /// max_mesh_invocations_per_workgroup: 0, + /// max_mesh_invocations_per_dimension: 0, + /// max_task_payload_size: 0, + /// max_mesh_output_vertices: 0, + /// max_mesh_output_primitives: 0, + /// max_mesh_output_layers: 0, + /// max_mesh_multiview_view_count: 0, + /// + /// max_blas_primitive_count: 0, + /// max_blas_geometry_count: 0, + /// max_tlas_instance_count: 0, + /// max_acceleration_structures_per_shader_stage: 0, + /// + /// max_multiview_view_count: 0, + /// }); + /// ``` + #[must_use] + pub const fn downlevel_defaults() -> Self { + Self { + max_texture_dimension_1d: 2048, + max_texture_dimension_2d: 2048, + max_texture_dimension_3d: 256, + max_storage_buffers_per_shader_stage: 4, + max_uniform_buffer_binding_size: 16 << 10, // (16 KiB) + max_color_attachments: 4, + // see: https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf#page=7 + max_compute_workgroup_storage_size: 16352, + ..Self::defaults() + } + } + + /// These default limits are guaranteed to be compatible with GLES-3.0, and D3D11, and WebGL2 + /// + /// Those limits are as follows (different from `downlevel_defaults` are marked with +, + /// *'s from `downlevel_defaults` shown as well.): + /// ```rust + /// # use wgpu_types::Limits; + /// assert_eq!(Limits::downlevel_webgl2_defaults(), Limits { + /// max_texture_dimension_1d: 2048, // * + /// max_texture_dimension_2d: 2048, // * + /// max_texture_dimension_3d: 256, // * + /// max_texture_array_layers: 256, + /// max_bind_groups: 4, + /// max_bindings_per_bind_group: 1000, + /// max_dynamic_uniform_buffers_per_pipeline_layout: 8, + /// max_dynamic_storage_buffers_per_pipeline_layout: 0, // + + /// max_sampled_textures_per_shader_stage: 16, + /// max_samplers_per_shader_stage: 16, + /// max_storage_buffers_per_shader_stage: 0, // * + + /// max_storage_textures_per_shader_stage: 0, // + + /// max_uniform_buffers_per_shader_stage: 11, // + + /// max_binding_array_elements_per_shader_stage: 0, + /// max_binding_array_sampler_elements_per_shader_stage: 0, + /// max_uniform_buffer_binding_size: 16 << 10, // * (16 KiB) + /// max_storage_buffer_binding_size: 0, // * + + /// max_vertex_buffers: 8, + /// max_vertex_attributes: 16, + /// max_vertex_buffer_array_stride: 255, // + + /// max_immediate_size: 0, + /// min_uniform_buffer_offset_alignment: 256, + /// min_storage_buffer_offset_alignment: 256, + /// max_inter_stage_shader_components: 31, + /// max_color_attachments: 4, + /// max_color_attachment_bytes_per_sample: 32, + /// max_compute_workgroup_storage_size: 0, // + + /// max_compute_invocations_per_workgroup: 0, // + + /// max_compute_workgroup_size_x: 0, // + + /// max_compute_workgroup_size_y: 0, // + + /// max_compute_workgroup_size_z: 0, // + + /// max_compute_workgroups_per_dimension: 0, // + + /// max_buffer_size: 256 << 20, // (256 MiB), + /// max_non_sampler_bindings: 1_000_000, + /// + /// max_task_mesh_workgroup_total_count: 0, + /// max_task_mesh_workgroups_per_dimension: 0, + /// max_task_invocations_per_workgroup: 0, + /// max_task_invocations_per_dimension: 0, + /// max_mesh_invocations_per_workgroup: 0, + /// max_mesh_invocations_per_dimension: 0, + /// max_task_payload_size: 0, + /// max_mesh_output_vertices: 0, + /// max_mesh_output_primitives: 0, + /// max_mesh_output_layers: 0, + /// max_mesh_multiview_view_count: 0, + /// + /// max_blas_primitive_count: 0, + /// max_blas_geometry_count: 0, + /// max_tlas_instance_count: 0, + /// max_acceleration_structures_per_shader_stage: 0, + /// + /// max_multiview_view_count: 0, + /// }); + /// ``` + #[must_use] + pub const fn downlevel_webgl2_defaults() -> Self { + Self { + max_uniform_buffers_per_shader_stage: 11, + max_storage_buffers_per_shader_stage: 0, + max_storage_textures_per_shader_stage: 0, + max_dynamic_storage_buffers_per_pipeline_layout: 0, + max_storage_buffer_binding_size: 0, + max_vertex_buffer_array_stride: 255, + max_compute_workgroup_storage_size: 0, + max_compute_invocations_per_workgroup: 0, + max_compute_workgroup_size_x: 0, + max_compute_workgroup_size_y: 0, + max_compute_workgroup_size_z: 0, + max_compute_workgroups_per_dimension: 0, + + // Value supported by Intel Celeron B830 on Windows (OpenGL 3.1) + max_inter_stage_shader_components: 31, + + // Most of the values should be the same as the downlevel defaults + ..Self::downlevel_defaults() + } + } + + /// Modify the current limits to use the resolution limits of the other. + /// + /// This is useful because the swapchain might need to be larger than any other image in the application. + /// + /// If your application only needs 512x512, you might be running on a 4k display and need extremely high resolution limits. + #[must_use] + pub const fn using_resolution(self, other: Self) -> Self { + Self { + max_texture_dimension_1d: other.max_texture_dimension_1d, + max_texture_dimension_2d: other.max_texture_dimension_2d, + max_texture_dimension_3d: other.max_texture_dimension_3d, + ..self + } + } + + /// Modify the current limits to use the buffer alignment limits of the adapter. + /// + /// This is useful for when you'd like to dynamically use the "best" supported buffer alignments. + #[must_use] + pub const fn using_alignment(self, other: Self) -> Self { + Self { + min_uniform_buffer_offset_alignment: other.min_uniform_buffer_offset_alignment, + min_storage_buffer_offset_alignment: other.min_storage_buffer_offset_alignment, + ..self + } + } + + /// The minimum guaranteed limits for acceleration structures if you enable [`Features::EXPERIMENTAL_RAY_QUERY`] + #[must_use] + pub const fn using_minimum_supported_acceleration_structure_values(self) -> Self { + Self { + max_blas_geometry_count: (1 << 24) - 1, // 2^24 - 1: Vulkan's minimum + max_tlas_instance_count: (1 << 24) - 1, // 2^24 - 1: Vulkan's minimum + max_blas_primitive_count: 1 << 28, // 2^28: Metal's minimum + max_acceleration_structures_per_shader_stage: 16, // Vulkan's minimum + ..self + } + } + + /// Modify the current limits to use the acceleration structure limits of `other` (`other` could + /// be the limits of the adapter). + #[must_use] + pub const fn using_acceleration_structure_values(self, other: Self) -> Self { + Self { + max_blas_geometry_count: other.max_blas_geometry_count, + max_tlas_instance_count: other.max_tlas_instance_count, + max_blas_primitive_count: other.max_blas_primitive_count, + max_acceleration_structures_per_shader_stage: other + .max_acceleration_structures_per_shader_stage, + ..self + } + } + + /// The recommended minimum limits for mesh shaders if you enable [`Features::EXPERIMENTAL_MESH_SHADER`] + /// + /// These are chosen somewhat arbitrarily. They are small enough that they should cover all physical devices, + /// but not necessarily all use cases. + #[must_use] + pub const fn using_recommended_minimum_mesh_shader_values(self) -> Self { + Self { + // This limitation comes from metal + max_task_mesh_workgroup_total_count: 1024, + // This is a DirectX limitation + max_task_mesh_workgroups_per_dimension: 256, + // Nvidia limit on vulkan + max_task_invocations_per_workgroup: 128, + max_task_invocations_per_dimension: 64, + + // DX12 limitation, revisit for vulkan + max_mesh_invocations_per_workgroup: 128, + max_mesh_invocations_per_dimension: 128, + + // Metal specifies this as its max + max_task_payload_size: 16384 - 32, + // DX12 limitation, revisit for vulkan + max_mesh_output_vertices: 256, + max_mesh_output_primitives: 256, + // llvmpipe once again requires this to be 8. An RTX 3060 supports well over 1024. + // Also DX12 vaguely suggests going over this is illegal in some cases. + max_mesh_output_layers: 8, + // llvmpipe reports 0 multiview count, which just means no multiview is allowed + max_mesh_multiview_view_count: 0, + ..self + } + } + + /// Compares every limits within self is within the limits given in `allowed`. + /// + /// If you need detailed information on failures, look at [`Limits::check_limits_with_fail_fn`]. + #[must_use] + pub fn check_limits(&self, allowed: &Self) -> bool { + let mut within = true; + self.check_limits_with_fail_fn(allowed, true, |_, _, _| within = false); + within + } + + /// Compares every limits within self is within the limits given in `allowed`. + /// For an easy to use binary choice, use [`Limits::check_limits`]. + /// + /// If a value is not within the allowed limit, this function calls the `fail_fn` + /// with the: + /// - limit name + /// - self's limit + /// - allowed's limit. + /// + /// If fatal is true, a single failure bails out the comparison after a single failure. + pub fn check_limits_with_fail_fn( + &self, + allowed: &Self, + fatal: bool, + mut fail_fn: impl FnMut(&'static str, u64, u64), + ) { + macro_rules! check_with_fail_fn { + ($name:ident, $ordering:expr) => { + let invalid_ord = $ordering.reverse(); + if self.$name.cmp(&allowed.$name) == invalid_ord { + fail_fn(stringify!($name), self.$name as u64, allowed.$name as u64); + if fatal { + return; + } + } + }; + } + + with_limits!(check_with_fail_fn); + } + + /// For each limit in `other` that is better than the value in `self`, + /// replace the value in `self` with the value from `other`. + /// + /// A request for a limit value less than the WebGPU-specified default must + /// be ignored. This function is used to clamp such requests to the default + /// value. + /// + /// This function is not for clamping requests for values beyond the + /// supported limits. For that purpose the desired function would be + /// `or_worse_values_from` (which doesn't exist, but could be added if + /// needed). + #[must_use] + pub fn or_better_values_from(mut self, other: &Self) -> Self { + macro_rules! or_better_value_from { + ($name:ident, $ordering:expr) => { + match $ordering { + // Limits that are maximum values (most of them) + Ordering::Less => self.$name = self.$name.max(other.$name), + // Limits that are minimum values + Ordering::Greater => self.$name = self.$name.min(other.$name), + Ordering::Equal => unreachable!(), + } + }; + } + + with_limits!(or_better_value_from); + + self + } +} + +/// Represents the sets of additional limits on an adapter, +/// which take place when running on downlevel backends. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct DownlevelLimits {} + +#[allow(clippy::derivable_impls)] +impl Default for DownlevelLimits { + fn default() -> Self { + DownlevelLimits {} + } +} + +/// Lists various ways the underlying platform does not conform to the WebGPU standard. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct DownlevelCapabilities { + /// Combined boolean flags. + pub flags: DownlevelFlags, + /// Additional limits + pub limits: DownlevelLimits, + /// Which collections of features shaders support. Defined in terms of D3D's shader models. + pub shader_model: ShaderModel, +} + +impl Default for DownlevelCapabilities { + fn default() -> Self { + Self { + flags: DownlevelFlags::all(), + limits: DownlevelLimits::default(), + shader_model: ShaderModel::Sm5, + } + } +} + +impl DownlevelCapabilities { + /// Returns true if the underlying platform offers complete support of the baseline WebGPU standard. + /// + /// If this returns false, some parts of the API will result in validation errors where they would not normally. + /// These parts can be determined by the values in this structure. + #[must_use] + pub fn is_webgpu_compliant(&self) -> bool { + self.flags.contains(DownlevelFlags::compliant()) + && self.limits == DownlevelLimits::default() + && self.shader_model >= ShaderModel::Sm5 + } +} + +bitflags::bitflags! { + /// Binary flags listing features that may or may not be present on downlevel adapters. + /// + /// A downlevel adapter is a GPU adapter that wgpu supports, but with potentially limited + /// features, due to the lack of hardware feature support. + /// + /// Flags that are **not** present for a downlevel adapter or device usually indicates + /// non-compliance with the WebGPU specification, but not always. + /// + /// You can check whether a set of flags is compliant through the + /// [`DownlevelCapabilities::is_webgpu_compliant()`] function. + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + #[cfg_attr(feature = "serde", serde(transparent))] + #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct DownlevelFlags: u32 { + /// The device supports compiling and using compute shaders. + /// + /// WebGL2, and GLES3.0 devices do not support compute. + const COMPUTE_SHADERS = 1 << 0; + /// Supports binding storage buffers and textures to fragment shaders. + const FRAGMENT_WRITABLE_STORAGE = 1 << 1; + /// Supports indirect drawing and dispatching. + /// + /// [`Self::COMPUTE_SHADERS`] must be present for this flag. + /// + /// WebGL2, GLES 3.0, and Metal on Apple1/Apple2 GPUs do not support indirect. + const INDIRECT_EXECUTION = 1 << 2; + /// Supports non-zero `base_vertex` parameter to direct indexed draw calls. + /// + /// Indirect calls, if supported, always support non-zero `base_vertex`. + /// + /// Supported by: + /// - Vulkan + /// - DX12 + /// - Metal on Apple3+ or Mac1+ + /// - OpenGL 3.2+ + /// - OpenGL ES 3.2 + const BASE_VERTEX = 1 << 3; + /// Supports reading from a depth/stencil texture while using it as a read-only + /// depth/stencil attachment. + /// + /// The WebGL2 and GLES backends do not support RODS. + const READ_ONLY_DEPTH_STENCIL = 1 << 4; + /// Supports textures with mipmaps which have a non power of two size. + const NON_POWER_OF_TWO_MIPMAPPED_TEXTURES = 1 << 5; + /// Supports textures that are cube arrays. + const CUBE_ARRAY_TEXTURES = 1 << 6; + /// Supports comparison samplers. + const COMPARISON_SAMPLERS = 1 << 7; + /// Supports different blend operations per color attachment. + const INDEPENDENT_BLEND = 1 << 8; + /// Supports storage buffers in vertex shaders. + const VERTEX_STORAGE = 1 << 9; + + /// Supports samplers with anisotropic filtering. Note this isn't actually required by + /// WebGPU, the implementation is allowed to completely ignore aniso clamp. This flag is + /// here for native backends so they can communicate to the user of aniso is enabled. + /// + /// All backends and all devices support anisotropic filtering. + const ANISOTROPIC_FILTERING = 1 << 10; + + /// Supports storage buffers in fragment shaders. + const FRAGMENT_STORAGE = 1 << 11; + + /// Supports sample-rate shading. + const MULTISAMPLED_SHADING = 1 << 12; + + /// Supports copies between depth textures and buffers. + /// + /// GLES/WebGL don't support this. + const DEPTH_TEXTURE_AND_BUFFER_COPIES = 1 << 13; + + /// Supports all the texture usages described in WebGPU. If this isn't supported, you + /// should call `get_texture_format_features` to get how you can use textures of a given format + const WEBGPU_TEXTURE_FORMAT_SUPPORT = 1 << 14; + + /// Supports buffer bindings with sizes that aren't a multiple of 16. + /// + /// WebGL doesn't support this. + const BUFFER_BINDINGS_NOT_16_BYTE_ALIGNED = 1 << 15; + + /// Supports buffers to combine [`BufferUsages::INDEX`] with usages other than [`BufferUsages::COPY_DST`] and [`BufferUsages::COPY_SRC`]. + /// Furthermore, in absence of this feature it is not allowed to copy index buffers from/to buffers with a set of usage flags containing + /// [`BufferUsages::VERTEX`]/[`BufferUsages::UNIFORM`]/[`BufferUsages::STORAGE`] or [`BufferUsages::INDIRECT`]. + /// + /// WebGL doesn't support this. + const UNRESTRICTED_INDEX_BUFFER = 1 << 16; + + /// Supports full 32-bit range indices (2^32-1 as opposed to 2^24-1 without this flag) + /// + /// Corresponds to Vulkan's `VkPhysicalDeviceFeatures.fullDrawIndexUint32` + const FULL_DRAW_INDEX_UINT32 = 1 << 17; + + /// Supports depth bias clamping + /// + /// Corresponds to Vulkan's `VkPhysicalDeviceFeatures.depthBiasClamp` + const DEPTH_BIAS_CLAMP = 1 << 18; + + /// Supports specifying which view format values are allowed when create_view() is called on a texture. + /// + /// The WebGL and GLES backends doesn't support this. + const VIEW_FORMATS = 1 << 19; + + /// With this feature not present, there are the following restrictions on `Queue::copy_external_image_to_texture`: + /// - The source must not be [`web_sys::OffscreenCanvas`] + /// - [`CopyExternalImageSourceInfo::origin`] must be zero. + /// - [`CopyExternalImageDestInfo::color_space`] must be srgb. + /// - If the source is an [`web_sys::ImageBitmap`]: + /// - [`CopyExternalImageSourceInfo::flip_y`] must be false. + /// - [`CopyExternalImageDestInfo::premultiplied_alpha`] must be false. + /// + /// WebGL doesn't support this. WebGPU does. + const UNRESTRICTED_EXTERNAL_TEXTURE_COPIES = 1 << 20; + + /// Supports specifying which view formats are allowed when calling create_view on the texture returned by + /// `Surface::get_current_texture`. + /// + /// The GLES/WebGL and Vulkan on Android doesn't support this. + const SURFACE_VIEW_FORMATS = 1 << 21; + + /// If this is true, calls to `CommandEncoder::resolve_query_set` will be performed on the queue timeline. + /// + /// If this is false, calls to `CommandEncoder::resolve_query_set` will be performed on the device (i.e. cpu) timeline + /// and will block that timeline until the query has data. You may work around this limitation by waiting until the submit + /// whose queries you are resolving is fully finished (through use of `queue.on_submitted_work_done`) and only + /// then submitting the resolve_query_set command. The queries will be guaranteed finished, so will not block. + /// + /// Supported by: + /// - Vulkan, + /// - DX12 + /// - Metal + /// - OpenGL 4.4+ + /// + /// Not Supported by: + /// - GL ES / WebGL + const NONBLOCKING_QUERY_RESOLVE = 1 << 22; + + /// Allows shaders to use `quantizeToF16`, `pack2x16float`, and `unpack2x16float`, which + /// operate on `f16`-precision values stored in `f32`s. + /// + /// Not supported by Vulkan on Mesa when [`Features::SHADER_F16`] is absent. + const SHADER_F16_IN_F32 = 1 << 23; + } +} + +impl DownlevelFlags { + /// All flags that indicate if the backend is WebGPU compliant + #[must_use] + pub const fn compliant() -> Self { + // We use manual bit twiddling to make this a const fn as `Sub` and `.remove` aren't const + + // WebGPU doesn't actually require aniso + Self::from_bits_truncate(Self::all().bits() & !Self::ANISOTROPIC_FILTERING.bits()) + } +} + +/// Collections of shader features a device supports if they support less than WebGPU normally allows. +// TODO: Fill out the differences between shader models more completely +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum ShaderModel { + /// Extremely limited shaders, including a total instruction limit. + Sm2, + /// Missing minor features and storage images. + Sm4, + /// WebGPU supports shader module 5. + Sm5, +} diff --git a/third_party/rust/wgpu-types/src/origin_extent.rs b/third_party/rust/wgpu-types/src/origin_extent.rs @@ -0,0 +1,192 @@ +#[cfg(any(feature = "serde", test))] +use serde::{Deserialize, Serialize}; + +use crate::TextureDimension; + +/// Origin of a copy from a 2D image. +/// +/// Corresponds to [WebGPU `GPUOrigin2D`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gpuorigin2ddict). +#[repr(C)] +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct Origin2d { + #[allow(missing_docs)] + pub x: u32, + #[allow(missing_docs)] + pub y: u32, +} + +impl Origin2d { + /// Zero origin. + pub const ZERO: Self = Self { x: 0, y: 0 }; + + /// Adds the third dimension to this origin + #[must_use] + pub fn to_3d(self, z: u32) -> Origin3d { + Origin3d { + x: self.x, + y: self.y, + z, + } + } +} + +impl core::fmt::Debug for Origin2d { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + (self.x, self.y).fmt(f) + } +} + +/// Origin of a copy to/from a texture. +/// +/// Corresponds to [WebGPU `GPUOrigin3D`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gpuorigin3ddict). +#[repr(C)] +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct Origin3d { + /// X position of the origin + pub x: u32, + /// Y position of the origin + pub y: u32, + /// Z position of the origin + pub z: u32, +} + +impl Origin3d { + /// Zero origin. + pub const ZERO: Self = Self { x: 0, y: 0, z: 0 }; + + /// Removes the third dimension from this origin + #[must_use] + pub fn to_2d(self) -> Origin2d { + Origin2d { + x: self.x, + y: self.y, + } + } +} + +impl Default for Origin3d { + fn default() -> Self { + Self::ZERO + } +} + +impl core::fmt::Debug for Origin3d { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + (self.x, self.y, self.z).fmt(f) + } +} + +/// Extent of a texture related operation. +/// +/// Corresponds to [WebGPU `GPUExtent3D`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gpuextent3ddict). +#[repr(C)] +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct Extent3d { + /// Width of the extent + pub width: u32, + /// Height of the extent + pub height: u32, + /// The depth of the extent or the number of array layers + #[cfg_attr(feature = "serde", serde(default = "default_depth"))] + pub depth_or_array_layers: u32, +} + +impl core::fmt::Debug for Extent3d { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + (self.width, self.height, self.depth_or_array_layers).fmt(f) + } +} + +#[cfg(feature = "serde")] +fn default_depth() -> u32 { + 1 +} + +impl Default for Extent3d { + fn default() -> Self { + Self { + width: 1, + height: 1, + depth_or_array_layers: 1, + } + } +} + +impl Extent3d { + /// Calculates the [physical size] backing a texture of the given + /// format and extent. This includes padding to the block width + /// and height of the format. + /// + /// This is the texture extent that you must upload at when uploading to _mipmaps_ of compressed textures. + /// + /// [physical size]: https://gpuweb.github.io/gpuweb/#physical-miplevel-specific-texture-extent + #[must_use] + pub fn physical_size(&self, format: crate::TextureFormat) -> Self { + let (block_width, block_height) = format.block_dimensions(); + + let width = self.width.div_ceil(block_width) * block_width; + let height = self.height.div_ceil(block_height) * block_height; + + Self { + width, + height, + depth_or_array_layers: self.depth_or_array_layers, + } + } + + /// Calculates the maximum possible count of mipmaps. + /// + /// Treats the depth as part of the mipmaps. If calculating + /// for a 2DArray texture, which does not mipmap depth, set depth to 1. + #[must_use] + pub fn max_mips(&self, dim: TextureDimension) -> u32 { + match dim { + TextureDimension::D1 => 1, + TextureDimension::D2 => { + let max_dim = self.width.max(self.height); + 32 - max_dim.leading_zeros() + } + TextureDimension::D3 => { + let max_dim = self.width.max(self.height.max(self.depth_or_array_layers)); + 32 - max_dim.leading_zeros() + } + } + } + + /// Calculates the extent at a given mip level. + /// + /// This is a low-level helper for internal use. + /// + /// It does *not* account for memory size being a multiple of block size. + /// + /// TODO(<https://github.com/gfx-rs/wgpu/issues/8491>): It also does not + /// consider whether an even dimension is required due to chroma + /// subsampling, but it probably should. + /// + /// <https://gpuweb.github.io/gpuweb/#logical-miplevel-specific-texture-extent> + #[doc(hidden)] + #[must_use] + pub fn mip_level_size(&self, level: u32, dim: TextureDimension) -> Self { + Self { + width: u32::max(1, self.width >> level), + height: match dim { + TextureDimension::D1 => 1, + _ => u32::max(1, self.height >> level), + }, + depth_or_array_layers: match dim { + TextureDimension::D1 => 1, + TextureDimension::D2 => self.depth_or_array_layers, + TextureDimension::D3 => u32::max(1, self.depth_or_array_layers >> level), + }, + } + } +} diff --git a/third_party/rust/wgpu-types/src/ray_tracing.rs b/third_party/rust/wgpu-types/src/ray_tracing.rs @@ -0,0 +1,177 @@ +use alloc::vec::Vec; + +#[cfg(any(feature = "serde", test))] +use serde::{Deserialize, Serialize}; + +#[cfg(doc)] +use crate::{Features, VertexFormat}; + +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +/// Descriptor for all size defining attributes of a single triangle geometry inside a bottom level acceleration structure. +pub struct BlasTriangleGeometrySizeDescriptor { + /// Format of a vertex position, must be [`VertexFormat::Float32x3`] + /// with just [`Features::EXPERIMENTAL_RAY_QUERY`] + /// but [`Features::EXTENDED_ACCELERATION_STRUCTURE_VERTEX_FORMATS`] adds more. + pub vertex_format: crate::VertexFormat, + /// Number of vertices. + pub vertex_count: u32, + /// Format of an index. Only needed if an index buffer is used. + /// If `index_format` is provided `index_count` is required. + pub index_format: Option<crate::IndexFormat>, + /// Number of indices. Only needed if an index buffer is used. + /// If `index_count` is provided `index_format` is required. + pub index_count: Option<u32>, + /// Flags for the geometry. + pub flags: AccelerationStructureGeometryFlags, +} + +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +/// Descriptor for all size defining attributes of all geometries inside a bottom level acceleration structure. +pub enum BlasGeometrySizeDescriptors { + /// Triangle geometry version. + Triangles { + /// Descriptor for each triangle geometry. + descriptors: Vec<BlasTriangleGeometrySizeDescriptor>, + }, +} + +#[repr(u8)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +/// Update mode for acceleration structure builds. +pub enum AccelerationStructureUpdateMode { + /// Always perform a full build. + Build, + /// If possible, perform an incremental update. + /// + /// Not advised for major topology changes. + /// (Useful for e.g. skinning) + PreferUpdate, +} + +#[repr(C)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +/// Descriptor for creating a bottom level acceleration structure. +pub struct CreateBlasDescriptor<L> { + /// Label for the bottom level acceleration structure. + pub label: L, + /// Flags for the bottom level acceleration structure. + pub flags: AccelerationStructureFlags, + /// Update mode for the bottom level acceleration structure. + pub update_mode: AccelerationStructureUpdateMode, +} + +impl<L> CreateBlasDescriptor<L> { + /// Takes a closure and maps the label of the blas descriptor into another. + pub fn map_label<K>(&self, fun: impl FnOnce(&L) -> K) -> CreateBlasDescriptor<K> { + CreateBlasDescriptor { + label: fun(&self.label), + flags: self.flags, + update_mode: self.update_mode, + } + } +} + +#[repr(C)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +/// Descriptor for creating a top level acceleration structure. +pub struct CreateTlasDescriptor<L> { + /// Label for the top level acceleration structure. + pub label: L, + /// Number of instances that can be stored in the acceleration structure. + pub max_instances: u32, + /// Flags for the bottom level acceleration structure. + pub flags: AccelerationStructureFlags, + /// Update mode for the bottom level acceleration structure. + pub update_mode: AccelerationStructureUpdateMode, +} + +impl<L> CreateTlasDescriptor<L> { + /// Takes a closure and maps the label of the blas descriptor into another. + pub fn map_label<K>(&self, fun: impl FnOnce(&L) -> K) -> CreateTlasDescriptor<K> { + CreateTlasDescriptor { + label: fun(&self.label), + flags: self.flags, + update_mode: self.update_mode, + max_instances: self.max_instances, + } + } +} + +bitflags::bitflags!( + /// Flags for acceleration structures + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + #[cfg_attr(feature = "serde", serde(transparent))] + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + pub struct AccelerationStructureFlags: u8 { + /// Allow for incremental updates (no change in size), currently this is unimplemented + /// and will build as normal (this is fine, update vs build should be unnoticeable) + const ALLOW_UPDATE = 1 << 0; + /// Allow the acceleration structure to be compacted in a copy operation + /// (`Blas::prepare_for_compaction`, `CommandEncoder::compact_blas`). + const ALLOW_COMPACTION = 1 << 1; + /// Optimize for fast ray tracing performance, recommended if the geometry is unlikely + /// to change (e.g. in a game: non-interactive scene geometry) + const PREFER_FAST_TRACE = 1 << 2; + /// Optimize for fast build time, recommended if geometry is likely to change frequently + /// (e.g. in a game: player model). + const PREFER_FAST_BUILD = 1 << 3; + /// Optimize for low memory footprint (both while building and in the output BLAS). + const LOW_MEMORY = 1 << 4; + /// Use `BlasTriangleGeometry::transform_buffer` when building a BLAS (only allowed in + /// BLAS creation) + const USE_TRANSFORM = 1 << 5; + /// Allow retrieval of the vertices of the triangle hit by a ray. + const ALLOW_RAY_HIT_VERTEX_RETURN = 1 << 6; + } +); + +bitflags::bitflags!( + /// Flags for acceleration structure geometries + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + #[cfg_attr(feature = "serde", serde(transparent))] + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + pub struct AccelerationStructureGeometryFlags: u8 { + /// Is OPAQUE (is there no alpha test) recommended as currently in naga there is no + /// candidate intersections yet so currently BLASes without this flag will not have hits. + /// Not enabling this makes the BLAS unable to be interacted with in WGSL. + const OPAQUE = 1 << 0; + /// NO_DUPLICATE_ANY_HIT_INVOCATION, not useful unless using hal with wgpu, ray-tracing + /// pipelines are not supported in wgpu so any-hit shaders do not exist. For when any-hit + /// shaders are implemented (or experienced users who combine this with an underlying library: + /// for any primitive (triangle or AABB) multiple any-hit shaders sometimes may be invoked + /// (especially in AABBs like a sphere), if this flag in present only one hit on a primitive may + /// invoke an any-hit shader. + const NO_DUPLICATE_ANY_HIT_INVOCATION = 1 << 1; + } +); + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +/// What a copy between acceleration structures should do +pub enum AccelerationStructureCopy { + /// Directly duplicate an acceleration structure to another + Clone, + /// Duplicate and compact an acceleration structure + Compact, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +/// What type the data of an acceleration structure is +pub enum AccelerationStructureType { + /// The types of the acceleration structure are triangles + Triangles, + /// The types of the acceleration structure are axis aligned bounding boxes + AABBs, + /// The types of the acceleration structure are instances + Instances, +} + +/// Alignment requirement for transform buffers used in acceleration structure builds +pub const TRANSFORM_BUFFER_ALIGNMENT: crate::BufferAddress = 16; + +/// Alignment requirement for instance buffers used in acceleration structure builds (`build_acceleration_structures_unsafe_tlas`) +pub const INSTANCE_BUFFER_ALIGNMENT: crate::BufferAddress = 16; diff --git a/third_party/rust/wgpu-types/src/render.rs b/third_party/rust/wgpu-types/src/render.rs @@ -0,0 +1,961 @@ +//! Types for configuring render passes and render pipelines (except for vertex attributes). + +use bytemuck::{Pod, Zeroable}; + +#[cfg(any(feature = "serde", test))] +use serde::{Deserialize, Serialize}; + +use crate::{link_to_wgpu_docs, LoadOpDontCare}; + +#[cfg(doc)] +use crate::{Features, TextureFormat}; + +/// Alpha blend factor. +/// +/// Corresponds to [WebGPU `GPUBlendFactor`]( +/// https://gpuweb.github.io/gpuweb/#enumdef-gpublendfactor). Values using `Src1` +/// require [`Features::DUAL_SOURCE_BLENDING`] and can only be used with the first +/// render target. +/// +/// For further details on how the blend factors are applied, see the analogous +/// functionality in OpenGL: <https://www.khronos.org/opengl/wiki/Blending#Blending_Parameters>. +#[repr(C)] +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] +pub enum BlendFactor { + /// 0.0 + Zero = 0, + /// 1.0 + One = 1, + /// S.component + Src = 2, + /// 1.0 - S.component + OneMinusSrc = 3, + /// S.alpha + SrcAlpha = 4, + /// 1.0 - S.alpha + OneMinusSrcAlpha = 5, + /// D.component + Dst = 6, + /// 1.0 - D.component + OneMinusDst = 7, + /// D.alpha + DstAlpha = 8, + /// 1.0 - D.alpha + OneMinusDstAlpha = 9, + /// min(S.alpha, 1.0 - D.alpha) + SrcAlphaSaturated = 10, + /// Constant + 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`]. + #[must_use] + pub fn ref_second_blend_source(&self) -> bool { + match self { + BlendFactor::Src1 + | BlendFactor::OneMinusSrc1 + | BlendFactor::Src1Alpha + | BlendFactor::OneMinusSrc1Alpha => true, + _ => false, + } + } +} + +/// Alpha blend operation. +/// +/// Corresponds to [WebGPU `GPUBlendOperation`]( +/// https://gpuweb.github.io/gpuweb/#enumdef-gpublendoperation). +/// +/// For further details on how the blend operations are applied, see +/// the analogous functionality in OpenGL: <https://www.khronos.org/opengl/wiki/Blending#Blend_Equations>. +#[repr(C)] +#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] +pub enum BlendOperation { + /// Src + Dst + #[default] + Add = 0, + /// Src - Dst + Subtract = 1, + /// Dst - Src + ReverseSubtract = 2, + /// min(Src, Dst) + Min = 3, + /// max(Src, Dst) + Max = 4, +} + +/// Describes a blend component of a [`BlendState`]. +/// +/// Corresponds to [WebGPU `GPUBlendComponent`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gpublendcomponent). +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct BlendComponent { + /// Multiplier for the source, which is produced by the fragment shader. + pub src_factor: BlendFactor, + /// Multiplier for the destination, which is stored in the target. + pub dst_factor: BlendFactor, + /// The binary operation applied to the source and destination, + /// multiplied by their respective factors. + pub operation: BlendOperation, +} + +impl BlendComponent { + /// Default blending state that replaces destination with the source. + pub const REPLACE: Self = Self { + src_factor: BlendFactor::One, + dst_factor: BlendFactor::Zero, + operation: BlendOperation::Add, + }; + + /// Blend state of `(1 * src) + ((1 - src_alpha) * dst)`. + pub const OVER: Self = Self { + src_factor: BlendFactor::One, + dst_factor: BlendFactor::OneMinusSrcAlpha, + operation: BlendOperation::Add, + }; + + /// Returns true if the state relies on the constant color, which is + /// set independently on a render command encoder. + #[must_use] + pub fn uses_constant(&self) -> bool { + match (self.src_factor, self.dst_factor) { + (BlendFactor::Constant, _) + | (BlendFactor::OneMinusConstant, _) + | (_, BlendFactor::Constant) + | (_, BlendFactor::OneMinusConstant) => true, + (_, _) => false, + } + } +} + +impl Default for BlendComponent { + fn default() -> Self { + Self::REPLACE + } +} + +/// Describe the blend state of a render pipeline, +/// within [`ColorTargetState`]. +/// +/// Corresponds to [WebGPU `GPUBlendState`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gpublendstate). +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct BlendState { + /// Color equation. + pub color: BlendComponent, + /// Alpha equation. + pub alpha: BlendComponent, +} + +impl BlendState { + /// Blend mode that does no color blending, just overwrites the output with the contents of the shader. + pub const REPLACE: Self = Self { + color: BlendComponent::REPLACE, + alpha: BlendComponent::REPLACE, + }; + + /// Blend mode that does standard alpha blending with non-premultiplied alpha. + pub const ALPHA_BLENDING: Self = Self { + color: BlendComponent { + src_factor: BlendFactor::SrcAlpha, + dst_factor: BlendFactor::OneMinusSrcAlpha, + operation: BlendOperation::Add, + }, + alpha: BlendComponent::OVER, + }; + + /// Blend mode that does standard alpha blending with premultiplied alpha. + pub const PREMULTIPLIED_ALPHA_BLENDING: Self = Self { + color: BlendComponent::OVER, + alpha: BlendComponent::OVER, + }; +} + +/// Describes the color state of a render pipeline. +/// +/// Corresponds to [WebGPU `GPUColorTargetState`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gpucolortargetstate). +#[repr(C)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct ColorTargetState { + /// The [`TextureFormat`] of the image that this pipeline will render to. Must match the format + /// of the corresponding color attachment in [`CommandEncoder::begin_render_pass`][CEbrp] + /// + #[doc = link_to_wgpu_docs!(["CEbrp"]: "struct.CommandEncoder.html#method.begin_render_pass")] + pub format: crate::TextureFormat, + /// The blending that is used for this pipeline. + #[cfg_attr(feature = "serde", serde(default))] + pub blend: Option<BlendState>, + /// Mask which enables/disables writes to different color/alpha channel. + #[cfg_attr(feature = "serde", serde(default))] + pub write_mask: ColorWrites, +} + +impl From<crate::TextureFormat> for ColorTargetState { + fn from(format: crate::TextureFormat) -> Self { + Self { + format, + blend: None, + write_mask: ColorWrites::ALL, + } + } +} + +/// Color write mask. Disabled color channels will not be written to. +/// +/// Corresponds to [WebGPU `GPUColorWriteFlags`]( +/// https://gpuweb.github.io/gpuweb/#typedefdef-gpucolorwriteflags). +#[repr(transparent)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(transparent))] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct ColorWrites(u32); + +bitflags::bitflags! { + impl ColorWrites: u32 { + /// Enable red channel writes + const RED = 1 << 0; + /// Enable green channel writes + const GREEN = 1 << 1; + /// Enable blue channel writes + const BLUE = 1 << 2; + /// Enable alpha channel writes + const ALPHA = 1 << 3; + /// Enable red, green, and blue channel writes + const COLOR = Self::RED.bits() | Self::GREEN.bits() | Self::BLUE.bits(); + /// Enable writes to all channels. + const ALL = Self::RED.bits() | Self::GREEN.bits() | Self::BLUE.bits() | Self::ALPHA.bits(); + } +} + +impl Default for ColorWrites { + fn default() -> Self { + Self::ALL + } +} + +/// Primitive type the input mesh is composed of. +/// +/// Corresponds to [WebGPU `GPUPrimitiveTopology`]( +/// https://gpuweb.github.io/gpuweb/#enumdef-gpuprimitivetopology). +#[repr(C)] +#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] +pub enum PrimitiveTopology { + /// Vertex data is a list of points. Each vertex is a new point. + PointList = 0, + /// Vertex data is a list of lines. Each pair of vertices composes a new line. + /// + /// Vertices `0 1 2 3` create two lines `0 1` and `2 3` + LineList = 1, + /// Vertex data is a strip of lines. Each set of two adjacent vertices form a line. + /// + /// Vertices `0 1 2 3` create three lines `0 1`, `1 2`, and `2 3`. + LineStrip = 2, + /// Vertex data is a list of triangles. Each set of 3 vertices composes a new triangle. + /// + /// Vertices `0 1 2 3 4 5` create two triangles `0 1 2` and `3 4 5` + #[default] + TriangleList = 3, + /// Vertex data is a triangle strip. Each set of three adjacent vertices form a triangle. + /// + /// Vertices `0 1 2 3 4 5` create four triangles `0 1 2`, `2 1 3`, `2 3 4`, and `4 3 5` + TriangleStrip = 4, +} + +impl PrimitiveTopology { + /// Returns true for strip topologies. + #[must_use] + pub fn is_strip(&self) -> bool { + match *self { + Self::PointList | Self::LineList | Self::TriangleList => false, + Self::LineStrip | Self::TriangleStrip => true, + } + } +} + +/// Vertex winding order which classifies the "front" face of a triangle. +/// +/// Corresponds to [WebGPU `GPUFrontFace`]( +/// https://gpuweb.github.io/gpuweb/#enumdef-gpufrontface). +#[repr(C)] +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] +pub enum FrontFace { + /// Triangles with vertices in counter clockwise order are considered the front face. + /// + /// This is the default with right handed coordinate spaces. + #[default] + Ccw = 0, + /// Triangles with vertices in clockwise order are considered the front face. + /// + /// This is the default with left handed coordinate spaces. + Cw = 1, +} + +/// Face of a vertex. +/// +/// Corresponds to [WebGPU `GPUCullMode`]( +/// https://gpuweb.github.io/gpuweb/#enumdef-gpucullmode), +/// except that the `"none"` value is represented using `Option<Face>` instead. +#[repr(C)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] +pub enum Face { + /// Front face + Front = 0, + /// Back face + Back = 1, +} + +/// Type of drawing mode for polygons +#[repr(C)] +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] +pub enum PolygonMode { + /// Polygons are filled + #[default] + Fill = 0, + /// Polygons are drawn as line segments + Line = 1, + /// Polygons are drawn as points + Point = 2, +} + +/// Describes the state of primitive assembly and rasterization in a render pipeline. +/// +/// Corresponds to [WebGPU `GPUPrimitiveState`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gpuprimitivestate). +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct PrimitiveState { + /// The primitive topology used to interpret vertices. + pub topology: PrimitiveTopology, + /// When drawing strip topologies with indices, this is the required format for the index buffer. + /// This has no effect on non-indexed or non-strip draws. + /// + /// Specifying this value enables primitive restart, allowing individual strips to be separated + /// with the index value `0xFFFF` when using `Uint16`, or `0xFFFFFFFF` when using `Uint32`. + #[cfg_attr(feature = "serde", serde(default))] + pub strip_index_format: Option<IndexFormat>, + /// The face to consider the front for the purpose of culling and stencil operations. + #[cfg_attr(feature = "serde", serde(default))] + pub front_face: FrontFace, + /// The face culling mode. + #[cfg_attr(feature = "serde", serde(default))] + pub cull_mode: Option<Face>, + /// If set to true, the polygon depth is not clipped to 0-1 before rasterization. + /// + /// Enabling this requires [`Features::DEPTH_CLIP_CONTROL`] to be enabled. + #[cfg_attr(feature = "serde", serde(default))] + pub unclipped_depth: bool, + /// Controls the way each polygon is rasterized. Can be either `Fill` (default), `Line` or `Point` + /// + /// Setting this to `Line` requires [`Features::POLYGON_MODE_LINE`] to be enabled. + /// + /// Setting this to `Point` requires [`Features::POLYGON_MODE_POINT`] to be enabled. + #[cfg_attr(feature = "serde", serde(default))] + pub polygon_mode: PolygonMode, + /// If set to true, the primitives are rendered with conservative overestimation. I.e. any rastered pixel touched by it is filled. + /// Only valid for `[PolygonMode::Fill`]! + /// + /// Enabling this requires [`Features::CONSERVATIVE_RASTERIZATION`] to be enabled. + pub conservative: bool, +} + +/// Describes the multi-sampling state of a render pipeline. +/// +/// Corresponds to [WebGPU `GPUMultisampleState`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gpumultisamplestate). +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct MultisampleState { + /// The number of samples calculated per pixel (for MSAA). For non-multisampled textures, + /// this should be `1` + pub count: u32, + /// Bitmask that restricts the samples of a pixel modified by this pipeline. All samples + /// can be enabled using the value `!0` + pub mask: u64, + /// When enabled, produces another sample mask per pixel based on the alpha output value, that + /// is ANDed with the sample mask and the primitive coverage to restrict the set of samples + /// affected by a primitive. + /// + /// The implicit mask produced for alpha of zero is guaranteed to be zero, and for alpha of one + /// is guaranteed to be all 1-s. + pub alpha_to_coverage_enabled: bool, +} + +impl Default for MultisampleState { + fn default() -> Self { + MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + } + } +} + +/// Format of indices used with pipeline. +/// +/// Corresponds to [WebGPU `GPUIndexFormat`]( +/// https://gpuweb.github.io/gpuweb/#enumdef-gpuindexformat). +#[repr(C)] +#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] +pub enum IndexFormat { + /// Indices are 16 bit unsigned integers. + Uint16 = 0, + /// Indices are 32 bit unsigned integers. + #[default] + Uint32 = 1, +} + +impl IndexFormat { + /// Returns the size in bytes of the index format + pub fn byte_size(&self) -> usize { + match self { + IndexFormat::Uint16 => 2, + IndexFormat::Uint32 => 4, + } + } +} + +/// Operation to perform on the stencil value. +/// +/// Corresponds to [WebGPU `GPUStencilOperation`]( +/// https://gpuweb.github.io/gpuweb/#enumdef-gpustenciloperation). +#[repr(C)] +#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] +pub enum StencilOperation { + /// Keep stencil value unchanged. + #[default] + Keep = 0, + /// Set stencil value to zero. + Zero = 1, + /// Replace stencil value with value provided in most recent call to + /// [`RenderPass::set_stencil_reference`][RPssr]. + /// + #[doc = link_to_wgpu_docs!(["RPssr"]: "struct.RenderPass.html#method.set_stencil_reference")] + Replace = 2, + /// Bitwise inverts stencil value. + Invert = 3, + /// Increments stencil value by one, clamping on overflow. + IncrementClamp = 4, + /// Decrements stencil value by one, clamping on underflow. + DecrementClamp = 5, + /// Increments stencil value by one, wrapping on overflow. + IncrementWrap = 6, + /// Decrements stencil value by one, wrapping on underflow. + DecrementWrap = 7, +} + +/// Describes stencil state in a render pipeline. +/// +/// If you are not using stencil state, set this to [`StencilFaceState::IGNORE`]. +/// +/// Corresponds to [WebGPU `GPUStencilFaceState`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gpustencilfacestate). +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct StencilFaceState { + /// Comparison function that determines if the fail_op or pass_op is used on the stencil buffer. + pub compare: CompareFunction, + /// Operation that is performed when stencil test fails. + pub fail_op: StencilOperation, + /// Operation that is performed when depth test fails but stencil test succeeds. + pub depth_fail_op: StencilOperation, + /// Operation that is performed when stencil test success. + pub pass_op: StencilOperation, +} + +impl StencilFaceState { + /// Ignore the stencil state for the face. + pub const IGNORE: Self = StencilFaceState { + compare: CompareFunction::Always, + fail_op: StencilOperation::Keep, + depth_fail_op: StencilOperation::Keep, + pass_op: StencilOperation::Keep, + }; + + /// Returns true if the face state uses the reference value for testing or operation. + #[must_use] + pub fn needs_ref_value(&self) -> bool { + self.compare.needs_ref_value() + || self.fail_op == StencilOperation::Replace + || self.depth_fail_op == StencilOperation::Replace + || self.pass_op == StencilOperation::Replace + } + + /// Returns true if the face state doesn't mutate the target values. + #[must_use] + pub fn is_read_only(&self) -> bool { + self.pass_op == StencilOperation::Keep + && self.depth_fail_op == StencilOperation::Keep + && self.fail_op == StencilOperation::Keep + } +} + +impl Default for StencilFaceState { + fn default() -> Self { + Self::IGNORE + } +} + +/// Comparison function used for depth and stencil operations. +/// +/// Corresponds to [WebGPU `GPUCompareFunction`]( +/// https://gpuweb.github.io/gpuweb/#enumdef-gpucomparefunction). +#[repr(C)] +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] +pub enum CompareFunction { + /// Function never passes + Never = 1, + /// Function passes if new value less than existing value + Less = 2, + /// Function passes if new value is equal to existing value. When using + /// this compare function, make sure to mark your Vertex Shader's `@builtin(position)` + /// output as `@invariant` to prevent artifacting. + Equal = 3, + /// Function passes if new value is less than or equal to existing value + LessEqual = 4, + /// Function passes if new value is greater than existing value + Greater = 5, + /// Function passes if new value is not equal to existing value. When using + /// this compare function, make sure to mark your Vertex Shader's `@builtin(position)` + /// output as `@invariant` to prevent artifacting. + NotEqual = 6, + /// Function passes if new value is greater than or equal to existing value + GreaterEqual = 7, + /// Function always passes + Always = 8, +} + +impl CompareFunction { + /// Returns true if the comparison depends on the reference value. + #[must_use] + pub fn needs_ref_value(self) -> bool { + match self { + Self::Never | Self::Always => false, + _ => true, + } + } +} + +/// State of the stencil operation (fixed-pipeline stage). +/// +/// For use in [`DepthStencilState`]. +/// +/// Corresponds to a portion of [WebGPU `GPUDepthStencilState`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gpudepthstencilstate). +#[repr(C)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct StencilState { + /// Front face mode. + pub front: StencilFaceState, + /// Back face mode. + pub back: StencilFaceState, + /// Stencil values are AND'd with this mask when reading and writing from the stencil buffer. Only low 8 bits are used. + pub read_mask: u32, + /// Stencil values are AND'd with this mask when writing to the stencil buffer. Only low 8 bits are used. + pub write_mask: u32, +} + +impl StencilState { + /// Returns true if the stencil test is enabled. + #[must_use] + pub fn is_enabled(&self) -> bool { + (self.front != StencilFaceState::IGNORE || self.back != StencilFaceState::IGNORE) + && (self.read_mask != 0 || self.write_mask != 0) + } + /// Returns true if the state doesn't mutate the target values. + #[must_use] + pub fn is_read_only(&self, cull_mode: Option<Face>) -> bool { + // The rules are defined in step 7 of the "Device timeline initialization steps" + // subsection of the "Render Pipeline Creation" section of WebGPU + // (link to the section: https://gpuweb.github.io/gpuweb/#render-pipeline-creation) + + if self.write_mask == 0 { + return true; + } + + let front_ro = cull_mode == Some(Face::Front) || self.front.is_read_only(); + let back_ro = cull_mode == Some(Face::Back) || self.back.is_read_only(); + + front_ro && back_ro + } + /// Returns true if the stencil state uses the reference value for testing. + #[must_use] + pub fn needs_ref_value(&self) -> bool { + self.front.needs_ref_value() || self.back.needs_ref_value() + } +} + +/// Describes the biasing setting for the depth target. +/// +/// For use in [`DepthStencilState`]. +/// +/// Corresponds to a portion of [WebGPU `GPUDepthStencilState`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gpudepthstencilstate). +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct DepthBiasState { + /// Constant depth biasing factor, in basic units of the depth format. + pub constant: i32, + /// Slope depth biasing factor. + pub slope_scale: f32, + /// Depth bias clamp value (absolute). + pub clamp: f32, +} + +impl DepthBiasState { + /// Returns true if the depth biasing is enabled. + #[must_use] + pub fn is_enabled(&self) -> bool { + self.constant != 0 || self.slope_scale != 0.0 + } +} + +impl core::hash::Hash for DepthBiasState { + fn hash<H: core::hash::Hasher>(&self, state: &mut H) { + self.constant.hash(state); + self.slope_scale.to_bits().hash(state); + self.clamp.to_bits().hash(state); + } +} + +impl PartialEq for DepthBiasState { + fn eq(&self, other: &Self) -> bool { + (self.constant == other.constant) + && (self.slope_scale.to_bits() == other.slope_scale.to_bits()) + && (self.clamp.to_bits() == other.clamp.to_bits()) + } +} + +impl Eq for DepthBiasState {} + +/// Operation to perform to the output attachment at the start of a render pass. +/// +/// Corresponds to [WebGPU `GPULoadOp`](https://gpuweb.github.io/gpuweb/#enumdef-gpuloadop), +/// plus the corresponding clearValue. +#[repr(u8)] +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] +pub enum LoadOp<V> { + /// Loads the specified value for this attachment into the render pass. + /// + /// On some GPU hardware (primarily mobile), "clear" is significantly cheaper + /// because it avoids loading data from main memory into tile-local memory. + /// + /// On other GPU hardware, there isn’t a significant difference. + /// + /// As a result, it is recommended to use "clear" rather than "load" in cases + /// where the initial value doesn’t matter + /// (e.g. the render target will be cleared using a skybox). + Clear(V) = 0, + /// Loads the existing value for this attachment into the render pass. + Load = 1, + /// The render target has undefined contents at the start of the render pass. + /// This may lead to undefined behavior if you read from the any of the + /// render target pixels without first writing to them. + /// + /// Blending also becomes undefined behavior if the source + /// pixels are undefined. + /// + /// This is the fastest option on all GPUs if you always overwrite all pixels + /// in the render target after this load operation. + /// + /// Backends that don't support `DontCare` internally, will pick a different (unspecified) + /// load op instead. + /// + /// # Safety + /// + /// - All pixels in the render target must be written to before + /// any read or a [`StoreOp::Store`] occurs. + DontCare(#[cfg_attr(feature = "serde", serde(skip))] LoadOpDontCare) = 2, +} + +impl<V> LoadOp<V> { + /// Returns true if variants are same (ignoring clear value) + pub fn eq_variant<T>(&self, other: LoadOp<T>) -> bool { + matches!( + (self, other), + (LoadOp::Clear(_), LoadOp::Clear(_)) + | (LoadOp::Load, LoadOp::Load) + | (LoadOp::DontCare(_), LoadOp::DontCare(_)) + ) + } +} + +impl<V: Default> Default for LoadOp<V> { + fn default() -> Self { + Self::Clear(Default::default()) + } +} + +/// Operation to perform to the output attachment at the end of a render pass. +/// +/// Corresponds to [WebGPU `GPUStoreOp`](https://gpuweb.github.io/gpuweb/#enumdef-gpustoreop). +#[repr(C)] +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] +pub enum StoreOp { + /// Stores the resulting value of the render pass for this attachment. + #[default] + Store = 0, + /// Discards the resulting value of the render pass for this attachment. + /// + /// The attachment will be treated as uninitialized afterwards. + /// (If only either Depth or Stencil texture-aspects is set to `Discard`, + /// the respective other texture-aspect will be preserved.) + /// + /// This can be significantly faster on tile-based render hardware. + /// + /// Prefer this if the attachment is not read by subsequent passes. + Discard = 1, +} + +/// Pair of load and store operations for an attachment aspect. +/// +/// This type is unique to the Rust API of `wgpu`. In the WebGPU specification, +/// separate `loadOp` and `storeOp` fields are used instead. +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Operations<V> { + /// How data should be read through this attachment. + pub load: LoadOp<V>, + /// Whether data will be written to through this attachment. + /// + /// Note that resolve textures (if specified) are always written to, + /// regardless of this setting. + pub store: StoreOp, +} + +impl<V: Default> Default for Operations<V> { + #[inline] + fn default() -> Self { + Self { + load: LoadOp::<V>::default(), + store: StoreOp::default(), + } + } +} + +/// Describes the depth/stencil state in a render pipeline. +/// +/// Corresponds to [WebGPU `GPUDepthStencilState`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gpudepthstencilstate). +#[repr(C)] +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct DepthStencilState { + /// Format of the depth/stencil buffer, must be special depth format. Must match the format + /// of the depth/stencil attachment in [`CommandEncoder::begin_render_pass`][CEbrp]. + /// + #[doc = link_to_wgpu_docs!(["CEbrp"]: "struct.CommandEncoder.html#method.begin_render_pass")] + pub format: crate::TextureFormat, + /// If disabled, depth will not be written to. + pub depth_write_enabled: bool, + /// Comparison function used to compare depth values in the depth test. + pub depth_compare: CompareFunction, + /// Stencil state. + #[cfg_attr(feature = "serde", serde(default))] + pub stencil: StencilState, + /// Depth bias state. + #[cfg_attr(feature = "serde", serde(default))] + pub bias: DepthBiasState, +} + +impl DepthStencilState { + /// Returns true if the depth testing is enabled. + #[must_use] + pub fn is_depth_enabled(&self) -> bool { + self.depth_compare != CompareFunction::Always || self.depth_write_enabled + } + + /// Returns true if the state doesn't mutate the depth buffer. + #[must_use] + pub fn is_depth_read_only(&self) -> bool { + !self.depth_write_enabled + } + + /// Returns true if the state doesn't mutate the stencil. + #[must_use] + pub fn is_stencil_read_only(&self, cull_mode: Option<Face>) -> bool { + self.stencil.is_read_only(cull_mode) + } + + /// Returns true if the state doesn't mutate either depth or stencil of the target. + #[must_use] + pub fn is_read_only(&self, cull_mode: Option<Face>) -> bool { + self.is_depth_read_only() && self.is_stencil_read_only(cull_mode) + } +} + +/// Describes the depth/stencil attachment for render bundles. +/// +/// Corresponds to a portion of [WebGPU `GPURenderBundleEncoderDescriptor`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gpurenderbundleencoderdescriptor). +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct RenderBundleDepthStencil { + /// Format of the attachment. + pub format: crate::TextureFormat, + /// If the depth aspect of the depth stencil attachment is going to be written to. + /// + /// This must match the [`RenderPassDepthStencilAttachment::depth_ops`] of the renderpass this render bundle is executed in. + /// If `depth_ops` is `Some(..)` this must be false. If it is `None` this must be true. + /// + #[doc = link_to_wgpu_docs!(["`RenderPassDepthStencilAttachment::depth_ops`"]: "struct.RenderPassDepthStencilAttachment.html#structfield.depth_ops")] + pub depth_read_only: bool, + + /// If the stencil aspect of the depth stencil attachment is going to be written to. + /// + /// This must match the [`RenderPassDepthStencilAttachment::stencil_ops`] of the renderpass this render bundle is executed in. + /// If `depth_ops` is `Some(..)` this must be false. If it is `None` this must be true. + /// + #[doc = link_to_wgpu_docs!(["`RenderPassDepthStencilAttachment::stencil_ops`"]: "struct.RenderPassDepthStencilAttachment.html#structfield.stencil_ops")] + pub stencil_read_only: bool, +} + +/// Describes a [`RenderBundle`](../wgpu/struct.RenderBundle.html). +/// +/// Corresponds to [WebGPU `GPURenderBundleDescriptor`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gpurenderbundledescriptor). +#[repr(C)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct RenderBundleDescriptor<L> { + /// Debug label of the render bundle encoder. This will show up in graphics debuggers for easy identification. + pub label: L, +} + +impl<L> RenderBundleDescriptor<L> { + /// Takes a closure and maps the label of the render bundle descriptor into another. + #[must_use] + pub fn map_label<K>(&self, fun: impl FnOnce(&L) -> K) -> RenderBundleDescriptor<K> { + RenderBundleDescriptor { + label: fun(&self.label), + } + } +} + +impl<T> Default for RenderBundleDescriptor<Option<T>> { + fn default() -> Self { + Self { label: None } + } +} + +/// Argument buffer layout for `draw_indirect` commands. +#[repr(C)] +#[derive(Copy, Clone, Debug, Default, Pod, Zeroable)] +pub struct DrawIndirectArgs { + /// The number of vertices to draw. + pub vertex_count: u32, + /// The number of instances to draw. + pub instance_count: u32, + /// The Index of the first vertex to draw. + pub first_vertex: u32, + /// The instance ID of the first instance to draw. + /// + /// Has to be 0, unless [`Features::INDIRECT_FIRST_INSTANCE`](crate::Features::INDIRECT_FIRST_INSTANCE) is enabled. + pub first_instance: u32, +} + +impl DrawIndirectArgs { + /// Returns the bytes representation of the struct, ready to be written in a buffer. + #[must_use] + pub fn as_bytes(&self) -> &[u8] { + bytemuck::bytes_of(self) + } +} + +/// Argument buffer layout for `draw_indexed_indirect` commands. +#[repr(C)] +#[derive(Copy, Clone, Debug, Default, Pod, Zeroable)] +pub struct DrawIndexedIndirectArgs { + /// The number of indices to draw. + pub index_count: u32, + /// The number of instances to draw. + pub instance_count: u32, + /// The first index within the index buffer. + pub first_index: u32, + /// The value added to the vertex index before indexing into the vertex buffer. + pub base_vertex: i32, + /// The instance ID of the first instance to draw. + /// + /// Has to be 0, unless [`Features::INDIRECT_FIRST_INSTANCE`](crate::Features::INDIRECT_FIRST_INSTANCE) is enabled. + pub first_instance: u32, +} + +impl DrawIndexedIndirectArgs { + /// Returns the bytes representation of the struct, ready to be written in a buffer. + #[must_use] + pub fn as_bytes(&self) -> &[u8] { + bytemuck::bytes_of(self) + } +} + +/// Argument buffer layout for `dispatch_indirect` commands. +#[repr(C)] +#[derive(Copy, Clone, Debug, Default, Pod, Zeroable)] +pub struct DispatchIndirectArgs { + /// The number of work groups in X dimension. + pub x: u32, + /// The number of work groups in Y dimension. + pub y: u32, + /// The number of work groups in Z dimension. + pub z: u32, +} + +impl DispatchIndirectArgs { + /// Returns the bytes representation of the struct, ready to be written into a buffer. + #[must_use] + pub fn as_bytes(&self) -> &[u8] { + bytemuck::bytes_of(self) + } +} diff --git a/third_party/rust/wgpu-types/src/send_sync.rs b/third_party/rust/wgpu-types/src/send_sync.rs @@ -0,0 +1,67 @@ +pub trait WasmNotSendSync: WasmNotSend + WasmNotSync {} +impl<T: WasmNotSend + WasmNotSync> WasmNotSendSync for T {} +#[cfg(any( + not(target_arch = "wasm32"), + all( + feature = "fragile-send-sync-non-atomic-wasm", + not(target_feature = "atomics") + ) +))] +pub trait WasmNotSend: Send {} +#[cfg(any( + not(target_arch = "wasm32"), + all( + feature = "fragile-send-sync-non-atomic-wasm", + not(target_feature = "atomics") + ) +))] +impl<T: Send> WasmNotSend for T {} +#[cfg(not(any( + not(target_arch = "wasm32"), + all( + feature = "fragile-send-sync-non-atomic-wasm", + not(target_feature = "atomics") + ) +)))] +pub trait WasmNotSend {} +#[cfg(not(any( + not(target_arch = "wasm32"), + all( + feature = "fragile-send-sync-non-atomic-wasm", + not(target_feature = "atomics") + ) +)))] +impl<T> WasmNotSend for T {} + +#[cfg(any( + not(target_arch = "wasm32"), + all( + feature = "fragile-send-sync-non-atomic-wasm", + not(target_feature = "atomics") + ) +))] +pub trait WasmNotSync: Sync {} +#[cfg(any( + not(target_arch = "wasm32"), + all( + feature = "fragile-send-sync-non-atomic-wasm", + not(target_feature = "atomics") + ) +))] +impl<T: Sync> WasmNotSync for T {} +#[cfg(not(any( + not(target_arch = "wasm32"), + all( + feature = "fragile-send-sync-non-atomic-wasm", + not(target_feature = "atomics") + ) +)))] +pub trait WasmNotSync {} +#[cfg(not(any( + not(target_arch = "wasm32"), + all( + feature = "fragile-send-sync-non-atomic-wasm", + not(target_feature = "atomics") + ) +)))] +impl<T> WasmNotSync for T {} diff --git a/third_party/rust/wgpu-types/src/shader.rs b/third_party/rust/wgpu-types/src/shader.rs @@ -0,0 +1,184 @@ +use alloc::{borrow::Cow, string::String}; + +/// Describes how shader bound checks should be performed. +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ShaderRuntimeChecks { + /// Enforce bounds checks in shaders, even if the underlying driver doesn't + /// support doing so natively. + /// + /// When this is `true`, `wgpu` promises that shaders can only read or + /// write the accessible region of a bindgroup's buffer bindings. If + /// the underlying graphics platform cannot implement these bounds checks + /// itself, `wgpu` will inject bounds checks before presenting the + /// shader to the platform. + /// + /// When this is `false`, `wgpu` only enforces such bounds checks if the + /// underlying platform provides a way to do so itself. `wgpu` does not + /// itself add any bounds checks to generated shader code. + /// + /// Note that `wgpu` users may try to initialize only those portions of + /// buffers that they anticipate might be read from. Passing `false` here + /// may allow shaders to see wider regions of the buffers than expected, + /// making such deferred initialization visible to the application. + pub bounds_checks: bool, + /// + /// If false, the caller MUST ensure that all passed shaders do not contain any infinite loops. + /// + /// If it does, backend compilers MAY treat such a loop as unreachable code and draw + /// conclusions about other safety-critical code paths. This option SHOULD NOT be disabled + /// when running untrusted code. + pub force_loop_bounding: bool, + /// If false, the caller **MUST** ensure that in all passed shaders every function operating + /// on a ray query must obey these rules (functions using wgsl naming) + /// - `rayQueryInitialize` must have called before `rayQueryProceed` + /// - `rayQueryProceed` must have been called, returned true and have hit an AABB before + /// `rayQueryGenerateIntersection` is called + /// - `rayQueryProceed` must have been called, returned true and have hit a triangle before + /// `rayQueryConfirmIntersection` is called + /// - `rayQueryProceed` must have been called and have returned true before `rayQueryTerminate`, + /// `getCandidateHitVertexPositions` or `rayQueryGetCandidateIntersection` is called + /// - `rayQueryProceed` must have been called and have returned false before `rayQueryGetCommittedIntersection` + /// or `getCommittedHitVertexPositions` are called + /// + /// It is the aim that these cases will not cause UB if this is set to true, but currently this will still happen on DX12 and Metal. + pub ray_query_initialization_tracking: bool, +} + +impl ShaderRuntimeChecks { + /// Creates a new configuration where the shader is fully checked. + #[must_use] + pub const fn checked() -> Self { + unsafe { Self::all(true) } + } + + /// Creates a new configuration where none of the checks are performed. + /// + /// # Safety + /// + /// See the documentation for the `set_*` methods for the safety requirements + /// of each sub-configuration. + #[must_use] + pub const fn unchecked() -> Self { + unsafe { Self::all(false) } + } + + /// Creates a new configuration where all checks are enabled or disabled. To safely + /// create a configuration with all checks enabled, use [`ShaderRuntimeChecks::checked`]. + /// + /// # Safety + /// + /// See the documentation for the `set_*` methods for the safety requirements + /// of each sub-configuration. + #[must_use] + pub const unsafe fn all(all_checks: bool) -> Self { + Self { + bounds_checks: all_checks, + force_loop_bounding: all_checks, + ray_query_initialization_tracking: all_checks, + } + } +} + +impl Default for ShaderRuntimeChecks { + fn default() -> Self { + Self::checked() + } +} + +/// Descriptor for a shader module given by any of several sources. +/// These shaders are passed through directly to the underlying api. +/// At least one shader type that may be used by the backend must be `Some` or a panic is raised. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct CreateShaderModuleDescriptorPassthrough<'a, L> { + /// Entrypoint. Unused for Spir-V. + pub entry_point: String, + /// Debug label of the shader module. This will show up in graphics debuggers for easy identification. + pub label: L, + /// Number of workgroups in each dimension x, y and z. Unused for Spir-V. + pub num_workgroups: (u32, u32, u32), + /// Runtime checks that should be enabled. + pub runtime_checks: ShaderRuntimeChecks, + + /// Binary SPIR-V data, in 4-byte words. + pub spirv: Option<Cow<'a, [u32]>>, + /// Shader DXIL source. + pub dxil: Option<Cow<'a, [u8]>>, + /// Shader MSL source. + pub msl: Option<Cow<'a, str>>, + /// Shader HLSL source. + pub hlsl: Option<Cow<'a, str>>, + /// Shader GLSL source (currently unused). + pub glsl: Option<Cow<'a, str>>, + /// Shader WGSL source. + pub wgsl: Option<Cow<'a, str>>, +} + +// This is so people don't have to fill in fields they don't use, like num_workgroups, +// entry_point, or other shader languages they didn't compile for +impl<'a, L: Default> Default for CreateShaderModuleDescriptorPassthrough<'a, L> { + fn default() -> Self { + Self { + entry_point: "".into(), + label: Default::default(), + num_workgroups: (0, 0, 0), + runtime_checks: ShaderRuntimeChecks::unchecked(), + spirv: None, + dxil: None, + msl: None, + hlsl: None, + glsl: None, + wgsl: None, + } + } +} + +impl<'a, L> CreateShaderModuleDescriptorPassthrough<'a, L> { + /// Takes a closure and maps the label of the shader module descriptor into another. + pub fn map_label<K>( + &self, + fun: impl FnOnce(&L) -> K, + ) -> CreateShaderModuleDescriptorPassthrough<'a, K> { + CreateShaderModuleDescriptorPassthrough { + entry_point: self.entry_point.clone(), + label: fun(&self.label), + num_workgroups: self.num_workgroups, + runtime_checks: self.runtime_checks, + spirv: self.spirv.clone(), + dxil: self.dxil.clone(), + msl: self.msl.clone(), + hlsl: self.hlsl.clone(), + glsl: self.glsl.clone(), + wgsl: self.wgsl.clone(), + } + } + + #[cfg(feature = "trace")] + /// Returns the source data for tracing purpose. + pub fn trace_data(&self) -> &[u8] { + if let Some(spirv) = &self.spirv { + bytemuck::cast_slice(spirv) + } else if let Some(msl) = &self.msl { + msl.as_bytes() + } else if let Some(dxil) = &self.dxil { + dxil + } else { + panic!("No binary data provided to `ShaderModuleDescriptorGeneric`") + } + } + + #[cfg(feature = "trace")] + /// Returns the binary file extension for tracing purpose. + pub fn trace_binary_ext(&self) -> &'static str { + if self.spirv.is_some() { + "spv" + } else if self.msl.is_some() { + "msl" + } else if self.dxil.is_some() { + "dxil" + } else { + panic!("No binary data provided to `ShaderModuleDescriptorGeneric`") + } + } +} diff --git a/third_party/rust/wgpu-types/src/surface.rs b/third_party/rust/wgpu-types/src/surface.rs @@ -0,0 +1,318 @@ +use alloc::{vec, vec::Vec}; + +use crate::{link_to_wgpu_docs, link_to_wgpu_item, TextureFormat, TextureUsages}; + +#[cfg(any(feature = "serde", test))] +use serde::{Deserialize, Serialize}; + +/// Timing and queueing with which frames are actually displayed to the user. +/// +/// Use this as part of a [`SurfaceConfiguration`] to control the behavior of +/// [`SurfaceTexture::present()`]. +/// +/// Some modes are only supported by some backends. +/// You can use one of the `Auto*` modes, [`Fifo`](Self::Fifo), +/// or choose one of the supported modes from [`SurfaceCapabilities::present_modes`]. +/// +#[doc = link_to_wgpu_docs!(["presented"]: "struct.SurfaceTexture.html#method.present")] +#[doc = link_to_wgpu_docs!(["`SurfaceTexture::present()`"]: "struct.SurfaceTexture.html#method.present")] +#[repr(C)] +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum PresentMode { + /// Chooses the first supported mode out of: + /// + /// 1. [`FifoRelaxed`](Self::FifoRelaxed) + /// 2. [`Fifo`](Self::Fifo) + /// + /// Because of the fallback behavior, this is supported everywhere. + AutoVsync = 0, + + /// Chooses the first supported mode out of: + /// + /// 1. [`Immediate`](Self::Immediate) + /// 2. [`Mailbox`](Self::Mailbox) + /// 3. [`Fifo`](Self::Fifo) + /// + /// Because of the fallback behavior, this is supported everywhere. + AutoNoVsync = 1, + + /// Presentation frames are kept in a First-In-First-Out queue approximately 3 frames + /// long. Every vertical blanking period, the presentation engine will pop a frame + /// off the queue to display. If there is no frame to display, it will present the same + /// frame again until the next vblank. + /// + /// When a present command is executed on the GPU, the presented image is added on the queue. + /// + /// Calls to [`Surface::get_current_texture()`] will block until there is a spot in the queue. + /// + /// * **Tearing:** No tearing will be observed. + /// * **Supported on**: All platforms. + /// * **Also known as**: "Vsync On" + /// + /// This is the [default](Self::default) value for `PresentMode`. + /// If you don't know what mode to choose, choose this mode. + /// + #[doc = link_to_wgpu_docs!(["`Surface::get_current_texture()`"]: "struct.Surface.html#method.get_current_texture")] + #[default] + Fifo = 2, + + /// Presentation frames are kept in a First-In-First-Out queue approximately 3 frames + /// long. Every vertical blanking period, the presentation engine will pop a frame + /// off the queue to display. If there is no frame to display, it will present the + /// same frame until there is a frame in the queue. The moment there is a frame in the + /// queue, it will immediately pop the frame off the queue. + /// + /// When a present command is executed on the GPU, the presented image is added on the queue. + /// + /// Calls to [`Surface::get_current_texture()`] will block until there is a spot in the queue. + /// + /// * **Tearing**: + /// Tearing will be observed if frames last more than one vblank as the front buffer. + /// * **Supported on**: AMD on Vulkan. + /// * **Also known as**: "Adaptive Vsync" + /// + #[doc = link_to_wgpu_docs!(["`Surface::get_current_texture()`"]: "struct.Surface.html#method.get_current_texture")] + FifoRelaxed = 3, + + /// Presentation frames are not queued at all. The moment a present command + /// is executed on the GPU, the presented image is swapped onto the front buffer + /// immediately. + /// + /// * **Tearing**: Tearing can be observed. + /// * **Supported on**: Most platforms except older DX12 and Wayland. + /// * **Also known as**: "Vsync Off" + Immediate = 4, + + /// Presentation frames are kept in a single-frame queue. Every vertical blanking period, + /// the presentation engine will pop a frame from the queue. If there is no frame to display, + /// it will present the same frame again until the next vblank. + /// + /// When a present command is executed on the GPU, the frame will be put into the queue. + /// If there was already a frame in the queue, the new frame will _replace_ the old frame + /// on the queue. + /// + /// * **Tearing**: No tearing will be observed. + /// * **Supported on**: DX12 on Windows 10, NVidia on Vulkan and Wayland on Vulkan. + /// * **Also known as**: "Fast Vsync" + Mailbox = 5, +} + +/// Specifies how the alpha channel of the textures should be handled during +/// compositing. +#[repr(C)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))] +pub enum CompositeAlphaMode { + /// Chooses either `Opaque` or `Inherit` automatically,depending on the + /// `alpha_mode` that the current surface can support. + #[default] + Auto = 0, + /// The alpha channel, if it exists, of the textures is ignored in the + /// compositing process. Instead, the textures is treated as if it has a + /// constant alpha of 1.0. + Opaque = 1, + /// The alpha channel, if it exists, of the textures is respected in the + /// compositing process. The non-alpha channels of the textures are + /// expected to already be multiplied by the alpha channel by the + /// application. + PreMultiplied = 2, + /// The alpha channel, if it exists, of the textures is respected in the + /// compositing process. The non-alpha channels of the textures are not + /// expected to already be multiplied by the alpha channel by the + /// application; instead, the compositor will multiply the non-alpha + /// channels of the texture by the alpha channel during compositing. + PostMultiplied = 3, + /// The alpha channel, if it exists, of the textures is unknown for processing + /// during compositing. Instead, the application is responsible for setting + /// the composite alpha blending mode using native WSI command. If not set, + /// then a platform-specific default will be used. + Inherit = 4, +} + +/// Defines the capabilities of a given surface and adapter. +#[derive(Debug)] +pub struct SurfaceCapabilities { + /// List of supported formats to use with the given adapter. The first format in the vector is preferred. + /// + /// Returns an empty vector if the surface is incompatible with the adapter. + pub formats: Vec<TextureFormat>, + /// List of supported presentation modes to use with the given adapter. + /// + /// Returns an empty vector if the surface is incompatible with the adapter. + pub present_modes: Vec<PresentMode>, + /// List of supported alpha modes to use with the given adapter. + /// + /// Will return at least one element, [`CompositeAlphaMode::Opaque`] or [`CompositeAlphaMode::Inherit`]. + pub alpha_modes: Vec<CompositeAlphaMode>, + /// Bitflag of supported texture usages for the surface to use with the given adapter. + /// + /// The usage [`TextureUsages::RENDER_ATTACHMENT`] is guaranteed. + pub usages: TextureUsages, +} + +impl Default for SurfaceCapabilities { + fn default() -> Self { + Self { + formats: Vec::new(), + present_modes: Vec::new(), + alpha_modes: vec![CompositeAlphaMode::Opaque], + usages: TextureUsages::RENDER_ATTACHMENT, + } + } +} + +/// Configures a [`Surface`] for presentation. +/// +#[doc = link_to_wgpu_item!(struct Surface)] +#[repr(C)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct SurfaceConfiguration<V> { + /// The usage of the swap chain. The only usage guaranteed to be supported is [`TextureUsages::RENDER_ATTACHMENT`]. + pub usage: TextureUsages, + /// The texture format of the swap chain. The only formats that are guaranteed are + /// [`TextureFormat::Bgra8Unorm`] and [`TextureFormat::Bgra8UnormSrgb`]. + pub format: TextureFormat, + /// Width of the swap chain. Must be the same size as the surface, and nonzero. + /// + /// If this is not the same size as the underlying surface (e.g. if it is + /// set once, and the window is later resized), the behaviour is defined + /// but platform-specific, and may change in the future (currently macOS + /// scales the surface, other platforms may do something else). + pub width: u32, + /// Height of the swap chain. Must be the same size as the surface, and nonzero. + /// + /// If this is not the same size as the underlying surface (e.g. if it is + /// set once, and the window is later resized), the behaviour is defined + /// but platform-specific, and may change in the future (currently macOS + /// scales the surface, other platforms may do something else). + pub height: u32, + /// Presentation mode of the swap chain. Fifo is the only mode guaranteed to be supported. + /// `FifoRelaxed`, `Immediate`, and `Mailbox` will crash if unsupported, while `AutoVsync` and + /// `AutoNoVsync` will gracefully do a designed sets of fallbacks if their primary modes are + /// unsupported. + pub present_mode: PresentMode, + /// Desired maximum number of monitor refreshes between a [`Surface::get_current_texture`] call and the + /// texture being presented to the screen. This is sometimes called "Frames in Flight". + /// + /// Defaults to `2` when created via [`Surface::get_default_config`] as this is a reasonable default. + /// + /// This is ultimately a hint to the backend implementation and will always be clamped + /// to the supported range. + /// + /// Typical values are `1` to `3`, but higher values are valid, though likely to be clamped. + /// * Choose `1` to minimize latency above all else. This only gives a single monitor refresh for all of + /// the CPU and GPU work to complete. ⚠️ As a result of these short swapchains, the CPU and GPU + /// cannot run in parallel, prioritizing latency over throughput. For applications like GUIs doing + /// a small amount of GPU work each frame that need low latency, this is a reasonable choice. + /// * Choose `2` for a balance between latency and throughput. The CPU and GPU both can each use + /// a full monitor refresh to do their computations. This is a reasonable default for most applications. + /// * Choose `3` or higher to maximize throughput, sacrificing latency when the the CPU and GPU + /// are using less than a full monitor refresh each. For applications that use CPU-side pipelining + /// of frames this may be a reasonable choice. ⚠️ On 60hz displays the latency can be very noticeable. + /// + /// This maps to the backend in the following ways: + /// - Vulkan: Number of frames in the swapchain is `desired_maximum_frame_latency + 1`, + /// clamped to the supported range. + /// - DX12: Calls [`IDXGISwapChain2::SetMaximumFrameLatency(desired_maximum_frame_latency)`][SMFL]. + /// - Metal: Sets the `maximumDrawableCount` of the underlying `CAMetalLayer` to + /// `desired_maximum_frame_latency + 1`, clamped to the supported range. + /// - OpenGL: Ignored + /// + /// It also has various subtle interactions with various present modes and APIs. + /// - DX12 + Mailbox: Limits framerate to `desired_maximum_frame_latency * Monitor Hz` fps. + /// - Vulkan/Metal + Mailbox: If this is set to `2`, limits framerate to `2 * Monitor Hz` fps. `3` or higher is unlimited. + /// + #[doc = link_to_wgpu_docs!(["`Surface::get_current_texture`"]: "struct.Surface.html#method.get_current_texture")] + #[doc = link_to_wgpu_docs!(["`Surface::get_default_config`"]: "struct.Surface.html#method.get_default_config")] + /// [SMFL]: https://learn.microsoft.com/en-us/windows/win32/api/dxgi1_3/nf-dxgi1_3-idxgiswapchain2-setmaximumframelatency + pub desired_maximum_frame_latency: u32, + /// Specifies how the alpha channel of the textures should be handled during compositing. + pub alpha_mode: CompositeAlphaMode, + /// Specifies what view formats will be allowed when calling `Texture::create_view` on the texture returned by `Surface::get_current_texture`. + /// + /// View formats of the same format as the texture are always allowed. + /// + /// Note: currently, only the srgb-ness is allowed to change. (ex: `Rgba8Unorm` texture + `Rgba8UnormSrgb` view) + pub view_formats: V, +} + +impl<V: Clone> SurfaceConfiguration<V> { + /// Map `view_formats` of the texture descriptor into another. + pub fn map_view_formats<M>(&self, fun: impl FnOnce(V) -> M) -> SurfaceConfiguration<M> { + SurfaceConfiguration { + usage: self.usage, + format: self.format, + width: self.width, + height: self.height, + present_mode: self.present_mode, + desired_maximum_frame_latency: self.desired_maximum_frame_latency, + alpha_mode: self.alpha_mode, + view_formats: fun(self.view_formats.clone()), + } + } +} + +/// Status of the received surface image. +#[repr(C)] +#[derive(Debug)] +pub enum SurfaceStatus { + /// No issues. + Good, + /// The swap chain is operational, but it does no longer perfectly + /// match the surface. A re-configuration is needed. + Suboptimal, + /// Unable to get the next frame, timed out. + Timeout, + /// The surface under the swap chain has changed. + Outdated, + /// The surface under the swap chain is lost. + Lost, + /// The surface status is not known since `Surface::get_current_texture` previously failed. + Unknown, +} + +/// Nanosecond timestamp used by the presentation engine. +/// +/// The specific clock depends on the window system integration (WSI) API used. +/// +/// <table> +/// <tr> +/// <td>WSI</td> +/// <td>Clock</td> +/// </tr> +/// <tr> +/// <td>IDXGISwapchain</td> +/// <td><a href="https://docs.microsoft.com/en-us/windows/win32/api/profileapi/nf-profileapi-queryperformancecounter">QueryPerformanceCounter</a></td> +/// </tr> +/// <tr> +/// <td>IPresentationManager</td> +/// <td><a href="https://docs.microsoft.com/en-us/windows/win32/api/realtimeapiset/nf-realtimeapiset-queryinterrupttimeprecise">QueryInterruptTimePrecise</a></td> +/// </tr> +/// <tr> +/// <td>CAMetalLayer</td> +/// <td><a href="https://developer.apple.com/documentation/kernel/1462446-mach_absolute_time">mach_absolute_time</a></td> +/// </tr> +/// <tr> +/// <td>VK_GOOGLE_display_timing</td> +/// <td><a href="https://linux.die.net/man/3/clock_gettime">clock_gettime(CLOCK_MONOTONIC)</a></td> +/// </tr> +/// </table> +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct PresentationTimestamp( + /// Timestamp in nanoseconds. + pub u128, +); + +impl PresentationTimestamp { + /// A timestamp that is invalid due to the platform not having a timestamp system. + pub const INVALID_TIMESTAMP: Self = Self(u128::MAX); + + /// Returns true if this timestamp is the invalid timestamp. + #[must_use] + pub fn is_invalid(self) -> bool { + self == Self::INVALID_TIMESTAMP + } +} diff --git a/third_party/rust/wgpu-types/src/texture.rs b/third_party/rust/wgpu-types/src/texture.rs @@ -0,0 +1,1098 @@ +use core::ops::Range; + +use crate::{link_to_wgpu_docs, link_to_wgpu_item, Extent3d, Origin3d}; + +#[cfg(any(feature = "serde", test))] +use serde::{Deserialize, Serialize}; + +#[cfg(doc)] +use crate::{BindingType, Features}; + +mod external_image; +mod external_texture; +mod format; + +pub use external_image::*; +pub use external_texture::*; +pub use format::*; + +/// Dimensionality of a texture. +/// +/// Corresponds to [WebGPU `GPUTextureDimension`]( +/// https://gpuweb.github.io/gpuweb/#enumdef-gputexturedimension). +#[repr(C)] +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum TextureDimension { + /// 1D texture + #[cfg_attr(feature = "serde", serde(rename = "1d"))] + D1, + /// 2D texture + #[cfg_attr(feature = "serde", serde(rename = "2d"))] + D2, + /// 3D texture + #[cfg_attr(feature = "serde", serde(rename = "3d"))] + D3, +} + +/// Order in which texture data is laid out in memory. +#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Hash)] +pub enum TextureDataOrder { + /// The texture is laid out densely in memory as: + /// + /// ```text + /// Layer0Mip0 Layer0Mip1 Layer0Mip2 + /// Layer1Mip0 Layer1Mip1 Layer1Mip2 + /// Layer2Mip0 Layer2Mip1 Layer2Mip2 + /// ```` + /// + /// This is the layout used by dds files. + #[default] + LayerMajor, + /// The texture is laid out densely in memory as: + /// + /// ```text + /// Layer0Mip0 Layer1Mip0 Layer2Mip0 + /// Layer0Mip1 Layer1Mip1 Layer2Mip1 + /// Layer0Mip2 Layer1Mip2 Layer2Mip2 + /// ``` + /// + /// This is the layout used by ktx and ktx2 files. + MipMajor, +} + +/// Dimensions of a particular texture view. +/// +/// Corresponds to [WebGPU `GPUTextureViewDimension`]( +/// https://gpuweb.github.io/gpuweb/#enumdef-gputextureviewdimension). +#[repr(C)] +#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum TextureViewDimension { + /// A one dimensional texture. `texture_1d` in WGSL and `texture1D` in GLSL. + #[cfg_attr(feature = "serde", serde(rename = "1d"))] + D1, + /// A two dimensional texture. `texture_2d` in WGSL and `texture2D` in GLSL. + #[cfg_attr(feature = "serde", serde(rename = "2d"))] + #[default] + D2, + /// A two dimensional array texture. `texture_2d_array` in WGSL and `texture2DArray` in GLSL. + #[cfg_attr(feature = "serde", serde(rename = "2d-array"))] + D2Array, + /// A cubemap texture. `texture_cube` in WGSL and `textureCube` in GLSL. + #[cfg_attr(feature = "serde", serde(rename = "cube"))] + Cube, + /// A cubemap array texture. `texture_cube_array` in WGSL and `textureCubeArray` in GLSL. + #[cfg_attr(feature = "serde", serde(rename = "cube-array"))] + CubeArray, + /// A three dimensional texture. `texture_3d` in WGSL and `texture3D` in GLSL. + #[cfg_attr(feature = "serde", serde(rename = "3d"))] + D3, +} + +impl TextureViewDimension { + /// Get the texture dimension required of this texture view dimension. + #[must_use] + pub fn compatible_texture_dimension(self) -> TextureDimension { + match self { + Self::D1 => TextureDimension::D1, + Self::D2 | Self::D2Array | Self::Cube | Self::CubeArray => TextureDimension::D2, + Self::D3 => TextureDimension::D3, + } + } +} + +/// Selects a subset of the data a [`Texture`] holds. +/// +/// Used in [texture views](TextureViewDescriptor) and +/// [texture copy operations](TexelCopyTextureInfo). +/// +/// Corresponds to [WebGPU `GPUTextureAspect`]( +/// https://gpuweb.github.io/gpuweb/#enumdef-gputextureaspect). +/// +#[doc = link_to_wgpu_item!(struct Texture)] +#[repr(C)] +#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] +pub enum TextureAspect { + /// Depth, Stencil, and Color. + #[default] + All, + /// Stencil. + StencilOnly, + /// Depth. + DepthOnly, + /// Plane 0. + Plane0, + /// Plane 1. + Plane1, + /// Plane 2. + Plane2, +} + +impl TextureAspect { + /// Returns the texture aspect for a given plane. + #[must_use] + pub fn from_plane(plane: u32) -> Option<Self> { + Some(match plane { + 0 => Self::Plane0, + 1 => Self::Plane1, + 2 => Self::Plane2, + _ => return None, + }) + } + + /// Returns the plane for a given texture aspect. + #[must_use] + pub fn to_plane(&self) -> Option<u32> { + match self { + TextureAspect::Plane0 => Some(0), + TextureAspect::Plane1 => Some(1), + TextureAspect::Plane2 => Some(2), + _ => None, + } + } +} + +bitflags::bitflags! { + /// Different ways that you can use a texture. + /// + /// The usages determine what kind of memory the texture is allocated from and what + /// actions the texture can partake in. + /// + /// Corresponds to [WebGPU `GPUTextureUsageFlags`]( + /// https://gpuweb.github.io/gpuweb/#typedefdef-gputextureusageflags). + #[repr(transparent)] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + #[cfg_attr(feature = "serde", serde(transparent))] + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] + pub struct TextureUsages: u32 { + // + // ---- Start numbering at 1 << 0 ---- + // + // WebGPU features: + // + /// Allows a texture to be the source in a [`CommandEncoder::copy_texture_to_buffer`] or + /// [`CommandEncoder::copy_texture_to_texture`] operation. + const COPY_SRC = 1 << 0; + /// Allows a texture to be the destination in a [`CommandEncoder::copy_buffer_to_texture`], + /// [`CommandEncoder::copy_texture_to_texture`], or [`Queue::write_texture`] operation. + const COPY_DST = 1 << 1; + /// Allows a texture to be a [`BindingType::Texture`] in a bind group. + const TEXTURE_BINDING = 1 << 2; + /// Allows a texture to be a [`BindingType::StorageTexture`] in a bind group. + const STORAGE_BINDING = 1 << 3; + /// Allows a texture to be an output attachment of a render pass. + /// + /// Consider adding [`TextureUsages::TRANSIENT`] if the contents are not reused. + const RENDER_ATTACHMENT = 1 << 4; + + // + // ---- Restart Numbering for Native Features --- + // + // Native Features: + // + /// Allows a texture to be used with image atomics. Requires [`Features::TEXTURE_ATOMIC`]. + const STORAGE_ATOMIC = 1 << 16; + /// Specifies the contents of this texture will not be used in another pass to potentially reduce memory usage and bandwidth. + /// + /// No-op on platforms on platforms that do not benefit from transient textures. + /// Generally mobile and Apple chips care about this. + /// + /// Incompatible with ALL other usages except [`TextureUsages::RENDER_ATTACHMENT`] and requires it. + /// + /// Requires [`StoreOp::Discard`]. + const TRANSIENT = 1 << 17; + } +} + +bitflags::bitflags! { + /// Similar to `TextureUsages`, but used only for `CommandEncoder::transition_resources`. + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + #[cfg_attr(feature = "serde", serde(transparent))] + pub struct TextureUses: u16 { + /// The texture is in unknown state. + const UNINITIALIZED = 1 << 0; + /// Ready to present image to the surface. + const PRESENT = 1 << 1; + /// The source of a hardware copy. + /// cbindgen:ignore + const COPY_SRC = 1 << 2; + /// The destination of a hardware copy. + /// cbindgen:ignore + const COPY_DST = 1 << 3; + /// Read-only sampled or fetched resource. + const RESOURCE = 1 << 4; + /// The color target of a renderpass. + const COLOR_TARGET = 1 << 5; + /// Read-only depth stencil usage. + const DEPTH_STENCIL_READ = 1 << 6; + /// Read-write depth stencil usage + const DEPTH_STENCIL_WRITE = 1 << 7; + /// Read-only storage texture usage. Corresponds to a UAV in d3d, so is exclusive, despite being read only. + /// cbindgen:ignore + const STORAGE_READ_ONLY = 1 << 8; + /// Write-only storage texture usage. + /// cbindgen:ignore + const STORAGE_WRITE_ONLY = 1 << 9; + /// Read-write storage texture usage. + /// cbindgen:ignore + const STORAGE_READ_WRITE = 1 << 10; + /// Image atomic enabled storage. + /// cbindgen:ignore + const STORAGE_ATOMIC = 1 << 11; + /// Transient texture that may not have any backing memory. Not a resource state stored in the trackers, only used for passing down usages to create_texture. + const TRANSIENT = 1 << 12; + /// The combination of states that a texture may be in _at the same time_. + /// cbindgen:ignore + const INCLUSIVE = Self::COPY_SRC.bits() | Self::RESOURCE.bits() | Self::DEPTH_STENCIL_READ.bits() | Self::STORAGE_READ_ONLY.bits(); + /// The combination of states that a texture must exclusively be in. + /// cbindgen:ignore + const EXCLUSIVE = Self::COPY_DST.bits() | Self::COLOR_TARGET.bits() | Self::DEPTH_STENCIL_WRITE.bits() | Self::STORAGE_WRITE_ONLY.bits() | Self::STORAGE_READ_WRITE.bits() | Self::STORAGE_ATOMIC.bits() | Self::PRESENT.bits(); + /// The combination of all usages that the are guaranteed to be be ordered by the hardware. + /// If a usage is ordered, then if the texture state doesn't change between draw calls, there + /// are no barriers needed for synchronization. + /// cbindgen:ignore + const ORDERED = Self::INCLUSIVE.bits() | Self::COLOR_TARGET.bits() | Self::DEPTH_STENCIL_WRITE.bits() | Self::STORAGE_READ_ONLY.bits(); + + /// Flag used by the wgpu-core texture tracker to say a texture is in different states for every sub-resource + const COMPLEX = 1 << 13; + /// Flag used by the wgpu-core texture tracker to say that the tracker does not know the state of the sub-resource. + /// This is different from UNINITIALIZED as that says the tracker does know, but the texture has not been initialized. + const UNKNOWN = 1 << 14; + } +} + +/// A texture transition for use with `CommandEncoder::transition_resources`. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct TextureTransition<T> { + /// The texture to transition. + pub texture: T, + /// An optional selector to transition only part of the texture. + /// + /// If None, the entire texture will be transitioned. + pub selector: Option<TextureSelector>, + /// The new state to transition to. + pub state: TextureUses, +} + +/// Specifies a particular set of subresources in a texture. +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct TextureSelector { + /// Range of mips to use. + pub mips: Range<u32>, + /// Range of layers to use. + pub layers: Range<u32>, +} + +/// Specific type of a sample in a texture binding. +/// +/// Corresponds to [WebGPU `GPUTextureSampleType`]( +/// https://gpuweb.github.io/gpuweb/#enumdef-gputexturesampletype). +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum TextureSampleType { + /// Sampling returns floats. + /// + /// Example WGSL syntax: + /// ```rust,ignore + /// @group(0) @binding(0) + /// var t: texture_2d<f32>; + /// ``` + /// + /// Example GLSL syntax: + /// ```cpp,ignore + /// layout(binding = 0) + /// uniform texture2D t; + /// ``` + Float { + /// If this is `false`, the texture can't be sampled with + /// a filtering sampler. + /// + /// Even if this is `true`, it's possible to sample with + /// a **non-filtering** sampler. + filterable: bool, + }, + /// Sampling does the depth reference comparison. + /// + /// This is also compatible with a non-filtering sampler. + /// + /// Example WGSL syntax: + /// ```rust,ignore + /// @group(0) @binding(0) + /// var t: texture_depth_2d; + /// ``` + /// + /// Example GLSL syntax: + /// ```cpp,ignore + /// layout(binding = 0) + /// uniform texture2DShadow t; + /// ``` + Depth, + /// Sampling returns signed integers. + /// + /// Example WGSL syntax: + /// ```rust,ignore + /// @group(0) @binding(0) + /// var t: texture_2d<i32>; + /// ``` + /// + /// Example GLSL syntax: + /// ```cpp,ignore + /// layout(binding = 0) + /// uniform itexture2D t; + /// ``` + Sint, + /// Sampling returns unsigned integers. + /// + /// Example WGSL syntax: + /// ```rust,ignore + /// @group(0) @binding(0) + /// var t: texture_2d<u32>; + /// ``` + /// + /// Example GLSL syntax: + /// ```cpp,ignore + /// layout(binding = 0) + /// uniform utexture2D t; + /// ``` + Uint, +} + +impl Default for TextureSampleType { + fn default() -> Self { + Self::Float { filterable: true } + } +} + +/// Specific type of a sample in a texture binding. +/// +/// For use in [`BindingType::StorageTexture`]. +/// +/// Corresponds to [WebGPU `GPUStorageTextureAccess`]( +/// https://gpuweb.github.io/gpuweb/#enumdef-gpustoragetextureaccess). +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] +pub enum StorageTextureAccess { + /// The texture can only be written in the shader and it: + /// - may or may not be annotated with `write` (WGSL). + /// - must be annotated with `writeonly` (GLSL). + /// + /// Example WGSL syntax: + /// ```rust,ignore + /// @group(0) @binding(0) + /// var my_storage_image: texture_storage_2d<r32float, write>; + /// ``` + /// + /// Example GLSL syntax: + /// ```cpp,ignore + /// layout(set=0, binding=0, r32f) writeonly uniform image2D myStorageImage; + /// ``` + WriteOnly, + /// The texture can only be read in the shader and it must be annotated with `read` (WGSL) or + /// `readonly` (GLSL). + /// + /// [`Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES`] must be enabled to use this access + /// mode. This is a native-only extension. + /// + /// Example WGSL syntax: + /// ```rust,ignore + /// @group(0) @binding(0) + /// var my_storage_image: texture_storage_2d<r32float, read>; + /// ``` + /// + /// Example GLSL syntax: + /// ```cpp,ignore + /// layout(set=0, binding=0, r32f) readonly uniform image2D myStorageImage; + /// ``` + ReadOnly, + /// The texture can be both read and written in the shader and must be annotated with + /// `read_write` in WGSL. + /// + /// [`Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES`] must be enabled to use this access + /// mode. This is a nonstandard, native-only extension. + /// + /// Example WGSL syntax: + /// ```rust,ignore + /// @group(0) @binding(0) + /// var my_storage_image: texture_storage_2d<r32float, read_write>; + /// ``` + /// + /// Example GLSL syntax: + /// ```cpp,ignore + /// layout(set=0, binding=0, r32f) uniform image2D myStorageImage; + /// ``` + ReadWrite, + /// The texture can be both read and written in the shader via atomics and must be annotated + /// with `read_write` in WGSL. + /// + /// [`Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES`] must be enabled to use this access + /// mode. This is a nonstandard, native-only extension. + /// + /// Example WGSL syntax: + /// ```rust,ignore + /// @group(0) @binding(0) + /// var my_storage_image: texture_storage_2d<r32uint, atomic>; + /// ``` + Atomic, +} + +/// Describes a [`TextureView`]. +/// +/// For use with [`Texture::create_view()`]. +/// +/// Corresponds to [WebGPU `GPUTextureViewDescriptor`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gputextureviewdescriptor). +/// +#[doc = link_to_wgpu_item!(struct TextureView)] +#[doc = link_to_wgpu_docs!(["`Texture::create_view()`"]: "struct.Texture.html#method.create_view")] +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct TextureViewDescriptor<L> { + /// Debug label of the texture view. This will show up in graphics debuggers for easy identification. + pub label: L, + /// Format of the texture view. Either must be the same as the texture format or in the list + /// of `view_formats` in the texture's descriptor. + pub format: Option<TextureFormat>, + /// The dimension of the texture view. For 1D textures, this must be `D1`. For 2D textures it must be one of + /// `D2`, `D2Array`, `Cube`, and `CubeArray`. For 3D textures it must be `D3` + pub dimension: Option<TextureViewDimension>, + /// The allowed usage(s) for the texture view. Must be a subset of the usage flags of the texture. + /// If not provided, defaults to the full set of usage flags of the texture. + pub usage: Option<TextureUsages>, + /// Aspect of the texture. Color textures must be [`TextureAspect::All`]. + pub aspect: TextureAspect, + /// Base mip level. + pub base_mip_level: u32, + /// Mip level count. + /// If `Some(count)`, `base_mip_level + count` must be less or equal to underlying texture mip count. + /// If `None`, considered to include the rest of the mipmap levels, but at least 1 in total. + pub mip_level_count: Option<u32>, + /// Base array layer. + pub base_array_layer: u32, + /// Layer count. + /// If `Some(count)`, `base_array_layer + count` must be less or equal to the underlying array count. + /// If `None`, considered to include the rest of the array layers, but at least 1 in total. + pub array_layer_count: Option<u32>, +} + +/// Describes a [`Texture`](../wgpu/struct.Texture.html). +/// +/// Corresponds to [WebGPU `GPUTextureDescriptor`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gputexturedescriptor). +#[repr(C)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct TextureDescriptor<L, V> { + /// Debug label of the texture. This will show up in graphics debuggers for easy identification. + pub label: L, + /// Size of the texture. All components must be greater than zero. For a + /// regular 1D/2D texture, the unused sizes will be 1. For 2DArray textures, + /// Z is the number of 2D textures in that array. + pub size: Extent3d, + /// Mip count of texture. For a texture with no extra mips, this must be 1. + pub mip_level_count: u32, + /// Sample count of texture. If this is not 1, texture must have [`BindingType::Texture::multisampled`] set to true. + pub sample_count: u32, + /// Dimensions of the texture. + pub dimension: TextureDimension, + /// Format of the texture. + pub format: TextureFormat, + /// Allowed usages of the texture. If used in other ways, the operation will panic. + pub usage: TextureUsages, + /// Specifies what view formats will be allowed when calling `Texture::create_view` on this texture. + /// + /// View formats of the same format as the texture are always allowed. + /// + /// Note: currently, only the srgb-ness is allowed to change. (ex: `Rgba8Unorm` texture + `Rgba8UnormSrgb` view) + pub view_formats: V, +} + +impl<L, V> TextureDescriptor<L, V> { + /// Takes a closure and maps the label of the texture descriptor into another. + #[must_use] + pub fn map_label<K>(&self, fun: impl FnOnce(&L) -> K) -> TextureDescriptor<K, V> + where + V: Clone, + { + TextureDescriptor { + label: fun(&self.label), + size: self.size, + mip_level_count: self.mip_level_count, + sample_count: self.sample_count, + dimension: self.dimension, + format: self.format, + usage: self.usage, + view_formats: self.view_formats.clone(), + } + } + + /// Maps the label and view formats of the texture descriptor into another. + #[must_use] + pub fn map_label_and_view_formats<K, M>( + &self, + l_fun: impl FnOnce(&L) -> K, + v_fun: impl FnOnce(V) -> M, + ) -> TextureDescriptor<K, M> + where + V: Clone, + { + TextureDescriptor { + label: l_fun(&self.label), + size: self.size, + mip_level_count: self.mip_level_count, + sample_count: self.sample_count, + dimension: self.dimension, + format: self.format, + usage: self.usage, + view_formats: v_fun(self.view_formats.clone()), + } + } + + /// Calculates the extent at a given mip level. + /// + /// If the given mip level is larger than possible, returns None. + /// + /// Treats the depth as part of the mipmaps. If calculating + /// for a 2DArray texture, which does not mipmap depth, set depth to 1. + /// + /// ```rust + /// # use wgpu_types as wgpu; + /// # type TextureDescriptor<'a> = wgpu::TextureDescriptor<(), &'a [wgpu::TextureFormat]>; + /// let desc = TextureDescriptor { + /// label: (), + /// size: wgpu::Extent3d { width: 100, height: 60, depth_or_array_layers: 1 }, + /// mip_level_count: 7, + /// sample_count: 1, + /// dimension: wgpu::TextureDimension::D3, + /// format: wgpu::TextureFormat::Rgba8Sint, + /// usage: wgpu::TextureUsages::empty(), + /// view_formats: &[], + /// }; + /// + /// assert_eq!(desc.mip_level_size(0), Some(wgpu::Extent3d { width: 100, height: 60, depth_or_array_layers: 1 })); + /// assert_eq!(desc.mip_level_size(1), Some(wgpu::Extent3d { width: 50, height: 30, depth_or_array_layers: 1 })); + /// assert_eq!(desc.mip_level_size(2), Some(wgpu::Extent3d { width: 25, height: 15, depth_or_array_layers: 1 })); + /// assert_eq!(desc.mip_level_size(3), Some(wgpu::Extent3d { width: 12, height: 7, depth_or_array_layers: 1 })); + /// assert_eq!(desc.mip_level_size(4), Some(wgpu::Extent3d { width: 6, height: 3, depth_or_array_layers: 1 })); + /// assert_eq!(desc.mip_level_size(5), Some(wgpu::Extent3d { width: 3, height: 1, depth_or_array_layers: 1 })); + /// assert_eq!(desc.mip_level_size(6), Some(wgpu::Extent3d { width: 1, height: 1, depth_or_array_layers: 1 })); + /// assert_eq!(desc.mip_level_size(7), None); + /// ``` + #[must_use] + pub fn mip_level_size(&self, level: u32) -> Option<Extent3d> { + if level >= self.mip_level_count { + return None; + } + + Some(self.size.mip_level_size(level, self.dimension)) + } + + /// Computes the render extent of this texture. + /// + /// This is a low-level helper exported for use by wgpu-core. + /// + /// <https://gpuweb.github.io/gpuweb/#abstract-opdef-compute-render-extent> + /// + /// # Panics + /// + /// If the mip level is out of range. + #[doc(hidden)] + #[must_use] + pub fn compute_render_extent(&self, mip_level: u32, plane: Option<u32>) -> Extent3d { + let Extent3d { + width, + height, + depth_or_array_layers: _, + } = self.mip_level_size(mip_level).expect("invalid mip level"); + + let (w_subsampling, h_subsampling) = self.format.subsampling_factors(plane); + + let width = width / w_subsampling; + let height = height / h_subsampling; + + Extent3d { + width, + height, + depth_or_array_layers: 1, + } + } + + /// Returns the number of array layers. + /// + /// <https://gpuweb.github.io/gpuweb/#abstract-opdef-array-layer-count> + #[must_use] + pub fn array_layer_count(&self) -> u32 { + match self.dimension { + TextureDimension::D1 | TextureDimension::D3 => 1, + TextureDimension::D2 => self.size.depth_or_array_layers, + } + } +} + +/// Describes a `Sampler`. +/// +/// For use with `Device::create_sampler`. +/// +/// Corresponds to [WebGPU `GPUSamplerDescriptor`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gpusamplerdescriptor). +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct SamplerDescriptor<L> { + /// Debug label of the sampler. This will show up in graphics debuggers for easy identification. + pub label: L, + /// How to deal with out of bounds accesses in the u (i.e. x) direction + pub address_mode_u: AddressMode, + /// How to deal with out of bounds accesses in the v (i.e. y) direction + pub address_mode_v: AddressMode, + /// How to deal with out of bounds accesses in the w (i.e. z) direction + pub address_mode_w: AddressMode, + /// How to filter the texture when it needs to be magnified (made larger) + pub mag_filter: FilterMode, + /// How to filter the texture when it needs to be minified (made smaller) + pub min_filter: FilterMode, + /// How to filter between mip map levels + pub mipmap_filter: MipmapFilterMode, + /// Minimum level of detail (i.e. mip level) to use + pub lod_min_clamp: f32, + /// Maximum level of detail (i.e. mip level) to use + pub lod_max_clamp: f32, + /// If this is enabled, this is a comparison sampler using the given comparison function. + pub compare: Option<crate::CompareFunction>, + /// Must be at least 1. If this is not 1, all filter modes must be linear. + pub anisotropy_clamp: u16, + /// Border color to use when `address_mode` is [`AddressMode::ClampToBorder`] + pub border_color: Option<SamplerBorderColor>, +} + +impl<L: Default> Default for SamplerDescriptor<L> { + fn default() -> Self { + Self { + label: Default::default(), + address_mode_u: Default::default(), + address_mode_v: Default::default(), + address_mode_w: Default::default(), + mag_filter: Default::default(), + min_filter: Default::default(), + mipmap_filter: Default::default(), + lod_min_clamp: 0.0, + lod_max_clamp: 32.0, + compare: None, + anisotropy_clamp: 1, + border_color: None, + } + } +} + +/// How edges should be handled in texture addressing. +/// +/// Corresponds to [WebGPU `GPUAddressMode`]( +/// https://gpuweb.github.io/gpuweb/#enumdef-gpuaddressmode). +#[repr(C)] +#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] +pub enum AddressMode { + /// Clamp the value to the edge of the texture + /// + /// -0.25 -> 0.0 + /// 1.25 -> 1.0 + #[default] + ClampToEdge = 0, + /// Repeat the texture in a tiling fashion + /// + /// -0.25 -> 0.75 + /// 1.25 -> 0.25 + Repeat = 1, + /// Repeat the texture, mirroring it every repeat + /// + /// -0.25 -> 0.25 + /// 1.25 -> 0.75 + MirrorRepeat = 2, + /// Clamp the value to the border of the texture + /// Requires feature [`Features::ADDRESS_MODE_CLAMP_TO_BORDER`] + /// + /// -0.25 -> border + /// 1.25 -> border + ClampToBorder = 3, +} + +/// Texel mixing mode when sampling between texels. +/// +/// Corresponds to [WebGPU `GPUFilterMode`]( +/// https://gpuweb.github.io/gpuweb/#enumdef-gpufiltermode). +#[repr(C)] +#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] +pub enum FilterMode { + /// Nearest neighbor sampling. + /// + /// This creates a pixelated effect. + #[default] + Nearest = 0, + /// Linear Interpolation + /// + /// This makes textures smooth but blurry. + Linear = 1, +} + +/// Texel mixing mode when sampling between texels. +/// +/// Corresponds to [WebGPU `GPUMipmapFilterMode`]( +/// https://gpuweb.github.io/gpuweb/#enumdef-gpumipmapfiltermode). +#[repr(C)] +#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] +pub enum MipmapFilterMode { + /// Nearest neighbor sampling. + /// + /// Return the value of the texel nearest to the texture coordinates. + #[default] + Nearest = 0, + /// Linear Interpolation + /// + /// Select two texels in each dimension and return a linear interpolation between their values. + Linear = 1, +} + +/// Color variation to use when sampler addressing mode is [`AddressMode::ClampToBorder`] +#[repr(C)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum SamplerBorderColor { + /// [0, 0, 0, 0] + TransparentBlack, + /// [0, 0, 0, 1] + OpaqueBlack, + /// [1, 1, 1, 1] + OpaqueWhite, + + /// On the Metal backend, this is equivalent to `TransparentBlack` for + /// textures that have an alpha component, and equivalent to `OpaqueBlack` + /// for textures that do not have an alpha component. On other backends, + /// this is equivalent to `TransparentBlack`. Requires + /// [`Features::ADDRESS_MODE_CLAMP_TO_ZERO`]. Not supported on the web. + Zero, +} + +/// Layout of a texture in a buffer's memory. +/// +/// The bytes per row and rows per image can be hard to figure out so here are some examples: +/// +/// | Resolution | Format | Bytes per block | Pixels per block | Bytes per row | Rows per image | +/// |------------|--------|-----------------|------------------|----------------------------------------|------------------------------| +/// | 256x256 | RGBA8 | 4 | 1 * 1 * 1 | 256 * 4 = Some(1024) | None | +/// | 32x16x8 | RGBA8 | 4 | 1 * 1 * 1 | 32 * 4 = 128 padded to 256 = Some(256) | None | +/// | 256x256 | BC3 | 16 | 4 * 4 * 1 | 16 * (256 / 4) = 1024 = Some(1024) | None | +/// | 64x64x8 | BC3 | 16 | 4 * 4 * 1 | 16 * (64 / 4) = 256 = Some(256) | 64 / 4 = 16 = Some(16) | +/// +/// Corresponds to [WebGPU `GPUTexelCopyBufferLayout`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gpuimagedatalayout). +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct TexelCopyBufferLayout { + /// Offset into the buffer that is the start of the texture. Must be a multiple of texture block size. + /// For non-compressed textures, this is 1. + pub offset: crate::BufferAddress, + /// Bytes per "row" in an image. + /// + /// A row is one row of pixels or of compressed blocks in the x direction. + /// + /// This value is required if there are multiple rows (i.e. height or depth is more than one pixel or pixel block for compressed textures) + /// + /// Must be a multiple of 256 for [`CommandEncoder::copy_buffer_to_texture`][CEcbtt] + /// and [`CommandEncoder::copy_texture_to_buffer`][CEcttb]. You must manually pad the + /// image such that this is a multiple of 256. It will not affect the image data. + /// + /// [`Queue::write_texture`][Qwt] does not have this requirement. + /// + /// Must be a multiple of the texture block size. For non-compressed textures, this is 1. + /// + #[doc = link_to_wgpu_docs!(["CEcbtt"]: "struct.CommandEncoder.html#method.copy_buffer_to_texture")] + #[doc = link_to_wgpu_docs!(["CEcttb"]: "struct.CommandEncoder.html#method.copy_texture_to_buffer")] + #[doc = link_to_wgpu_docs!(["Qwt"]: "struct.Queue.html#method.write_texture")] + pub bytes_per_row: Option<u32>, + /// "Rows" that make up a single "image". + /// + /// A row is one row of pixels or of compressed blocks in the x direction. + /// + /// An image is one layer in the z direction of a 3D image or 2DArray texture. + /// + /// The amount of rows per image may be larger than the actual amount of rows of data. + /// + /// Required if there are multiple images (i.e. the depth is more than one). + pub rows_per_image: Option<u32>, +} + +/// View of a buffer which can be used to copy to/from a texture. +/// +/// Corresponds to [WebGPU `GPUTexelCopyBufferInfo`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gpuimagecopybuffer). +#[repr(C)] +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct TexelCopyBufferInfo<B> { + /// The buffer to be copied to/from. + pub buffer: B, + /// The layout of the texture data in this buffer. + pub layout: TexelCopyBufferLayout, +} + +/// View of a texture which can be used to copy to/from a buffer/texture. +/// +/// Corresponds to [WebGPU `GPUTexelCopyTextureInfo`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gpuimagecopytexture). +#[repr(C)] +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct TexelCopyTextureInfo<T> { + /// The texture to be copied to/from. + pub texture: T, + /// The target mip level of the texture. + pub mip_level: u32, + /// The base texel of the texture in the selected `mip_level`. Together + /// with the `copy_size` argument to copy functions, defines the + /// sub-region of the texture to copy. + #[cfg_attr(feature = "serde", serde(default))] + pub origin: Origin3d, + /// The copy aspect. + #[cfg_attr(feature = "serde", serde(default))] + pub aspect: TextureAspect, +} + +impl<T> TexelCopyTextureInfo<T> { + /// Adds color space and premultiplied alpha information to make this + /// descriptor tagged. + pub fn to_tagged( + self, + color_space: PredefinedColorSpace, + premultiplied_alpha: bool, + ) -> CopyExternalImageDestInfo<T> { + CopyExternalImageDestInfo { + texture: self.texture, + mip_level: self.mip_level, + origin: self.origin, + aspect: self.aspect, + color_space, + premultiplied_alpha, + } + } +} + +/// Subresource range within an image +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct ImageSubresourceRange { + /// Aspect of the texture. Color textures must be [`TextureAspect::All`][TAA]. + /// + #[doc = link_to_wgpu_docs!(["TAA"]: "enum.TextureAspect.html#variant.All")] + pub aspect: TextureAspect, + /// Base mip level. + pub base_mip_level: u32, + /// Mip level count. + /// If `Some(count)`, `base_mip_level + count` must be less or equal to underlying texture mip count. + /// If `None`, considered to include the rest of the mipmap levels, but at least 1 in total. + pub mip_level_count: Option<u32>, + /// Base array layer. + pub base_array_layer: u32, + /// Layer count. + /// If `Some(count)`, `base_array_layer + count` must be less or equal to the underlying array count. + /// If `None`, considered to include the rest of the array layers, but at least 1 in total. + pub array_layer_count: Option<u32>, +} + +impl ImageSubresourceRange { + /// Returns if the given range represents a full resource, with a texture of the given + /// layer count and mip count. + /// + /// ```rust + /// # use wgpu_types as wgpu; + /// + /// let range_none = wgpu::ImageSubresourceRange { + /// aspect: wgpu::TextureAspect::All, + /// base_mip_level: 0, + /// mip_level_count: None, + /// base_array_layer: 0, + /// array_layer_count: None, + /// }; + /// assert_eq!(range_none.is_full_resource(wgpu::TextureFormat::Stencil8, 5, 10), true); + /// + /// let range_some = wgpu::ImageSubresourceRange { + /// aspect: wgpu::TextureAspect::All, + /// base_mip_level: 0, + /// mip_level_count: Some(5), + /// base_array_layer: 0, + /// array_layer_count: Some(10), + /// }; + /// assert_eq!(range_some.is_full_resource(wgpu::TextureFormat::Stencil8, 5, 10), true); + /// + /// let range_mixed = wgpu::ImageSubresourceRange { + /// aspect: wgpu::TextureAspect::StencilOnly, + /// base_mip_level: 0, + /// // Only partial resource + /// mip_level_count: Some(3), + /// base_array_layer: 0, + /// array_layer_count: None, + /// }; + /// assert_eq!(range_mixed.is_full_resource(wgpu::TextureFormat::Stencil8, 5, 10), false); + /// ``` + #[must_use] + pub fn is_full_resource( + &self, + format: TextureFormat, + mip_levels: u32, + array_layers: u32, + ) -> bool { + // Mip level count and array layer count need to deal with both the None and Some(count) case. + let mip_level_count = self.mip_level_count.unwrap_or(mip_levels); + let array_layer_count = self.array_layer_count.unwrap_or(array_layers); + + let aspect_eq = Some(format) == format.aspect_specific_format(self.aspect); + + let base_mip_level_eq = self.base_mip_level == 0; + let mip_level_count_eq = mip_level_count == mip_levels; + + let base_array_layer_eq = self.base_array_layer == 0; + let array_layer_count_eq = array_layer_count == array_layers; + + aspect_eq + && base_mip_level_eq + && mip_level_count_eq + && base_array_layer_eq + && array_layer_count_eq + } + + /// Returns the mip level range of a subresource range describes for a specific texture. + #[must_use] + pub fn mip_range(&self, mip_level_count: u32) -> Range<u32> { + self.base_mip_level..match self.mip_level_count { + Some(mip_level_count) => self.base_mip_level + mip_level_count, + None => mip_level_count, + } + } + + /// Returns the layer range of a subresource range describes for a specific texture. + #[must_use] + pub fn layer_range(&self, array_layer_count: u32) -> Range<u32> { + self.base_array_layer..match self.array_layer_count { + Some(array_layer_count) => self.base_array_layer + array_layer_count, + None => array_layer_count, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::Extent3d; + + #[test] + fn test_physical_size() { + let format = TextureFormat::Bc1RgbaUnormSrgb; // 4x4 blocks + assert_eq!( + Extent3d { + width: 7, + height: 7, + depth_or_array_layers: 1 + } + .physical_size(format), + Extent3d { + width: 8, + height: 8, + depth_or_array_layers: 1 + } + ); + // Doesn't change, already aligned + assert_eq!( + Extent3d { + width: 8, + height: 8, + depth_or_array_layers: 1 + } + .physical_size(format), + Extent3d { + width: 8, + height: 8, + depth_or_array_layers: 1 + } + ); + let format = TextureFormat::Astc { + block: AstcBlock::B8x5, + channel: AstcChannel::Unorm, + }; // 8x5 blocks + assert_eq!( + Extent3d { + width: 7, + height: 7, + depth_or_array_layers: 1 + } + .physical_size(format), + Extent3d { + width: 8, + height: 10, + depth_or_array_layers: 1 + } + ); + } + + #[test] + fn test_max_mips() { + // 1D + assert_eq!( + Extent3d { + width: 240, + height: 1, + depth_or_array_layers: 1 + } + .max_mips(TextureDimension::D1), + 1 + ); + // 2D + assert_eq!( + Extent3d { + width: 1, + height: 1, + depth_or_array_layers: 1 + } + .max_mips(TextureDimension::D2), + 1 + ); + assert_eq!( + Extent3d { + width: 60, + height: 60, + depth_or_array_layers: 1 + } + .max_mips(TextureDimension::D2), + 6 + ); + assert_eq!( + Extent3d { + width: 240, + height: 1, + depth_or_array_layers: 1000 + } + .max_mips(TextureDimension::D2), + 8 + ); + // 3D + assert_eq!( + Extent3d { + width: 16, + height: 30, + depth_or_array_layers: 60 + } + .max_mips(TextureDimension::D3), + 6 + ); + } +} diff --git a/third_party/rust/wgpu-types/src/texture/external_image.rs b/third_party/rust/wgpu-types/src/texture/external_image.rs @@ -0,0 +1,163 @@ +#[allow(unused_imports, reason = "conditionally used, including in docs")] +use crate::{DownlevelFlags, Origin2d}; + +/// View of an external texture that can be used to copy to a texture. +/// +/// Corresponds to [WebGPU `GPUCopyExternalImageSourceInfo`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gpuimagecopyexternalimage). +#[cfg(all(target_arch = "wasm32", feature = "web"))] +#[derive(Clone, Debug)] +pub struct CopyExternalImageSourceInfo { + /// The texture to be copied from. The copy source data is captured at the moment + /// the copy is issued. + pub source: ExternalImageSource, + /// The base texel used for copying from the external image. Together + /// with the `copy_size` argument to copy functions, defines the + /// sub-region of the image to copy. + /// + /// Relative to the top left of the image. + /// + /// Must be [`Origin2d::ZERO`] if [`DownlevelFlags::UNRESTRICTED_EXTERNAL_TEXTURE_COPIES`] is not supported. + pub origin: Origin2d, + /// If the Y coordinate of the image should be flipped. Even if this is + /// true, `origin` is still relative to the top left. + pub flip_y: bool, +} + +/// Source of an external texture copy. +/// +/// Corresponds to the [implicit union type on WebGPU `GPUCopyExternalImageSourceInfo.source`]( +/// https://gpuweb.github.io/gpuweb/#dom-gpuimagecopyexternalimage-source). +#[cfg(all(target_arch = "wasm32", feature = "web"))] +#[derive(Clone, Debug)] +pub enum ExternalImageSource { + /// Copy from a previously-decoded image bitmap. + ImageBitmap(web_sys::ImageBitmap), + /// Copy from an image element. + HTMLImageElement(web_sys::HtmlImageElement), + /// Copy from a current frame of a video element. + HTMLVideoElement(web_sys::HtmlVideoElement), + /// Copy from an image. + ImageData(web_sys::ImageData), + /// Copy from a on-screen canvas. + HTMLCanvasElement(web_sys::HtmlCanvasElement), + /// Copy from a off-screen canvas. + /// + /// Requires [`DownlevelFlags::UNRESTRICTED_EXTERNAL_TEXTURE_COPIES`] + OffscreenCanvas(web_sys::OffscreenCanvas), + /// Copy from a video frame. + #[cfg(web_sys_unstable_apis)] + VideoFrame(web_sys::VideoFrame), +} + +#[cfg(all(target_arch = "wasm32", feature = "web"))] +impl ExternalImageSource { + /// Gets the pixel, not css, width of the source. + pub fn width(&self) -> u32 { + match self { + ExternalImageSource::ImageBitmap(b) => b.width(), + ExternalImageSource::HTMLImageElement(i) => i.width(), + ExternalImageSource::HTMLVideoElement(v) => v.video_width(), + ExternalImageSource::ImageData(i) => i.width(), + ExternalImageSource::HTMLCanvasElement(c) => c.width(), + ExternalImageSource::OffscreenCanvas(c) => c.width(), + #[cfg(web_sys_unstable_apis)] + ExternalImageSource::VideoFrame(v) => v.display_width(), + } + } + + /// Gets the pixel, not css, height of the source. + pub fn height(&self) -> u32 { + match self { + ExternalImageSource::ImageBitmap(b) => b.height(), + ExternalImageSource::HTMLImageElement(i) => i.height(), + ExternalImageSource::HTMLVideoElement(v) => v.video_height(), + ExternalImageSource::ImageData(i) => i.height(), + ExternalImageSource::HTMLCanvasElement(c) => c.height(), + ExternalImageSource::OffscreenCanvas(c) => c.height(), + #[cfg(web_sys_unstable_apis)] + ExternalImageSource::VideoFrame(v) => v.display_height(), + } + } +} + +#[cfg(all(target_arch = "wasm32", feature = "web"))] +impl core::ops::Deref for ExternalImageSource { + type Target = js_sys::Object; + + fn deref(&self) -> &Self::Target { + match self { + Self::ImageBitmap(b) => b, + Self::HTMLImageElement(i) => i, + Self::HTMLVideoElement(v) => v, + Self::ImageData(i) => i, + Self::HTMLCanvasElement(c) => c, + Self::OffscreenCanvas(c) => c, + #[cfg(web_sys_unstable_apis)] + Self::VideoFrame(v) => v, + } + } +} + +#[cfg(all( + target_arch = "wasm32", + feature = "web", + feature = "fragile-send-sync-non-atomic-wasm", + not(target_feature = "atomics") +))] +unsafe impl Send for ExternalImageSource {} +#[cfg(all( + target_arch = "wasm32", + feature = "web", + feature = "fragile-send-sync-non-atomic-wasm", + not(target_feature = "atomics") +))] +unsafe impl Sync for ExternalImageSource {} + +/// Color spaces supported on the web. +/// +/// Corresponds to [HTML Canvas `PredefinedColorSpace`]( +/// https://html.spec.whatwg.org/multipage/canvas.html#predefinedcolorspace). +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] +pub enum PredefinedColorSpace { + /// sRGB color space + Srgb, + /// Display-P3 color space + DisplayP3, +} + +/// View of a texture which can be used to copy to a texture, including +/// color space and alpha premultiplication information. +/// +/// Corresponds to [WebGPU `GPUCopyExternalImageDestInfo`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gpuimagecopytexturetagged). +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct CopyExternalImageDestInfo<T> { + /// The texture to be copied to/from. + pub texture: T, + /// The target mip level of the texture. + pub mip_level: u32, + /// The base texel of the texture in the selected `mip_level`. + pub origin: crate::Origin3d, + /// The copy aspect. + pub aspect: crate::TextureAspect, + /// The color space of this texture. + pub color_space: PredefinedColorSpace, + /// The premultiplication of this texture + pub premultiplied_alpha: bool, +} + +impl<T> CopyExternalImageDestInfo<T> { + /// Removes the colorspace information from the type. + pub fn to_untagged(self) -> crate::TexelCopyTextureInfo<T> { + crate::TexelCopyTextureInfo { + texture: self.texture, + mip_level: self.mip_level, + origin: self.origin, + aspect: self.aspect, + } + } +} diff --git a/third_party/rust/wgpu-types/src/texture/external_texture.rs b/third_party/rust/wgpu-types/src/texture/external_texture.rs @@ -0,0 +1,167 @@ +#[cfg(any(feature = "serde", test))] +use serde::{Deserialize, Serialize}; + +#[cfg(doc)] +use crate::TextureFormat; + +/// Format of an `ExternalTexture`. This indicates the number of underlying +/// planes used by the `ExternalTexture` as well as each plane's format. +#[repr(C)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum ExternalTextureFormat { + /// Single [`TextureFormat::Rgba8Unorm`] or [`TextureFormat::Bgra8Unorm`] format plane. + Rgba, + /// [`TextureFormat::R8Unorm`] Y plane, and [`TextureFormat::Rg8Unorm`] + /// interleaved CbCr plane. + Nv12, + /// Separate [`TextureFormat::R8Unorm`] Y, Cb, and Cr planes. + Yu12, +} + +/// Parameters describing a gamma encoding transfer function in the form +/// tf = { k * linear | linear < b +/// { a * pow(linear, 1/g) - (a-1) | linear >= b +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, bytemuck::Zeroable, bytemuck::Pod)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[allow(missing_docs)] +pub struct ExternalTextureTransferFunction { + pub a: f32, + pub b: f32, + pub g: f32, + pub k: f32, +} + +impl Default for ExternalTextureTransferFunction { + fn default() -> Self { + Self { + a: 1.0, + b: 1.0, + g: 1.0, + k: 1.0, + } + } +} + +/// Describes an [`ExternalTexture`](../wgpu/struct.ExternalTexture.html). +/// +/// Note that [`width`] and [`height`] are the values that should be returned by +/// size queries in shader code; they do not necessarily match the dimensions of +/// the underlying plane texture(s). As a special case, if `(width, height)` is +/// `(0, 0)`, the actual size of the first underlying plane should be used instead. +/// +/// The size given by [`width`] and [`height`] must be consistent with +/// [`sample_transform`]: they should be the size in texels of the rectangle +/// covered by the square (0,0)..(1,1) after [`sample_transform`] has been applied +/// to it. +/// +/// [`width`]: Self::width +/// [`height`]: Self::height +/// [`sample_transform`]: Self::sample_transform +/// +/// Corresponds to [WebGPU `GPUExternalTextureDescriptor`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gpuexternaltexturedescriptor). +#[repr(C)] +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct ExternalTextureDescriptor<L> { + /// Debug label of the external texture. This will show up in graphics + /// debuggers for easy identification. + pub label: L, + + /// Width of the external texture. + pub width: u32, + + /// Height of the external texture. + pub height: u32, + + /// Format of the external texture. + pub format: ExternalTextureFormat, + + /// 4x4 column-major matrix with which to convert sampled YCbCr values + /// to RGBA. + /// This is ignored when `format` is [`ExternalTextureFormat::Rgba`]. + pub yuv_conversion_matrix: [f32; 16], + + /// 3x3 column-major matrix to transform linear RGB values in the source + /// color space to linear RGB values in the destination color space. In + /// combination with [`Self::src_transfer_function`] and + /// [`Self::dst_transfer_function`] this can be used to ensure that + /// [`ImageSample`] and [`ImageLoad`] operations return values in the + /// desired destination color space rather than the source color space of + /// the underlying planes. + /// + /// [`ImageSample`]: https://docs.rs/naga/latest/naga/ir/enum.Expression.html#variant.ImageSample + /// [`ImageLoad`]: https://docs.rs/naga/latest/naga/ir/enum.Expression.html#variant.ImageLoad + pub gamut_conversion_matrix: [f32; 9], + + /// Transfer function for the source color space. The *inverse* of this + /// will be applied to decode non-linear RGB to linear RGB in the source + /// color space. + pub src_transfer_function: ExternalTextureTransferFunction, + + /// Transfer function for the destination color space. This will be applied + /// to encode linear RGB to non-linear RGB in the destination color space. + pub dst_transfer_function: ExternalTextureTransferFunction, + + /// Transform to apply to [`ImageSample`] coordinates. + /// + /// This is a 3x2 column-major matrix representing an affine transform from + /// normalized texture coordinates to the normalized coordinates that should + /// be sampled from the external texture's underlying plane(s). + /// + /// This transform may scale, translate, flip, and rotate in 90-degree + /// increments, but the result of transforming the rectangle (0,0)..(1,1) + /// must be an axis-aligned rectangle that falls within the bounds of + /// (0,0)..(1,1). + /// + /// [`ImageSample`]: https://docs.rs/naga/latest/naga/ir/enum.Expression.html#variant.ImageSample + pub sample_transform: [f32; 6], + + /// Transform to apply to [`ImageLoad`] coordinates. + /// + /// This is a 3x2 column-major matrix representing an affine transform from + /// non-normalized texel coordinates to the non-normalized coordinates of + /// the texel that should be loaded from the external texture's underlying + /// plane 0. For planes 1 and 2, if present, plane 0's coordinates are + /// scaled according to the textures' relative sizes. + /// + /// This transform may scale, translate, flip, and rotate in 90-degree + /// increments, but the result of transforming the rectangle (0,0)..([`width`], + /// [`height`]) must be an axis-aligned rectangle that falls within the bounds + /// of (0,0)..([`width`], [`height`]). + /// + /// [`ImageLoad`]: https://docs.rs/naga/latest/naga/ir/enum.Expression.html#variant.ImageLoad + /// [`width`]: Self::width + /// [`height`]: Self::height + pub load_transform: [f32; 6], +} + +impl<L> ExternalTextureDescriptor<L> { + /// Takes a closure and maps the label of the external texture descriptor into another. + #[must_use] + pub fn map_label<K>(&self, fun: impl FnOnce(&L) -> K) -> ExternalTextureDescriptor<K> { + ExternalTextureDescriptor { + label: fun(&self.label), + width: self.width, + height: self.height, + format: self.format, + yuv_conversion_matrix: self.yuv_conversion_matrix, + sample_transform: self.sample_transform, + load_transform: self.load_transform, + gamut_conversion_matrix: self.gamut_conversion_matrix, + src_transfer_function: self.src_transfer_function, + dst_transfer_function: self.dst_transfer_function, + } + } + + /// The number of underlying planes used by the external texture. + pub fn num_planes(&self) -> usize { + match self.format { + ExternalTextureFormat::Rgba => 1, + ExternalTextureFormat::Nv12 => 2, + ExternalTextureFormat::Yu12 => 3, + } + } +} diff --git a/third_party/rust/wgpu-types/src/texture/format.rs b/third_party/rust/wgpu-types/src/texture/format.rs @@ -0,0 +1,2478 @@ +use alloc::vec::Vec; + +use crate::{Features, TextureAspect, TextureSampleType, TextureUsages}; + +#[cfg(any(feature = "serde", test))] +use serde::{Deserialize, Serialize}; + +/// ASTC block dimensions +#[repr(C)] +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum AstcBlock { + /// 4x4 block compressed texture. 16 bytes per block (8 bit/px). + B4x4, + /// 5x4 block compressed texture. 16 bytes per block (6.4 bit/px). + B5x4, + /// 5x5 block compressed texture. 16 bytes per block (5.12 bit/px). + B5x5, + /// 6x5 block compressed texture. 16 bytes per block (4.27 bit/px). + B6x5, + /// 6x6 block compressed texture. 16 bytes per block (3.56 bit/px). + B6x6, + /// 8x5 block compressed texture. 16 bytes per block (3.2 bit/px). + B8x5, + /// 8x6 block compressed texture. 16 bytes per block (2.67 bit/px). + B8x6, + /// 8x8 block compressed texture. 16 bytes per block (2 bit/px). + B8x8, + /// 10x5 block compressed texture. 16 bytes per block (2.56 bit/px). + B10x5, + /// 10x6 block compressed texture. 16 bytes per block (2.13 bit/px). + B10x6, + /// 10x8 block compressed texture. 16 bytes per block (1.6 bit/px). + B10x8, + /// 10x10 block compressed texture. 16 bytes per block (1.28 bit/px). + B10x10, + /// 12x10 block compressed texture. 16 bytes per block (1.07 bit/px). + B12x10, + /// 12x12 block compressed texture. 16 bytes per block (0.89 bit/px). + B12x12, +} + +/// ASTC RGBA channel +#[repr(C)] +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum AstcChannel { + /// 8 bit integer RGBA, [0, 255] converted to/from linear-color float [0, 1] in shader. + /// + /// [`Features::TEXTURE_COMPRESSION_ASTC`] must be enabled to use this channel. + Unorm, + /// 8 bit integer RGBA, Srgb-color [0, 255] converted to/from linear-color float [0, 1] in shader. + /// + /// [`Features::TEXTURE_COMPRESSION_ASTC`] must be enabled to use this channel. + UnormSrgb, + /// floating-point RGBA, linear-color float can be outside of the [0, 1] range. + /// + /// [`Features::TEXTURE_COMPRESSION_ASTC_HDR`] must be enabled to use this channel. + Hdr, +} + +/// Format in which a texture’s texels are stored in GPU memory. +/// +/// Certain formats additionally specify a conversion. +/// When these formats are used in a shader, the conversion automatically takes place when loading +/// from or storing to the texture. +/// +/// * `Unorm` formats linearly scale the integer range of the storage format to a floating-point +/// range of 0 to 1, inclusive. +/// * `Snorm` formats linearly scale the integer range of the storage format to a floating-point +/// range of −1 to 1, inclusive, except that the most negative value +/// (−128 for 8-bit, −32768 for 16-bit) is excluded; on conversion, +/// it is treated as identical to the second most negative +/// (−127 for 8-bit, −32767 for 16-bit), +/// so that the positive and negative ranges are symmetric. +/// * `UnormSrgb` formats apply the [sRGB transfer function] so that the storage is sRGB encoded +/// while the shader works with linear intensity values. +/// * `Uint`, `Sint`, and `Float` formats perform no conversion. +/// +/// Corresponds to [WebGPU `GPUTextureFormat`]( +/// https://gpuweb.github.io/gpuweb/#enumdef-gputextureformat). +/// +/// [sRGB transfer function]: https://en.wikipedia.org/wiki/SRGB#Transfer_function_(%22gamma%22) +#[repr(C)] +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] +pub enum TextureFormat { + // Normal 8 bit formats + /// Red channel only. 8 bit integer per channel. [0, 255] converted to/from float [0, 1] in shader. + R8Unorm, + /// Red channel only. 8 bit integer per channel. [−127, 127] converted to/from float [−1, 1] in shader. + R8Snorm, + /// Red channel only. 8 bit integer per channel. Unsigned in shader. + R8Uint, + /// Red channel only. 8 bit integer per channel. Signed in shader. + R8Sint, + + // Normal 16 bit formats + /// Red channel only. 16 bit integer per channel. Unsigned in shader. + R16Uint, + /// Red channel only. 16 bit integer per channel. Signed in shader. + R16Sint, + /// Red channel only. 16 bit integer per channel. [0, 65535] converted to/from float [0, 1] in shader. + /// + /// [`Features::TEXTURE_FORMAT_16BIT_NORM`] must be enabled to use this texture format. + R16Unorm, + /// Red channel only. 16 bit integer per channel. [−32767, 32767] converted to/from float [−1, 1] in shader. + /// + /// [`Features::TEXTURE_FORMAT_16BIT_NORM`] must be enabled to use this texture format. + R16Snorm, + /// Red channel only. 16 bit float per channel. Float in shader. + R16Float, + /// Red and green channels. 8 bit integer per channel. [0, 255] converted to/from float [0, 1] in shader. + Rg8Unorm, + /// Red and green channels. 8 bit integer per channel. [−127, 127] converted to/from float [−1, 1] in shader. + Rg8Snorm, + /// Red and green channels. 8 bit integer per channel. Unsigned in shader. + Rg8Uint, + /// Red and green channels. 8 bit integer per channel. Signed in shader. + Rg8Sint, + + // Normal 32 bit formats + /// Red channel only. 32 bit integer per channel. Unsigned in shader. + R32Uint, + /// Red channel only. 32 bit integer per channel. Signed in shader. + R32Sint, + /// Red channel only. 32 bit float per channel. Float in shader. + R32Float, + /// Red and green channels. 16 bit integer per channel. Unsigned in shader. + Rg16Uint, + /// Red and green channels. 16 bit integer per channel. Signed in shader. + Rg16Sint, + /// Red and green channels. 16 bit integer per channel. [0, 65535] converted to/from float [0, 1] in shader. + /// + /// [`Features::TEXTURE_FORMAT_16BIT_NORM`] must be enabled to use this texture format. + Rg16Unorm, + /// Red and green channels. 16 bit integer per channel. [−32767, 32767] converted to/from float [−1, 1] in shader. + /// + /// [`Features::TEXTURE_FORMAT_16BIT_NORM`] must be enabled to use this texture format. + Rg16Snorm, + /// Red and green channels. 16 bit float per channel. Float in shader. + Rg16Float, + /// Red, green, blue, and alpha channels. 8 bit integer per channel. [0, 255] converted to/from float [0, 1] in shader. + Rgba8Unorm, + /// Red, green, blue, and alpha channels. 8 bit integer per channel. Srgb-color [0, 255] converted to/from linear-color float [0, 1] in shader. + Rgba8UnormSrgb, + /// Red, green, blue, and alpha channels. 8 bit integer per channel. [−127, 127] converted to/from float [−1, 1] in shader. + Rgba8Snorm, + /// Red, green, blue, and alpha channels. 8 bit integer per channel. Unsigned in shader. + Rgba8Uint, + /// Red, green, blue, and alpha channels. 8 bit integer per channel. Signed in shader. + Rgba8Sint, + /// Blue, green, red, and alpha channels. 8 bit integer per channel. [0, 255] converted to/from float [0, 1] in shader. + Bgra8Unorm, + /// Blue, green, red, and alpha channels. 8 bit integer per channel. Srgb-color [0, 255] converted to/from linear-color float [0, 1] in shader. + Bgra8UnormSrgb, + + // Packed 32 bit formats + /// Packed unsigned float with 9 bits mantisa for each RGB component, then a common 5 bits exponent + Rgb9e5Ufloat, + /// Red, green, blue, and alpha channels. 10 bit integer for RGB channels, 2 bit integer for alpha channel. Unsigned in shader. + Rgb10a2Uint, + /// Red, green, blue, and alpha channels. 10 bit integer for RGB channels, 2 bit integer for alpha channel. [0, 1023] ([0, 3] for alpha) converted to/from float [0, 1] in shader. + Rgb10a2Unorm, + /// Red, green, and blue channels. 11 bit float with no sign bit for RG channels. 10 bit float with no sign bit for blue channel. Float in shader. + Rg11b10Ufloat, + + // Normal 64 bit formats + /// Red channel only. 64 bit integer per channel. Unsigned in shader. + /// + /// [`Features::TEXTURE_INT64_ATOMIC`] must be enabled to use this texture format. + R64Uint, + /// Red and green channels. 32 bit integer per channel. Unsigned in shader. + Rg32Uint, + /// Red and green channels. 32 bit integer per channel. Signed in shader. + Rg32Sint, + /// Red and green channels. 32 bit float per channel. Float in shader. + Rg32Float, + /// Red, green, blue, and alpha channels. 16 bit integer per channel. Unsigned in shader. + Rgba16Uint, + /// Red, green, blue, and alpha channels. 16 bit integer per channel. Signed in shader. + Rgba16Sint, + /// Red, green, blue, and alpha channels. 16 bit integer per channel. [0, 65535] converted to/from float [0, 1] in shader. + /// + /// [`Features::TEXTURE_FORMAT_16BIT_NORM`] must be enabled to use this texture format. + Rgba16Unorm, + /// Red, green, blue, and alpha. 16 bit integer per channel. [−32767, 32767] converted to/from float [−1, 1] in shader. + /// + /// [`Features::TEXTURE_FORMAT_16BIT_NORM`] must be enabled to use this texture format. + Rgba16Snorm, + /// Red, green, blue, and alpha channels. 16 bit float per channel. Float in shader. + Rgba16Float, + + // Normal 128 bit formats + /// Red, green, blue, and alpha channels. 32 bit integer per channel. Unsigned in shader. + Rgba32Uint, + /// Red, green, blue, and alpha channels. 32 bit integer per channel. Signed in shader. + Rgba32Sint, + /// Red, green, blue, and alpha channels. 32 bit float per channel. Float in shader. + Rgba32Float, + + // Depth and stencil formats + /// Stencil format with 8 bit integer stencil. + Stencil8, + /// Special depth format with 16 bit integer depth. + Depth16Unorm, + /// Special depth format with at least 24 bit integer depth. + Depth24Plus, + /// Special depth/stencil format with at least 24 bit integer depth and 8 bits integer stencil. + Depth24PlusStencil8, + /// Special depth format with 32 bit floating point depth. + Depth32Float, + /// Special depth/stencil format with 32 bit floating point depth and 8 bits integer stencil. + /// + /// [`Features::DEPTH32FLOAT_STENCIL8`] must be enabled to use this texture format. + Depth32FloatStencil8, + + /// YUV 4:2:0 chroma subsampled format. + /// + /// Contains two planes: + /// - 0: Single 8 bit channel luminance. + /// - 1: Dual 8 bit channel chrominance at half width and half height. + /// + /// Valid view formats for luminance are [`TextureFormat::R8Unorm`]. + /// + /// Valid view formats for chrominance are [`TextureFormat::Rg8Unorm`]. + /// + /// Width and height must be even. + /// + /// [`Features::TEXTURE_FORMAT_NV12`] must be enabled to use this texture format. + NV12, + + /// YUV 4:2:0 chroma subsampled format. + /// + /// Contains two planes: + /// - 0: Single 16 bit channel luminance, of which only the high 10 bits + /// are used. + /// - 1: Dual 16 bit channel chrominance at half width and half height, of + /// which only the high 10 bits are used. + /// + /// Valid view formats for luminance are [`TextureFormat::R16Unorm`]. + /// + /// Valid view formats for chrominance are [`TextureFormat::Rg16Unorm`]. + /// + /// Width and height must be even. + /// + /// [`Features::TEXTURE_FORMAT_P010`] must be enabled to use this texture format. + P010, + + // Compressed textures usable with `TEXTURE_COMPRESSION_BC` feature. `TEXTURE_COMPRESSION_SLICED_3D` is required to use with 3D textures. + /// 4x4 block compressed texture. 8 bytes per block (4 bit/px). 4 color + alpha pallet. 5 bit R + 6 bit G + 5 bit B + 1 bit alpha. + /// [0, 63] ([0, 1] for alpha) converted to/from float [0, 1] in shader. + /// + /// Also known as DXT1. + /// + /// [`Features::TEXTURE_COMPRESSION_BC`] must be enabled to use this texture format. + /// [`Features::TEXTURE_COMPRESSION_BC_SLICED_3D`] must be enabled to use this texture format with 3D dimension. + Bc1RgbaUnorm, + /// 4x4 block compressed texture. 8 bytes per block (4 bit/px). 4 color + alpha pallet. 5 bit R + 6 bit G + 5 bit B + 1 bit alpha. + /// Srgb-color [0, 63] ([0, 1] for alpha) converted to/from linear-color float [0, 1] in shader. + /// + /// Also known as DXT1. + /// + /// [`Features::TEXTURE_COMPRESSION_BC`] must be enabled to use this texture format. + /// [`Features::TEXTURE_COMPRESSION_BC_SLICED_3D`] must be enabled to use this texture format with 3D dimension. + Bc1RgbaUnormSrgb, + /// 4x4 block compressed texture. 16 bytes per block (8 bit/px). 4 color pallet. 5 bit R + 6 bit G + 5 bit B + 4 bit alpha. + /// [0, 63] ([0, 15] for alpha) converted to/from float [0, 1] in shader. + /// + /// Also known as DXT3. + /// + /// [`Features::TEXTURE_COMPRESSION_BC`] must be enabled to use this texture format. + /// [`Features::TEXTURE_COMPRESSION_BC_SLICED_3D`] must be enabled to use this texture format with 3D dimension. + Bc2RgbaUnorm, + /// 4x4 block compressed texture. 16 bytes per block (8 bit/px). 4 color pallet. 5 bit R + 6 bit G + 5 bit B + 4 bit alpha. + /// Srgb-color [0, 63] ([0, 255] for alpha) converted to/from linear-color float [0, 1] in shader. + /// + /// Also known as DXT3. + /// + /// [`Features::TEXTURE_COMPRESSION_BC`] must be enabled to use this texture format. + /// [`Features::TEXTURE_COMPRESSION_BC_SLICED_3D`] must be enabled to use this texture format with 3D dimension. + Bc2RgbaUnormSrgb, + /// 4x4 block compressed texture. 16 bytes per block (8 bit/px). 4 color pallet + 8 alpha pallet. 5 bit R + 6 bit G + 5 bit B + 8 bit alpha. + /// [0, 63] ([0, 255] for alpha) converted to/from float [0, 1] in shader. + /// + /// Also known as DXT5. + /// + /// [`Features::TEXTURE_COMPRESSION_BC`] must be enabled to use this texture format. + /// [`Features::TEXTURE_COMPRESSION_BC_SLICED_3D`] must be enabled to use this texture format with 3D dimension. + Bc3RgbaUnorm, + /// 4x4 block compressed texture. 16 bytes per block (8 bit/px). 4 color pallet + 8 alpha pallet. 5 bit R + 6 bit G + 5 bit B + 8 bit alpha. + /// Srgb-color [0, 63] ([0, 255] for alpha) converted to/from linear-color float [0, 1] in shader. + /// + /// Also known as DXT5. + /// + /// [`Features::TEXTURE_COMPRESSION_BC`] must be enabled to use this texture format. + /// [`Features::TEXTURE_COMPRESSION_BC_SLICED_3D`] must be enabled to use this texture format with 3D dimension. + Bc3RgbaUnormSrgb, + /// 4x4 block compressed texture. 8 bytes per block (4 bit/px). 8 color pallet. 8 bit R. + /// [0, 255] converted to/from float [0, 1] in shader. + /// + /// Also known as RGTC1. + /// + /// [`Features::TEXTURE_COMPRESSION_BC`] must be enabled to use this texture format. + /// [`Features::TEXTURE_COMPRESSION_BC_SLICED_3D`] must be enabled to use this texture format with 3D dimension. + Bc4RUnorm, + /// 4x4 block compressed texture. 8 bytes per block (4 bit/px). 8 color pallet. 8 bit R. + /// [−127, 127] converted to/from float [−1, 1] in shader. + /// + /// Also known as RGTC1. + /// + /// [`Features::TEXTURE_COMPRESSION_BC`] must be enabled to use this texture format. + /// [`Features::TEXTURE_COMPRESSION_BC_SLICED_3D`] must be enabled to use this texture format with 3D dimension. + Bc4RSnorm, + /// 4x4 block compressed texture. 16 bytes per block (8 bit/px). 8 color red pallet + 8 color green pallet. 8 bit RG. + /// [0, 255] converted to/from float [0, 1] in shader. + /// + /// Also known as RGTC2. + /// + /// [`Features::TEXTURE_COMPRESSION_BC`] must be enabled to use this texture format. + /// [`Features::TEXTURE_COMPRESSION_BC_SLICED_3D`] must be enabled to use this texture format with 3D dimension. + Bc5RgUnorm, + /// 4x4 block compressed texture. 16 bytes per block (8 bit/px). 8 color red pallet + 8 color green pallet. 8 bit RG. + /// [−127, 127] converted to/from float [−1, 1] in shader. + /// + /// Also known as RGTC2. + /// + /// [`Features::TEXTURE_COMPRESSION_BC`] must be enabled to use this texture format. + /// [`Features::TEXTURE_COMPRESSION_BC_SLICED_3D`] must be enabled to use this texture format with 3D dimension. + Bc5RgSnorm, + /// 4x4 block compressed texture. 16 bytes per block (8 bit/px). Variable sized pallet. 16 bit unsigned float RGB. Float in shader. + /// + /// Also known as BPTC (float). + /// + /// [`Features::TEXTURE_COMPRESSION_BC`] must be enabled to use this texture format. + /// [`Features::TEXTURE_COMPRESSION_BC_SLICED_3D`] must be enabled to use this texture format with 3D dimension. + Bc6hRgbUfloat, + /// 4x4 block compressed texture. 16 bytes per block (8 bit/px). Variable sized pallet. 16 bit signed float RGB. Float in shader. + /// + /// Also known as BPTC (float). + /// + /// [`Features::TEXTURE_COMPRESSION_BC`] must be enabled to use this texture format. + /// [`Features::TEXTURE_COMPRESSION_BC_SLICED_3D`] must be enabled to use this texture format with 3D dimension. + Bc6hRgbFloat, + /// 4x4 block compressed texture. 16 bytes per block (8 bit/px). Variable sized pallet. 8 bit integer RGBA. + /// [0, 255] converted to/from float [0, 1] in shader. + /// + /// Also known as BPTC (unorm). + /// + /// [`Features::TEXTURE_COMPRESSION_BC`] must be enabled to use this texture format. + /// [`Features::TEXTURE_COMPRESSION_BC_SLICED_3D`] must be enabled to use this texture format with 3D dimension. + Bc7RgbaUnorm, + /// 4x4 block compressed texture. 16 bytes per block (8 bit/px). Variable sized pallet. 8 bit integer RGBA. + /// Srgb-color [0, 255] converted to/from linear-color float [0, 1] in shader. + /// + /// Also known as BPTC (unorm). + /// + /// [`Features::TEXTURE_COMPRESSION_BC`] must be enabled to use this texture format. + /// [`Features::TEXTURE_COMPRESSION_BC_SLICED_3D`] must be enabled to use this texture format with 3D dimension. + Bc7RgbaUnormSrgb, + /// 4x4 block compressed texture. 8 bytes per block (4 bit/px). Complex pallet. 8 bit integer RGB. + /// [0, 255] converted to/from float [0, 1] in shader. + /// + /// [`Features::TEXTURE_COMPRESSION_ETC2`] must be enabled to use this texture format. + Etc2Rgb8Unorm, + /// 4x4 block compressed texture. 8 bytes per block (4 bit/px). Complex pallet. 8 bit integer RGB. + /// Srgb-color [0, 255] converted to/from linear-color float [0, 1] in shader. + /// + /// [`Features::TEXTURE_COMPRESSION_ETC2`] must be enabled to use this texture format. + Etc2Rgb8UnormSrgb, + /// 4x4 block compressed texture. 8 bytes per block (4 bit/px). Complex pallet. 8 bit integer RGB + 1 bit alpha. + /// [0, 255] ([0, 1] for alpha) converted to/from float [0, 1] in shader. + /// + /// [`Features::TEXTURE_COMPRESSION_ETC2`] must be enabled to use this texture format. + Etc2Rgb8A1Unorm, + /// 4x4 block compressed texture. 8 bytes per block (4 bit/px). Complex pallet. 8 bit integer RGB + 1 bit alpha. + /// Srgb-color [0, 255] ([0, 1] for alpha) converted to/from linear-color float [0, 1] in shader. + /// + /// [`Features::TEXTURE_COMPRESSION_ETC2`] must be enabled to use this texture format. + Etc2Rgb8A1UnormSrgb, + /// 4x4 block compressed texture. 16 bytes per block (8 bit/px). Complex pallet. 8 bit integer RGB + 8 bit alpha. + /// [0, 255] converted to/from float [0, 1] in shader. + /// + /// [`Features::TEXTURE_COMPRESSION_ETC2`] must be enabled to use this texture format. + Etc2Rgba8Unorm, + /// 4x4 block compressed texture. 16 bytes per block (8 bit/px). Complex pallet. 8 bit integer RGB + 8 bit alpha. + /// Srgb-color [0, 255] converted to/from linear-color float [0, 1] in shader. + /// + /// [`Features::TEXTURE_COMPRESSION_ETC2`] must be enabled to use this texture format. + Etc2Rgba8UnormSrgb, + /// 4x4 block compressed texture. 8 bytes per block (4 bit/px). Complex pallet. 11 bit integer R. + /// [0, 255] converted to/from float [0, 1] in shader. + /// + /// [`Features::TEXTURE_COMPRESSION_ETC2`] must be enabled to use this texture format. + EacR11Unorm, + /// 4x4 block compressed texture. 8 bytes per block (4 bit/px). Complex pallet. 11 bit integer R. + /// [−127, 127] converted to/from float [−1, 1] in shader. + /// + /// [`Features::TEXTURE_COMPRESSION_ETC2`] must be enabled to use this texture format. + EacR11Snorm, + /// 4x4 block compressed texture. 16 bytes per block (8 bit/px). Complex pallet. 11 bit integer R + 11 bit integer G. + /// [0, 255] converted to/from float [0, 1] in shader. + /// + /// [`Features::TEXTURE_COMPRESSION_ETC2`] must be enabled to use this texture format. + EacRg11Unorm, + /// 4x4 block compressed texture. 16 bytes per block (8 bit/px). Complex pallet. 11 bit integer R + 11 bit integer G. + /// [−127, 127] converted to/from float [−1, 1] in shader. + /// + /// [`Features::TEXTURE_COMPRESSION_ETC2`] must be enabled to use this texture format. + EacRg11Snorm, + /// block compressed texture. 16 bytes per block. + /// + /// Features [`TEXTURE_COMPRESSION_ASTC`] or [`TEXTURE_COMPRESSION_ASTC_HDR`] + /// must be enabled to use this texture format. + /// + /// [`TEXTURE_COMPRESSION_ASTC`]: Features::TEXTURE_COMPRESSION_ASTC + /// [`TEXTURE_COMPRESSION_ASTC_HDR`]: Features::TEXTURE_COMPRESSION_ASTC_HDR + Astc { + /// compressed block dimensions + block: AstcBlock, + /// ASTC RGBA channel + channel: AstcChannel, + }, +} + +// There are some additional texture format helpers in `wgpu-core/src/conv.rs`, +// that may need to be modified along with the ones here. +impl TextureFormat { + /// Returns the aspect-specific format of the original format + /// + /// see <https://gpuweb.github.io/gpuweb/#abstract-opdef-resolving-gputextureaspect> + #[must_use] + pub fn aspect_specific_format(&self, aspect: TextureAspect) -> Option<Self> { + match (*self, aspect) { + (Self::Stencil8, TextureAspect::StencilOnly) => Some(*self), + ( + Self::Depth16Unorm | Self::Depth24Plus | Self::Depth32Float, + TextureAspect::DepthOnly, + ) => Some(*self), + ( + Self::Depth24PlusStencil8 | Self::Depth32FloatStencil8, + TextureAspect::StencilOnly, + ) => Some(Self::Stencil8), + (Self::Depth24PlusStencil8, TextureAspect::DepthOnly) => Some(Self::Depth24Plus), + (Self::Depth32FloatStencil8, TextureAspect::DepthOnly) => Some(Self::Depth32Float), + (Self::NV12, TextureAspect::Plane0) => Some(Self::R8Unorm), + (Self::NV12, TextureAspect::Plane1) => Some(Self::Rg8Unorm), + (Self::P010, TextureAspect::Plane0) => Some(Self::R16Unorm), + (Self::P010, TextureAspect::Plane1) => Some(Self::Rg16Unorm), + // views to multi-planar formats must specify the plane + (format, TextureAspect::All) if !format.is_multi_planar_format() => Some(format), + _ => None, + } + } + + /// Returns `true` if `self` is a depth or stencil component of the given + /// combined depth-stencil format + #[must_use] + pub fn is_depth_stencil_component(&self, combined_format: Self) -> bool { + match (combined_format, *self) { + (Self::Depth24PlusStencil8, Self::Depth24Plus | Self::Stencil8) + | (Self::Depth32FloatStencil8, Self::Depth32Float | Self::Stencil8) => true, + _ => false, + } + } + + /// Returns `true` if the format is a depth and/or stencil format + /// + /// see <https://gpuweb.github.io/gpuweb/#depth-formats> + #[must_use] + pub fn is_depth_stencil_format(&self) -> bool { + match *self { + Self::Stencil8 + | Self::Depth16Unorm + | Self::Depth24Plus + | Self::Depth24PlusStencil8 + | Self::Depth32Float + | Self::Depth32FloatStencil8 => true, + _ => false, + } + } + + /// Returns `true` if the format is a combined depth-stencil format + /// + /// see <https://gpuweb.github.io/gpuweb/#combined-depth-stencil-format> + #[must_use] + pub fn is_combined_depth_stencil_format(&self) -> bool { + match *self { + Self::Depth24PlusStencil8 | Self::Depth32FloatStencil8 => true, + _ => false, + } + } + + /// Returns `true` if the format is a multi-planar format + #[must_use] + pub fn is_multi_planar_format(&self) -> bool { + self.planes().is_some() + } + + /// Returns the number of planes a multi-planar format has. + #[must_use] + pub fn planes(&self) -> Option<u32> { + match *self { + Self::NV12 => Some(2), + Self::P010 => Some(2), + _ => None, + } + } + + /// Returns the subsampling factor for the indicated plane of a multi-planar format. + #[must_use] + pub fn subsampling_factors(&self, plane: Option<u32>) -> (u32, u32) { + match *self { + Self::NV12 | Self::P010 => match plane { + Some(0) => (1, 1), + Some(1) => (2, 2), + Some(plane) => unreachable!("plane {plane} is not valid for {self:?}"), + None => unreachable!("the plane must be specified for multi-planar formats"), + }, + _ => (1, 1), + } + } + + /// Returns `true` if the format has a color aspect + #[must_use] + pub fn has_color_aspect(&self) -> bool { + !self.is_depth_stencil_format() + } + + /// Returns `true` if the format has a depth aspect + #[must_use] + pub fn has_depth_aspect(&self) -> bool { + match *self { + Self::Depth16Unorm + | Self::Depth24Plus + | Self::Depth24PlusStencil8 + | Self::Depth32Float + | Self::Depth32FloatStencil8 => true, + _ => false, + } + } + + /// Returns `true` if the format has a stencil aspect + #[must_use] + pub fn has_stencil_aspect(&self) -> bool { + match *self { + Self::Stencil8 | Self::Depth24PlusStencil8 | Self::Depth32FloatStencil8 => true, + _ => false, + } + } + + /// Returns the size multiple requirement for a texture using this format. + /// + /// `create_texture` currently enforces a stricter restriction than this for + /// mipmapped multi-planar formats. + /// TODO(<https://github.com/gfx-rs/wgpu/issues/8491>): Remove this note. + #[must_use] + pub fn size_multiple_requirement(&self) -> (u32, u32) { + match *self { + Self::NV12 => (2, 2), + Self::P010 => (2, 2), + _ => self.block_dimensions(), + } + } + + /// Returns the dimension of a [block](https://gpuweb.github.io/gpuweb/#texel-block) of texels. + /// + /// Uncompressed formats have a block dimension of `(1, 1)`. + #[must_use] + pub fn block_dimensions(&self) -> (u32, u32) { + match *self { + Self::R8Unorm + | Self::R8Snorm + | Self::R8Uint + | Self::R8Sint + | Self::R16Uint + | Self::R16Sint + | Self::R16Unorm + | Self::R16Snorm + | Self::R16Float + | Self::Rg8Unorm + | Self::Rg8Snorm + | Self::Rg8Uint + | Self::Rg8Sint + | Self::R32Uint + | Self::R32Sint + | Self::R32Float + | Self::Rg16Uint + | Self::Rg16Sint + | Self::Rg16Unorm + | Self::Rg16Snorm + | Self::Rg16Float + | Self::Rgba8Unorm + | Self::Rgba8UnormSrgb + | Self::Rgba8Snorm + | Self::Rgba8Uint + | Self::Rgba8Sint + | Self::Bgra8Unorm + | Self::Bgra8UnormSrgb + | Self::Rgb9e5Ufloat + | Self::Rgb10a2Uint + | Self::Rgb10a2Unorm + | Self::Rg11b10Ufloat + | Self::R64Uint + | Self::Rg32Uint + | Self::Rg32Sint + | Self::Rg32Float + | Self::Rgba16Uint + | Self::Rgba16Sint + | Self::Rgba16Unorm + | Self::Rgba16Snorm + | Self::Rgba16Float + | Self::Rgba32Uint + | Self::Rgba32Sint + | Self::Rgba32Float + | Self::Stencil8 + | Self::Depth16Unorm + | Self::Depth24Plus + | Self::Depth24PlusStencil8 + | Self::Depth32Float + | Self::Depth32FloatStencil8 + | Self::NV12 + | Self::P010 => (1, 1), + + Self::Bc1RgbaUnorm + | Self::Bc1RgbaUnormSrgb + | Self::Bc2RgbaUnorm + | Self::Bc2RgbaUnormSrgb + | Self::Bc3RgbaUnorm + | Self::Bc3RgbaUnormSrgb + | Self::Bc4RUnorm + | Self::Bc4RSnorm + | Self::Bc5RgUnorm + | Self::Bc5RgSnorm + | Self::Bc6hRgbUfloat + | Self::Bc6hRgbFloat + | Self::Bc7RgbaUnorm + | Self::Bc7RgbaUnormSrgb => (4, 4), + + Self::Etc2Rgb8Unorm + | Self::Etc2Rgb8UnormSrgb + | Self::Etc2Rgb8A1Unorm + | Self::Etc2Rgb8A1UnormSrgb + | Self::Etc2Rgba8Unorm + | Self::Etc2Rgba8UnormSrgb + | Self::EacR11Unorm + | Self::EacR11Snorm + | Self::EacRg11Unorm + | Self::EacRg11Snorm => (4, 4), + + Self::Astc { block, .. } => match block { + AstcBlock::B4x4 => (4, 4), + AstcBlock::B5x4 => (5, 4), + AstcBlock::B5x5 => (5, 5), + AstcBlock::B6x5 => (6, 5), + AstcBlock::B6x6 => (6, 6), + AstcBlock::B8x5 => (8, 5), + AstcBlock::B8x6 => (8, 6), + AstcBlock::B8x8 => (8, 8), + AstcBlock::B10x5 => (10, 5), + AstcBlock::B10x6 => (10, 6), + AstcBlock::B10x8 => (10, 8), + AstcBlock::B10x10 => (10, 10), + AstcBlock::B12x10 => (12, 10), + AstcBlock::B12x12 => (12, 12), + }, + } + } + + /// Returns `true` for compressed formats. + #[must_use] + pub fn is_compressed(&self) -> bool { + self.block_dimensions() != (1, 1) + } + + /// Returns `true` for BCn compressed formats. + #[must_use] + pub fn is_bcn(&self) -> bool { + self.required_features() == Features::TEXTURE_COMPRESSION_BC + } + + /// Returns `true` for ASTC compressed formats. + #[must_use] + pub fn is_astc(&self) -> bool { + self.required_features() == Features::TEXTURE_COMPRESSION_ASTC + || self.required_features() == Features::TEXTURE_COMPRESSION_ASTC_HDR + } + + /// Returns the required features (if any) in order to use the texture. + #[must_use] + pub fn required_features(&self) -> Features { + match *self { + Self::R8Unorm + | Self::R8Snorm + | Self::R8Uint + | Self::R8Sint + | Self::R16Uint + | Self::R16Sint + | Self::R16Float + | Self::Rg8Unorm + | Self::Rg8Snorm + | Self::Rg8Uint + | Self::Rg8Sint + | Self::R32Uint + | Self::R32Sint + | Self::R32Float + | Self::Rg16Uint + | Self::Rg16Sint + | Self::Rg16Float + | Self::Rgba8Unorm + | Self::Rgba8UnormSrgb + | Self::Rgba8Snorm + | Self::Rgba8Uint + | Self::Rgba8Sint + | Self::Bgra8Unorm + | Self::Bgra8UnormSrgb + | Self::Rgb9e5Ufloat + | Self::Rgb10a2Uint + | Self::Rgb10a2Unorm + | Self::Rg11b10Ufloat + | Self::Rg32Uint + | Self::Rg32Sint + | Self::Rg32Float + | Self::Rgba16Uint + | Self::Rgba16Sint + | Self::Rgba16Float + | Self::Rgba32Uint + | Self::Rgba32Sint + | Self::Rgba32Float + | Self::Stencil8 + | Self::Depth16Unorm + | Self::Depth24Plus + | Self::Depth24PlusStencil8 + | Self::Depth32Float => Features::empty(), + + Self::R64Uint => Features::TEXTURE_INT64_ATOMIC, + + Self::Depth32FloatStencil8 => Features::DEPTH32FLOAT_STENCIL8, + + Self::NV12 => Features::TEXTURE_FORMAT_NV12, + Self::P010 => Features::TEXTURE_FORMAT_P010, + + Self::R16Unorm + | Self::R16Snorm + | Self::Rg16Unorm + | Self::Rg16Snorm + | Self::Rgba16Unorm + | Self::Rgba16Snorm => Features::TEXTURE_FORMAT_16BIT_NORM, + + Self::Bc1RgbaUnorm + | Self::Bc1RgbaUnormSrgb + | Self::Bc2RgbaUnorm + | Self::Bc2RgbaUnormSrgb + | Self::Bc3RgbaUnorm + | Self::Bc3RgbaUnormSrgb + | Self::Bc4RUnorm + | Self::Bc4RSnorm + | Self::Bc5RgUnorm + | Self::Bc5RgSnorm + | Self::Bc6hRgbUfloat + | Self::Bc6hRgbFloat + | Self::Bc7RgbaUnorm + | Self::Bc7RgbaUnormSrgb => Features::TEXTURE_COMPRESSION_BC, + + Self::Etc2Rgb8Unorm + | Self::Etc2Rgb8UnormSrgb + | Self::Etc2Rgb8A1Unorm + | Self::Etc2Rgb8A1UnormSrgb + | Self::Etc2Rgba8Unorm + | Self::Etc2Rgba8UnormSrgb + | Self::EacR11Unorm + | Self::EacR11Snorm + | Self::EacRg11Unorm + | Self::EacRg11Snorm => Features::TEXTURE_COMPRESSION_ETC2, + + Self::Astc { channel, .. } => match channel { + AstcChannel::Hdr => Features::TEXTURE_COMPRESSION_ASTC_HDR, + AstcChannel::Unorm | AstcChannel::UnormSrgb => Features::TEXTURE_COMPRESSION_ASTC, + }, + } + } + + /// Returns the format features guaranteed by the WebGPU spec. + /// + /// Additional features are available if `Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES` is enabled. + #[must_use] + pub fn guaranteed_format_features(&self, device_features: Features) -> TextureFormatFeatures { + // Multisampling + let none = TextureFormatFeatureFlags::empty(); + let msaa = TextureFormatFeatureFlags::MULTISAMPLE_X4; + let msaa_resolve = msaa | TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE; + + let s_ro_wo = TextureFormatFeatureFlags::STORAGE_READ_ONLY + | TextureFormatFeatureFlags::STORAGE_WRITE_ONLY; + let s_all = s_ro_wo | TextureFormatFeatureFlags::STORAGE_READ_WRITE; + + // Flags + let basic = + TextureUsages::COPY_SRC | TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING; + let attachment = basic | TextureUsages::RENDER_ATTACHMENT | TextureUsages::TRANSIENT; + let storage = basic | TextureUsages::STORAGE_BINDING; + let binding = TextureUsages::TEXTURE_BINDING; + let all_flags = attachment | storage | binding; + let atomic_64 = if device_features.contains(Features::TEXTURE_ATOMIC) { + storage | binding | TextureUsages::STORAGE_ATOMIC + } else { + storage | binding + }; + let atomic = attachment | atomic_64; + let (rg11b10f_f, rg11b10f_u) = + if device_features.contains(Features::RG11B10UFLOAT_RENDERABLE) { + (msaa_resolve, attachment) + } else { + (msaa, basic) + }; + let (bgra8unorm_f, bgra8unorm) = if device_features.contains(Features::BGRA8UNORM_STORAGE) { + ( + msaa_resolve | TextureFormatFeatureFlags::STORAGE_WRITE_ONLY, + attachment | TextureUsages::STORAGE_BINDING, + ) + } else { + (msaa_resolve, attachment) + }; + + #[rustfmt::skip] // lets make a nice table + let ( + mut flags, + allowed_usages, + ) = match *self { + Self::R8Unorm => (msaa_resolve, attachment), + Self::R8Snorm => ( none, basic), + Self::R8Uint => ( msaa, attachment), + Self::R8Sint => ( msaa, attachment), + Self::R16Uint => ( msaa, attachment), + Self::R16Sint => ( msaa, attachment), + Self::R16Float => (msaa_resolve, attachment), + Self::Rg8Unorm => (msaa_resolve, attachment), + Self::Rg8Snorm => ( none, basic), + Self::Rg8Uint => ( msaa, attachment), + Self::Rg8Sint => ( msaa, attachment), + Self::R32Uint => ( s_all, atomic), + Self::R32Sint => ( s_all, atomic), + Self::R32Float => (msaa | s_all, all_flags), + Self::Rg16Uint => ( msaa, attachment), + Self::Rg16Sint => ( msaa, attachment), + Self::Rg16Float => (msaa_resolve, attachment), + Self::Rgba8Unorm => (msaa_resolve | s_ro_wo, all_flags), + Self::Rgba8UnormSrgb => (msaa_resolve, attachment), + Self::Rgba8Snorm => ( s_ro_wo, storage), + Self::Rgba8Uint => ( msaa | s_ro_wo, all_flags), + Self::Rgba8Sint => ( msaa | s_ro_wo, all_flags), + Self::Bgra8Unorm => (bgra8unorm_f, bgra8unorm), + Self::Bgra8UnormSrgb => (msaa_resolve, attachment), + Self::Rgb10a2Uint => ( msaa, attachment), + Self::Rgb10a2Unorm => (msaa_resolve, attachment), + Self::Rg11b10Ufloat => ( rg11b10f_f, rg11b10f_u), + Self::R64Uint => ( s_ro_wo, atomic_64), + Self::Rg32Uint => ( s_ro_wo, all_flags), + Self::Rg32Sint => ( s_ro_wo, all_flags), + Self::Rg32Float => ( s_ro_wo, all_flags), + Self::Rgba16Uint => ( msaa | s_ro_wo, all_flags), + Self::Rgba16Sint => ( msaa | s_ro_wo, all_flags), + Self::Rgba16Float => (msaa_resolve | s_ro_wo, all_flags), + Self::Rgba32Uint => ( s_ro_wo, all_flags), + Self::Rgba32Sint => ( s_ro_wo, all_flags), + Self::Rgba32Float => ( s_ro_wo, all_flags), + + Self::Stencil8 => ( msaa, attachment), + Self::Depth16Unorm => ( msaa, attachment), + Self::Depth24Plus => ( msaa, attachment), + Self::Depth24PlusStencil8 => ( msaa, attachment), + Self::Depth32Float => ( msaa, attachment), + Self::Depth32FloatStencil8 => ( msaa, attachment), + + // We only support sampling nv12 and p010 textures until we + // implement transfer plane data. + Self::NV12 => ( none, binding), + Self::P010 => ( none, binding), + + Self::R16Unorm => ( msaa | s_ro_wo, storage), + Self::R16Snorm => ( msaa | s_ro_wo, storage), + Self::Rg16Unorm => ( msaa | s_ro_wo, storage), + Self::Rg16Snorm => ( msaa | s_ro_wo, storage), + Self::Rgba16Unorm => ( msaa | s_ro_wo, storage), + Self::Rgba16Snorm => ( msaa | s_ro_wo, storage), + + Self::Rgb9e5Ufloat => ( none, basic), + + Self::Bc1RgbaUnorm => ( none, basic), + Self::Bc1RgbaUnormSrgb => ( none, basic), + Self::Bc2RgbaUnorm => ( none, basic), + Self::Bc2RgbaUnormSrgb => ( none, basic), + Self::Bc3RgbaUnorm => ( none, basic), + Self::Bc3RgbaUnormSrgb => ( none, basic), + Self::Bc4RUnorm => ( none, basic), + Self::Bc4RSnorm => ( none, basic), + Self::Bc5RgUnorm => ( none, basic), + Self::Bc5RgSnorm => ( none, basic), + Self::Bc6hRgbUfloat => ( none, basic), + Self::Bc6hRgbFloat => ( none, basic), + Self::Bc7RgbaUnorm => ( none, basic), + Self::Bc7RgbaUnormSrgb => ( none, basic), + + Self::Etc2Rgb8Unorm => ( none, basic), + Self::Etc2Rgb8UnormSrgb => ( none, basic), + Self::Etc2Rgb8A1Unorm => ( none, basic), + Self::Etc2Rgb8A1UnormSrgb => ( none, basic), + Self::Etc2Rgba8Unorm => ( none, basic), + Self::Etc2Rgba8UnormSrgb => ( none, basic), + Self::EacR11Unorm => ( none, basic), + Self::EacR11Snorm => ( none, basic), + Self::EacRg11Unorm => ( none, basic), + Self::EacRg11Snorm => ( none, basic), + + Self::Astc { .. } => ( none, basic), + }; + + // Get whether the format is filterable, taking features into account + let sample_type1 = self.sample_type(None, Some(device_features)); + let is_filterable = sample_type1 == Some(TextureSampleType::Float { filterable: true }); + + // Features that enable filtering don't affect blendability + let sample_type2 = self.sample_type(None, None); + let is_blendable = sample_type2 == Some(TextureSampleType::Float { filterable: true }); + + flags.set(TextureFormatFeatureFlags::FILTERABLE, is_filterable); + flags.set(TextureFormatFeatureFlags::BLENDABLE, is_blendable); + flags.set( + TextureFormatFeatureFlags::STORAGE_ATOMIC, + allowed_usages.contains(TextureUsages::STORAGE_ATOMIC), + ); + + TextureFormatFeatures { + allowed_usages, + flags, + } + } + + /// Returns the sample type compatible with this format and aspect. + /// + /// Returns `None` only if this is a combined depth-stencil format or a multi-planar format + /// and `TextureAspect::All` or no `aspect` was provided. + #[must_use] + pub fn sample_type( + &self, + aspect: Option<TextureAspect>, + device_features: Option<Features>, + ) -> Option<TextureSampleType> { + let float = TextureSampleType::Float { filterable: true }; + let unfilterable_float = TextureSampleType::Float { filterable: false }; + let float32_sample_type = TextureSampleType::Float { + filterable: device_features + .unwrap_or(Features::empty()) + .contains(Features::FLOAT32_FILTERABLE), + }; + let depth = TextureSampleType::Depth; + let uint = TextureSampleType::Uint; + let sint = TextureSampleType::Sint; + + match *self { + Self::R8Unorm + | Self::R8Snorm + | Self::Rg8Unorm + | Self::Rg8Snorm + | Self::Rgba8Unorm + | Self::Rgba8UnormSrgb + | Self::Rgba8Snorm + | Self::Bgra8Unorm + | Self::Bgra8UnormSrgb + | Self::R16Float + | Self::Rg16Float + | Self::Rgba16Float + | Self::Rgb10a2Unorm + | Self::Rg11b10Ufloat => Some(float), + + Self::R32Float | Self::Rg32Float | Self::Rgba32Float => Some(float32_sample_type), + + Self::R8Uint + | Self::Rg8Uint + | Self::Rgba8Uint + | Self::R16Uint + | Self::Rg16Uint + | Self::Rgba16Uint + | Self::R32Uint + | Self::R64Uint + | Self::Rg32Uint + | Self::Rgba32Uint + | Self::Rgb10a2Uint => Some(uint), + + Self::R8Sint + | Self::Rg8Sint + | Self::Rgba8Sint + | Self::R16Sint + | Self::Rg16Sint + | Self::Rgba16Sint + | Self::R32Sint + | Self::Rg32Sint + | Self::Rgba32Sint => Some(sint), + + Self::Stencil8 => Some(uint), + Self::Depth16Unorm | Self::Depth24Plus | Self::Depth32Float => Some(depth), + Self::Depth24PlusStencil8 | Self::Depth32FloatStencil8 => match aspect { + Some(TextureAspect::DepthOnly) => Some(depth), + Some(TextureAspect::StencilOnly) => Some(uint), + _ => None, + }, + + Self::NV12 | Self::P010 => match aspect { + Some(TextureAspect::Plane0) | Some(TextureAspect::Plane1) => { + Some(unfilterable_float) + } + _ => None, + }, + + Self::R16Unorm + | Self::R16Snorm + | Self::Rg16Unorm + | Self::Rg16Snorm + | Self::Rgba16Unorm + | Self::Rgba16Snorm => Some(float), + + Self::Rgb9e5Ufloat => Some(float), + + Self::Bc1RgbaUnorm + | Self::Bc1RgbaUnormSrgb + | Self::Bc2RgbaUnorm + | Self::Bc2RgbaUnormSrgb + | Self::Bc3RgbaUnorm + | Self::Bc3RgbaUnormSrgb + | Self::Bc4RUnorm + | Self::Bc4RSnorm + | Self::Bc5RgUnorm + | Self::Bc5RgSnorm + | Self::Bc6hRgbUfloat + | Self::Bc6hRgbFloat + | Self::Bc7RgbaUnorm + | Self::Bc7RgbaUnormSrgb => Some(float), + + Self::Etc2Rgb8Unorm + | Self::Etc2Rgb8UnormSrgb + | Self::Etc2Rgb8A1Unorm + | Self::Etc2Rgb8A1UnormSrgb + | Self::Etc2Rgba8Unorm + | Self::Etc2Rgba8UnormSrgb + | Self::EacR11Unorm + | Self::EacR11Snorm + | Self::EacRg11Unorm + | Self::EacRg11Snorm => Some(float), + + Self::Astc { .. } => Some(float), + } + } + + /// The number of bytes one [texel block](https://gpuweb.github.io/gpuweb/#texel-block) occupies during an image copy, if applicable. + /// + /// Known as the [texel block copy footprint](https://gpuweb.github.io/gpuweb/#texel-block-copy-footprint). + /// + /// Note that for uncompressed formats this is the same as the size of a single texel, + /// since uncompressed formats have a block size of 1x1. + /// + /// Returns `None` if any of the following are true: + /// - the format is a combined depth-stencil and no `aspect` was provided + /// - the format is a multi-planar format and no `aspect` was provided + /// - the format is `Depth24Plus` + /// - the format is `Depth24PlusStencil8` and `aspect` is depth. + #[deprecated(since = "0.19.0", note = "Use `block_copy_size` instead.")] + #[must_use] + pub fn block_size(&self, aspect: Option<TextureAspect>) -> Option<u32> { + self.block_copy_size(aspect) + } + + /// The number of bytes one [texel block](https://gpuweb.github.io/gpuweb/#texel-block) occupies during an image copy, if applicable. + /// + /// Known as the [texel block copy footprint](https://gpuweb.github.io/gpuweb/#texel-block-copy-footprint). + /// + /// Note that for uncompressed formats this is the same as the size of a single texel, + /// since uncompressed formats have a block size of 1x1. + /// + /// Returns `None` if any of the following are true: + /// - the format is a combined depth-stencil and no `aspect` was provided + /// - the format is a multi-planar format and no `aspect` was provided + /// - the format is `Depth24Plus` + /// - the format is `Depth24PlusStencil8` and `aspect` is depth. + #[must_use] + pub fn block_copy_size(&self, aspect: Option<TextureAspect>) -> Option<u32> { + match *self { + Self::R8Unorm | Self::R8Snorm | Self::R8Uint | Self::R8Sint => Some(1), + + Self::Rg8Unorm | Self::Rg8Snorm | Self::Rg8Uint | Self::Rg8Sint => Some(2), + Self::R16Unorm | Self::R16Snorm | Self::R16Uint | Self::R16Sint | Self::R16Float => { + Some(2) + } + + Self::Rgba8Unorm + | Self::Rgba8UnormSrgb + | Self::Rgba8Snorm + | Self::Rgba8Uint + | Self::Rgba8Sint + | Self::Bgra8Unorm + | Self::Bgra8UnormSrgb => Some(4), + Self::Rg16Unorm + | Self::Rg16Snorm + | Self::Rg16Uint + | Self::Rg16Sint + | Self::Rg16Float => Some(4), + Self::R32Uint | Self::R32Sint | Self::R32Float => Some(4), + Self::Rgb9e5Ufloat | Self::Rgb10a2Uint | Self::Rgb10a2Unorm | Self::Rg11b10Ufloat => { + Some(4) + } + + Self::Rgba16Unorm + | Self::Rgba16Snorm + | Self::Rgba16Uint + | Self::Rgba16Sint + | Self::Rgba16Float => Some(8), + Self::R64Uint | Self::Rg32Uint | Self::Rg32Sint | Self::Rg32Float => Some(8), + + Self::Rgba32Uint | Self::Rgba32Sint | Self::Rgba32Float => Some(16), + + Self::Stencil8 => Some(1), + Self::Depth16Unorm => Some(2), + Self::Depth32Float => Some(4), + Self::Depth24Plus => None, + Self::Depth24PlusStencil8 => match aspect { + Some(TextureAspect::DepthOnly) => None, + Some(TextureAspect::StencilOnly) => Some(1), + _ => None, + }, + Self::Depth32FloatStencil8 => match aspect { + Some(TextureAspect::DepthOnly) => Some(4), + Some(TextureAspect::StencilOnly) => Some(1), + _ => None, + }, + + Self::NV12 => match aspect { + Some(TextureAspect::Plane0) => Some(1), + Some(TextureAspect::Plane1) => Some(2), + _ => None, + }, + + Self::P010 => match aspect { + Some(TextureAspect::Plane0) => Some(2), + Some(TextureAspect::Plane1) => Some(4), + _ => None, + }, + + Self::Bc1RgbaUnorm | Self::Bc1RgbaUnormSrgb | Self::Bc4RUnorm | Self::Bc4RSnorm => { + Some(8) + } + Self::Bc2RgbaUnorm + | Self::Bc2RgbaUnormSrgb + | Self::Bc3RgbaUnorm + | Self::Bc3RgbaUnormSrgb + | Self::Bc5RgUnorm + | Self::Bc5RgSnorm + | Self::Bc6hRgbUfloat + | Self::Bc6hRgbFloat + | Self::Bc7RgbaUnorm + | Self::Bc7RgbaUnormSrgb => Some(16), + + Self::Etc2Rgb8Unorm + | Self::Etc2Rgb8UnormSrgb + | Self::Etc2Rgb8A1Unorm + | Self::Etc2Rgb8A1UnormSrgb + | Self::EacR11Unorm + | Self::EacR11Snorm => Some(8), + Self::Etc2Rgba8Unorm + | Self::Etc2Rgba8UnormSrgb + | Self::EacRg11Unorm + | Self::EacRg11Snorm => Some(16), + + Self::Astc { .. } => Some(16), + } + } + + /// The largest number that can be returned by [`Self::target_pixel_byte_cost`]. + pub const MAX_TARGET_PIXEL_BYTE_COST: u32 = 16; + + /// The number of bytes occupied per pixel in a color attachment + /// <https://gpuweb.github.io/gpuweb/#render-target-pixel-byte-cost> + #[must_use] + pub fn target_pixel_byte_cost(&self) -> Option<u32> { + match *self { + Self::R8Unorm | Self::R8Snorm | Self::R8Uint | Self::R8Sint => Some(1), + Self::Rg8Unorm + | Self::Rg8Snorm + | Self::Rg8Uint + | Self::Rg8Sint + | Self::R16Uint + | Self::R16Sint + | Self::R16Unorm + | Self::R16Snorm + | Self::R16Float => Some(2), + Self::Rgba8Uint + | Self::Rgba8Sint + | Self::Rg16Uint + | Self::Rg16Sint + | Self::Rg16Unorm + | Self::Rg16Snorm + | Self::Rg16Float + | Self::R32Uint + | Self::R32Sint + | Self::R32Float => Some(4), + // Despite being 4 bytes per pixel, these are 8 bytes per pixel in the table + Self::Rgba8Unorm + | Self::Rgba8UnormSrgb + | Self::Rgba8Snorm + | Self::Bgra8Unorm + | Self::Bgra8UnormSrgb + // --- + | Self::Rgba16Uint + | Self::Rgba16Sint + | Self::Rgba16Unorm + | Self::Rgba16Snorm + | Self::Rgba16Float + | Self::R64Uint + | Self::Rg32Uint + | Self::Rg32Sint + | Self::Rg32Float + | Self::Rgb10a2Uint + | Self::Rgb10a2Unorm + | Self::Rg11b10Ufloat => Some(8), + Self::Rgba32Uint | Self::Rgba32Sint | Self::Rgba32Float => Some(16), + // ⚠️ If you add formats with larger sizes, make sure you change `MAX_TARGET_PIXEL_BYTE_COST`` ⚠️ + Self::Stencil8 + | Self::Depth16Unorm + | Self::Depth24Plus + | Self::Depth24PlusStencil8 + | Self::Depth32Float + | Self::Depth32FloatStencil8 + | Self::NV12 + | Self::P010 + | Self::Rgb9e5Ufloat + | Self::Bc1RgbaUnorm + | Self::Bc1RgbaUnormSrgb + | Self::Bc2RgbaUnorm + | Self::Bc2RgbaUnormSrgb + | Self::Bc3RgbaUnorm + | Self::Bc3RgbaUnormSrgb + | Self::Bc4RUnorm + | Self::Bc4RSnorm + | Self::Bc5RgUnorm + | Self::Bc5RgSnorm + | Self::Bc6hRgbUfloat + | Self::Bc6hRgbFloat + | Self::Bc7RgbaUnorm + | Self::Bc7RgbaUnormSrgb + | Self::Etc2Rgb8Unorm + | Self::Etc2Rgb8UnormSrgb + | Self::Etc2Rgb8A1Unorm + | Self::Etc2Rgb8A1UnormSrgb + | Self::Etc2Rgba8Unorm + | Self::Etc2Rgba8UnormSrgb + | Self::EacR11Unorm + | Self::EacR11Snorm + | Self::EacRg11Unorm + | Self::EacRg11Snorm + | Self::Astc { .. } => None, + } + } + + /// See <https://gpuweb.github.io/gpuweb/#render-target-component-alignment> + #[must_use] + pub fn target_component_alignment(&self) -> Option<u32> { + match *self { + Self::R8Unorm + | Self::R8Snorm + | Self::R8Uint + | Self::R8Sint + | Self::Rg8Unorm + | Self::Rg8Snorm + | Self::Rg8Uint + | Self::Rg8Sint + | Self::Rgba8Unorm + | Self::Rgba8UnormSrgb + | Self::Rgba8Snorm + | Self::Rgba8Uint + | Self::Rgba8Sint + | Self::Bgra8Unorm + | Self::Bgra8UnormSrgb => Some(1), + Self::R16Uint + | Self::R16Sint + | Self::R16Unorm + | Self::R16Snorm + | Self::R16Float + | Self::Rg16Uint + | Self::Rg16Sint + | Self::Rg16Unorm + | Self::Rg16Snorm + | Self::Rg16Float + | Self::Rgba16Uint + | Self::Rgba16Sint + | Self::Rgba16Unorm + | Self::Rgba16Snorm + | Self::Rgba16Float => Some(2), + Self::R32Uint + | Self::R32Sint + | Self::R32Float + | Self::R64Uint + | Self::Rg32Uint + | Self::Rg32Sint + | Self::Rg32Float + | Self::Rgba32Uint + | Self::Rgba32Sint + | Self::Rgba32Float + | Self::Rgb10a2Uint + | Self::Rgb10a2Unorm + | Self::Rg11b10Ufloat => Some(4), + Self::Stencil8 + | Self::Depth16Unorm + | Self::Depth24Plus + | Self::Depth24PlusStencil8 + | Self::Depth32Float + | Self::Depth32FloatStencil8 + | Self::NV12 + | Self::P010 + | Self::Rgb9e5Ufloat + | Self::Bc1RgbaUnorm + | Self::Bc1RgbaUnormSrgb + | Self::Bc2RgbaUnorm + | Self::Bc2RgbaUnormSrgb + | Self::Bc3RgbaUnorm + | Self::Bc3RgbaUnormSrgb + | Self::Bc4RUnorm + | Self::Bc4RSnorm + | Self::Bc5RgUnorm + | Self::Bc5RgSnorm + | Self::Bc6hRgbUfloat + | Self::Bc6hRgbFloat + | Self::Bc7RgbaUnorm + | Self::Bc7RgbaUnormSrgb + | Self::Etc2Rgb8Unorm + | Self::Etc2Rgb8UnormSrgb + | Self::Etc2Rgb8A1Unorm + | Self::Etc2Rgb8A1UnormSrgb + | Self::Etc2Rgba8Unorm + | Self::Etc2Rgba8UnormSrgb + | Self::EacR11Unorm + | Self::EacR11Snorm + | Self::EacRg11Unorm + | Self::EacRg11Snorm + | Self::Astc { .. } => None, + } + } + + /// Returns the number of components this format has. + #[must_use] + pub fn components(&self) -> u8 { + self.components_with_aspect(TextureAspect::All) + } + + /// Returns the number of components this format has taking into account the `aspect`. + /// + /// The `aspect` is only relevant for combined depth-stencil formats and multi-planar formats. + #[must_use] + pub fn components_with_aspect(&self, aspect: TextureAspect) -> u8 { + match *self { + Self::R8Unorm + | Self::R8Snorm + | Self::R8Uint + | Self::R8Sint + | Self::R16Unorm + | Self::R16Snorm + | Self::R16Uint + | Self::R16Sint + | Self::R16Float + | Self::R32Uint + | Self::R32Sint + | Self::R32Float + | Self::R64Uint => 1, + + Self::Rg8Unorm + | Self::Rg8Snorm + | Self::Rg8Uint + | Self::Rg8Sint + | Self::Rg16Unorm + | Self::Rg16Snorm + | Self::Rg16Uint + | Self::Rg16Sint + | Self::Rg16Float + | Self::Rg32Uint + | Self::Rg32Sint + | Self::Rg32Float => 2, + + Self::Rgba8Unorm + | Self::Rgba8UnormSrgb + | Self::Rgba8Snorm + | Self::Rgba8Uint + | Self::Rgba8Sint + | Self::Bgra8Unorm + | Self::Bgra8UnormSrgb + | Self::Rgba16Unorm + | Self::Rgba16Snorm + | Self::Rgba16Uint + | Self::Rgba16Sint + | Self::Rgba16Float + | Self::Rgba32Uint + | Self::Rgba32Sint + | Self::Rgba32Float => 4, + + Self::Rgb9e5Ufloat | Self::Rg11b10Ufloat => 3, + Self::Rgb10a2Uint | Self::Rgb10a2Unorm => 4, + + Self::Stencil8 | Self::Depth16Unorm | Self::Depth24Plus | Self::Depth32Float => 1, + + Self::Depth24PlusStencil8 | Self::Depth32FloatStencil8 => match aspect { + TextureAspect::DepthOnly | TextureAspect::StencilOnly => 1, + _ => 2, + }, + + Self::NV12 | Self::P010 => match aspect { + TextureAspect::Plane0 => 1, + TextureAspect::Plane1 => 2, + _ => 3, + }, + + Self::Bc4RUnorm | Self::Bc4RSnorm => 1, + Self::Bc5RgUnorm | Self::Bc5RgSnorm => 2, + Self::Bc6hRgbUfloat | Self::Bc6hRgbFloat => 3, + Self::Bc1RgbaUnorm + | Self::Bc1RgbaUnormSrgb + | Self::Bc2RgbaUnorm + | Self::Bc2RgbaUnormSrgb + | Self::Bc3RgbaUnorm + | Self::Bc3RgbaUnormSrgb + | Self::Bc7RgbaUnorm + | Self::Bc7RgbaUnormSrgb => 4, + + Self::EacR11Unorm | Self::EacR11Snorm => 1, + Self::EacRg11Unorm | Self::EacRg11Snorm => 2, + Self::Etc2Rgb8Unorm | Self::Etc2Rgb8UnormSrgb => 3, + Self::Etc2Rgb8A1Unorm + | Self::Etc2Rgb8A1UnormSrgb + | Self::Etc2Rgba8Unorm + | Self::Etc2Rgba8UnormSrgb => 4, + + Self::Astc { .. } => 4, + } + } + + /// Strips the `Srgb` suffix from the given texture format. + #[must_use] + pub fn remove_srgb_suffix(&self) -> TextureFormat { + match *self { + Self::Rgba8UnormSrgb => Self::Rgba8Unorm, + Self::Bgra8UnormSrgb => Self::Bgra8Unorm, + Self::Bc1RgbaUnormSrgb => Self::Bc1RgbaUnorm, + Self::Bc2RgbaUnormSrgb => Self::Bc2RgbaUnorm, + Self::Bc3RgbaUnormSrgb => Self::Bc3RgbaUnorm, + Self::Bc7RgbaUnormSrgb => Self::Bc7RgbaUnorm, + Self::Etc2Rgb8UnormSrgb => Self::Etc2Rgb8Unorm, + Self::Etc2Rgb8A1UnormSrgb => Self::Etc2Rgb8A1Unorm, + Self::Etc2Rgba8UnormSrgb => Self::Etc2Rgba8Unorm, + Self::Astc { + block, + channel: AstcChannel::UnormSrgb, + } => Self::Astc { + block, + channel: AstcChannel::Unorm, + }, + _ => *self, + } + } + + /// Adds an `Srgb` suffix to the given texture format, if the format supports it. + #[must_use] + pub fn add_srgb_suffix(&self) -> TextureFormat { + match *self { + Self::Rgba8Unorm => Self::Rgba8UnormSrgb, + Self::Bgra8Unorm => Self::Bgra8UnormSrgb, + Self::Bc1RgbaUnorm => Self::Bc1RgbaUnormSrgb, + Self::Bc2RgbaUnorm => Self::Bc2RgbaUnormSrgb, + Self::Bc3RgbaUnorm => Self::Bc3RgbaUnormSrgb, + Self::Bc7RgbaUnorm => Self::Bc7RgbaUnormSrgb, + Self::Etc2Rgb8Unorm => Self::Etc2Rgb8UnormSrgb, + Self::Etc2Rgb8A1Unorm => Self::Etc2Rgb8A1UnormSrgb, + Self::Etc2Rgba8Unorm => Self::Etc2Rgba8UnormSrgb, + Self::Astc { + block, + channel: AstcChannel::Unorm, + } => Self::Astc { + block, + channel: AstcChannel::UnormSrgb, + }, + _ => *self, + } + } + + /// Returns `true` for srgb formats. + #[must_use] + pub fn is_srgb(&self) -> bool { + *self != self.remove_srgb_suffix() + } + + /// Returns the theoretical memory footprint of a texture with the given format and dimensions. + /// + /// Actual memory usage may greatly exceed this value due to alignment and padding. + #[must_use] + pub fn theoretical_memory_footprint(&self, size: crate::Extent3d) -> u64 { + let (block_width, block_height) = self.block_dimensions(); + + let block_size = self.block_copy_size(None); + + let approximate_block_size = match block_size { + Some(size) => size, + None => match self { + // One f16 per pixel + Self::Depth16Unorm => 2, + // One u24 per pixel, padded to 4 bytes + Self::Depth24Plus => 4, + // One u24 per pixel, plus one u8 per pixel + Self::Depth24PlusStencil8 => 4, + // One f32 per pixel + Self::Depth32Float => 4, + // One f32 per pixel, plus one u8 per pixel, with 3 bytes intermediary padding + Self::Depth32FloatStencil8 => 8, + // One u8 per pixel + Self::Stencil8 => 1, + // Two chroma bytes per block, one luma byte per block + Self::NV12 => 3, + // Two chroma u16s and one luma u16 per block + Self::P010 => 6, + f => { + unimplemented!("Memory footprint for format {f:?} is not implemented"); + } + }, + }; + + let width_blocks = size.width.div_ceil(block_width) as u64; + let height_blocks = size.height.div_ceil(block_height) as u64; + + let total_blocks = width_blocks * height_blocks * size.depth_or_array_layers as u64; + + total_blocks * approximate_block_size as u64 + } +} + +#[cfg(any(feature = "serde", test))] +impl<'de> Deserialize<'de> for TextureFormat { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: serde::Deserializer<'de>, + { + use serde::de::{self, Error, Unexpected}; + + struct TextureFormatVisitor; + + impl de::Visitor<'_> for TextureFormatVisitor { + type Value = TextureFormat; + + fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { + formatter.write_str("a valid texture format") + } + + fn visit_str<E: Error>(self, s: &str) -> Result<Self::Value, E> { + let format = match s { + "r8unorm" => TextureFormat::R8Unorm, + "r8snorm" => TextureFormat::R8Snorm, + "r8uint" => TextureFormat::R8Uint, + "r8sint" => TextureFormat::R8Sint, + "r16uint" => TextureFormat::R16Uint, + "r16sint" => TextureFormat::R16Sint, + "r16unorm" => TextureFormat::R16Unorm, + "r16snorm" => TextureFormat::R16Snorm, + "r16float" => TextureFormat::R16Float, + "rg8unorm" => TextureFormat::Rg8Unorm, + "rg8snorm" => TextureFormat::Rg8Snorm, + "rg8uint" => TextureFormat::Rg8Uint, + "rg8sint" => TextureFormat::Rg8Sint, + "r32uint" => TextureFormat::R32Uint, + "r32sint" => TextureFormat::R32Sint, + "r32float" => TextureFormat::R32Float, + "rg16uint" => TextureFormat::Rg16Uint, + "rg16sint" => TextureFormat::Rg16Sint, + "rg16unorm" => TextureFormat::Rg16Unorm, + "rg16snorm" => TextureFormat::Rg16Snorm, + "rg16float" => TextureFormat::Rg16Float, + "rgba8unorm" => TextureFormat::Rgba8Unorm, + "rgba8unorm-srgb" => TextureFormat::Rgba8UnormSrgb, + "rgba8snorm" => TextureFormat::Rgba8Snorm, + "rgba8uint" => TextureFormat::Rgba8Uint, + "rgba8sint" => TextureFormat::Rgba8Sint, + "bgra8unorm" => TextureFormat::Bgra8Unorm, + "bgra8unorm-srgb" => TextureFormat::Bgra8UnormSrgb, + "rgb10a2uint" => TextureFormat::Rgb10a2Uint, + "rgb10a2unorm" => TextureFormat::Rgb10a2Unorm, + "rg11b10ufloat" => TextureFormat::Rg11b10Ufloat, + "r64uint" => TextureFormat::R64Uint, + "rg32uint" => TextureFormat::Rg32Uint, + "rg32sint" => TextureFormat::Rg32Sint, + "rg32float" => TextureFormat::Rg32Float, + "rgba16uint" => TextureFormat::Rgba16Uint, + "rgba16sint" => TextureFormat::Rgba16Sint, + "rgba16unorm" => TextureFormat::Rgba16Unorm, + "rgba16snorm" => TextureFormat::Rgba16Snorm, + "rgba16float" => TextureFormat::Rgba16Float, + "rgba32uint" => TextureFormat::Rgba32Uint, + "rgba32sint" => TextureFormat::Rgba32Sint, + "rgba32float" => TextureFormat::Rgba32Float, + "stencil8" => TextureFormat::Stencil8, + "depth32float" => TextureFormat::Depth32Float, + "depth32float-stencil8" => TextureFormat::Depth32FloatStencil8, + "depth16unorm" => TextureFormat::Depth16Unorm, + "depth24plus" => TextureFormat::Depth24Plus, + "depth24plus-stencil8" => TextureFormat::Depth24PlusStencil8, + "nv12" => TextureFormat::NV12, + "p010" => TextureFormat::P010, + "rgb9e5ufloat" => TextureFormat::Rgb9e5Ufloat, + "bc1-rgba-unorm" => TextureFormat::Bc1RgbaUnorm, + "bc1-rgba-unorm-srgb" => TextureFormat::Bc1RgbaUnormSrgb, + "bc2-rgba-unorm" => TextureFormat::Bc2RgbaUnorm, + "bc2-rgba-unorm-srgb" => TextureFormat::Bc2RgbaUnormSrgb, + "bc3-rgba-unorm" => TextureFormat::Bc3RgbaUnorm, + "bc3-rgba-unorm-srgb" => TextureFormat::Bc3RgbaUnormSrgb, + "bc4-r-unorm" => TextureFormat::Bc4RUnorm, + "bc4-r-snorm" => TextureFormat::Bc4RSnorm, + "bc5-rg-unorm" => TextureFormat::Bc5RgUnorm, + "bc5-rg-snorm" => TextureFormat::Bc5RgSnorm, + "bc6h-rgb-ufloat" => TextureFormat::Bc6hRgbUfloat, + "bc6h-rgb-float" => TextureFormat::Bc6hRgbFloat, + "bc7-rgba-unorm" => TextureFormat::Bc7RgbaUnorm, + "bc7-rgba-unorm-srgb" => TextureFormat::Bc7RgbaUnormSrgb, + "etc2-rgb8unorm" => TextureFormat::Etc2Rgb8Unorm, + "etc2-rgb8unorm-srgb" => TextureFormat::Etc2Rgb8UnormSrgb, + "etc2-rgb8a1unorm" => TextureFormat::Etc2Rgb8A1Unorm, + "etc2-rgb8a1unorm-srgb" => TextureFormat::Etc2Rgb8A1UnormSrgb, + "etc2-rgba8unorm" => TextureFormat::Etc2Rgba8Unorm, + "etc2-rgba8unorm-srgb" => TextureFormat::Etc2Rgba8UnormSrgb, + "eac-r11unorm" => TextureFormat::EacR11Unorm, + "eac-r11snorm" => TextureFormat::EacR11Snorm, + "eac-rg11unorm" => TextureFormat::EacRg11Unorm, + "eac-rg11snorm" => TextureFormat::EacRg11Snorm, + other => { + if let Some(parts) = other.strip_prefix("astc-") { + let (block, channel) = parts + .split_once('-') + .ok_or_else(|| E::invalid_value(Unexpected::Str(s), &self))?; + + let block = match block { + "4x4" => AstcBlock::B4x4, + "5x4" => AstcBlock::B5x4, + "5x5" => AstcBlock::B5x5, + "6x5" => AstcBlock::B6x5, + "6x6" => AstcBlock::B6x6, + "8x5" => AstcBlock::B8x5, + "8x6" => AstcBlock::B8x6, + "8x8" => AstcBlock::B8x8, + "10x5" => AstcBlock::B10x5, + "10x6" => AstcBlock::B10x6, + "10x8" => AstcBlock::B10x8, + "10x10" => AstcBlock::B10x10, + "12x10" => AstcBlock::B12x10, + "12x12" => AstcBlock::B12x12, + _ => return Err(E::invalid_value(Unexpected::Str(s), &self)), + }; + + let channel = match channel { + "unorm" => AstcChannel::Unorm, + "unorm-srgb" => AstcChannel::UnormSrgb, + "hdr" => AstcChannel::Hdr, + _ => return Err(E::invalid_value(Unexpected::Str(s), &self)), + }; + + TextureFormat::Astc { block, channel } + } else { + return Err(E::invalid_value(Unexpected::Str(s), &self)); + } + } + }; + + Ok(format) + } + } + + deserializer.deserialize_str(TextureFormatVisitor) + } +} + +#[cfg(any(feature = "serde", test))] +impl Serialize for TextureFormat { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + let s: alloc::string::String; + let name = match *self { + TextureFormat::R8Unorm => "r8unorm", + TextureFormat::R8Snorm => "r8snorm", + TextureFormat::R8Uint => "r8uint", + TextureFormat::R8Sint => "r8sint", + TextureFormat::R16Uint => "r16uint", + TextureFormat::R16Sint => "r16sint", + TextureFormat::R16Unorm => "r16unorm", + TextureFormat::R16Snorm => "r16snorm", + TextureFormat::R16Float => "r16float", + TextureFormat::Rg8Unorm => "rg8unorm", + TextureFormat::Rg8Snorm => "rg8snorm", + TextureFormat::Rg8Uint => "rg8uint", + TextureFormat::Rg8Sint => "rg8sint", + TextureFormat::R32Uint => "r32uint", + TextureFormat::R32Sint => "r32sint", + TextureFormat::R32Float => "r32float", + TextureFormat::Rg16Uint => "rg16uint", + TextureFormat::Rg16Sint => "rg16sint", + TextureFormat::Rg16Unorm => "rg16unorm", + TextureFormat::Rg16Snorm => "rg16snorm", + TextureFormat::Rg16Float => "rg16float", + TextureFormat::Rgba8Unorm => "rgba8unorm", + TextureFormat::Rgba8UnormSrgb => "rgba8unorm-srgb", + TextureFormat::Rgba8Snorm => "rgba8snorm", + TextureFormat::Rgba8Uint => "rgba8uint", + TextureFormat::Rgba8Sint => "rgba8sint", + TextureFormat::Bgra8Unorm => "bgra8unorm", + TextureFormat::Bgra8UnormSrgb => "bgra8unorm-srgb", + TextureFormat::Rgb10a2Uint => "rgb10a2uint", + TextureFormat::Rgb10a2Unorm => "rgb10a2unorm", + TextureFormat::Rg11b10Ufloat => "rg11b10ufloat", + TextureFormat::R64Uint => "r64uint", + TextureFormat::Rg32Uint => "rg32uint", + TextureFormat::Rg32Sint => "rg32sint", + TextureFormat::Rg32Float => "rg32float", + TextureFormat::Rgba16Uint => "rgba16uint", + TextureFormat::Rgba16Sint => "rgba16sint", + TextureFormat::Rgba16Unorm => "rgba16unorm", + TextureFormat::Rgba16Snorm => "rgba16snorm", + TextureFormat::Rgba16Float => "rgba16float", + TextureFormat::Rgba32Uint => "rgba32uint", + TextureFormat::Rgba32Sint => "rgba32sint", + TextureFormat::Rgba32Float => "rgba32float", + TextureFormat::Stencil8 => "stencil8", + TextureFormat::Depth32Float => "depth32float", + TextureFormat::Depth16Unorm => "depth16unorm", + TextureFormat::Depth32FloatStencil8 => "depth32float-stencil8", + TextureFormat::Depth24Plus => "depth24plus", + TextureFormat::Depth24PlusStencil8 => "depth24plus-stencil8", + TextureFormat::NV12 => "nv12", + TextureFormat::P010 => "p010", + TextureFormat::Rgb9e5Ufloat => "rgb9e5ufloat", + TextureFormat::Bc1RgbaUnorm => "bc1-rgba-unorm", + TextureFormat::Bc1RgbaUnormSrgb => "bc1-rgba-unorm-srgb", + TextureFormat::Bc2RgbaUnorm => "bc2-rgba-unorm", + TextureFormat::Bc2RgbaUnormSrgb => "bc2-rgba-unorm-srgb", + TextureFormat::Bc3RgbaUnorm => "bc3-rgba-unorm", + TextureFormat::Bc3RgbaUnormSrgb => "bc3-rgba-unorm-srgb", + TextureFormat::Bc4RUnorm => "bc4-r-unorm", + TextureFormat::Bc4RSnorm => "bc4-r-snorm", + TextureFormat::Bc5RgUnorm => "bc5-rg-unorm", + TextureFormat::Bc5RgSnorm => "bc5-rg-snorm", + TextureFormat::Bc6hRgbUfloat => "bc6h-rgb-ufloat", + TextureFormat::Bc6hRgbFloat => "bc6h-rgb-float", + TextureFormat::Bc7RgbaUnorm => "bc7-rgba-unorm", + TextureFormat::Bc7RgbaUnormSrgb => "bc7-rgba-unorm-srgb", + TextureFormat::Etc2Rgb8Unorm => "etc2-rgb8unorm", + TextureFormat::Etc2Rgb8UnormSrgb => "etc2-rgb8unorm-srgb", + TextureFormat::Etc2Rgb8A1Unorm => "etc2-rgb8a1unorm", + TextureFormat::Etc2Rgb8A1UnormSrgb => "etc2-rgb8a1unorm-srgb", + TextureFormat::Etc2Rgba8Unorm => "etc2-rgba8unorm", + TextureFormat::Etc2Rgba8UnormSrgb => "etc2-rgba8unorm-srgb", + TextureFormat::EacR11Unorm => "eac-r11unorm", + TextureFormat::EacR11Snorm => "eac-r11snorm", + TextureFormat::EacRg11Unorm => "eac-rg11unorm", + TextureFormat::EacRg11Snorm => "eac-rg11snorm", + TextureFormat::Astc { block, channel } => { + let block = match block { + AstcBlock::B4x4 => "4x4", + AstcBlock::B5x4 => "5x4", + AstcBlock::B5x5 => "5x5", + AstcBlock::B6x5 => "6x5", + AstcBlock::B6x6 => "6x6", + AstcBlock::B8x5 => "8x5", + AstcBlock::B8x6 => "8x6", + AstcBlock::B8x8 => "8x8", + AstcBlock::B10x5 => "10x5", + AstcBlock::B10x6 => "10x6", + AstcBlock::B10x8 => "10x8", + AstcBlock::B10x10 => "10x10", + AstcBlock::B12x10 => "12x10", + AstcBlock::B12x12 => "12x12", + }; + + let channel = match channel { + AstcChannel::Unorm => "unorm", + AstcChannel::UnormSrgb => "unorm-srgb", + AstcChannel::Hdr => "hdr", + }; + + s = alloc::format!("astc-{block}-{channel}"); + &s + } + }; + serializer.serialize_str(name) + } +} + +bitflags::bitflags! { + /// Feature flags for a texture format. + #[repr(transparent)] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + #[cfg_attr(feature = "serde", serde(transparent))] + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] + pub struct TextureFormatFeatureFlags: u32 { + /// If not present, the texture can't be sampled with a filtering sampler. + /// This may overwrite TextureSampleType::Float.filterable + const FILTERABLE = 1 << 0; + /// Allows [`TextureDescriptor::sample_count`] to be `2`. + const MULTISAMPLE_X2 = 1 << 1; + /// Allows [`TextureDescriptor::sample_count`] to be `4`. + const MULTISAMPLE_X4 = 1 << 2 ; + /// Allows [`TextureDescriptor::sample_count`] to be `8`. + const MULTISAMPLE_X8 = 1 << 3 ; + /// Allows [`TextureDescriptor::sample_count`] to be `16`. + const MULTISAMPLE_X16 = 1 << 4; + /// Allows a texture of this format to back a view passed as `resolve_target` + /// to a render pass for an automatic driver-implemented resolve. + const MULTISAMPLE_RESOLVE = 1 << 5; + /// When used as a STORAGE texture, then a texture with this format can be bound with + /// [`StorageTextureAccess::ReadOnly`]. + const STORAGE_READ_ONLY = 1 << 6; + /// When used as a STORAGE texture, then a texture with this format can be bound with + /// [`StorageTextureAccess::WriteOnly`]. + const STORAGE_WRITE_ONLY = 1 << 7; + /// When used as a STORAGE texture, then a texture with this format can be bound with + /// [`StorageTextureAccess::ReadWrite`]. + const STORAGE_READ_WRITE = 1 << 8; + /// When used as a STORAGE texture, then a texture with this format can be bound with + /// [`StorageTextureAccess::Atomic`]. + const STORAGE_ATOMIC = 1 << 9; + /// If not present, the texture can't be blended into the render target. + const BLENDABLE = 1 << 10; + } +} + +impl TextureFormatFeatureFlags { + /// Sample count supported by a given texture format. + /// + /// returns `true` if `count` is a supported sample count. + #[must_use] + pub fn sample_count_supported(&self, count: u32) -> bool { + use TextureFormatFeatureFlags as tfsc; + + match count { + 1 => true, + 2 => self.contains(tfsc::MULTISAMPLE_X2), + 4 => self.contains(tfsc::MULTISAMPLE_X4), + 8 => self.contains(tfsc::MULTISAMPLE_X8), + 16 => self.contains(tfsc::MULTISAMPLE_X16), + _ => false, + } + } + + /// A `Vec` of supported sample counts. + #[must_use] + pub fn supported_sample_counts(&self) -> Vec<u32> { + let all_possible_sample_counts: [u32; 5] = [1, 2, 4, 8, 16]; + all_possible_sample_counts + .into_iter() + .filter(|&sc| self.sample_count_supported(sc)) + .collect() + } +} + +/// Features supported by a given texture format +/// +/// Features are defined by WebGPU specification unless [`Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES`] is enabled. +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct TextureFormatFeatures { + /// Valid bits for `TextureDescriptor::Usage` provided for format creation. + pub allowed_usages: TextureUsages, + /// Additional property flags for the format. + pub flags: TextureFormatFeatureFlags, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn texture_format_serialize() { + use alloc::string::ToString; + + assert_eq!( + serde_json::to_string(&TextureFormat::R8Unorm).unwrap(), + "\"r8unorm\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::R8Snorm).unwrap(), + "\"r8snorm\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::R8Uint).unwrap(), + "\"r8uint\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::R8Sint).unwrap(), + "\"r8sint\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::R16Uint).unwrap(), + "\"r16uint\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::R16Sint).unwrap(), + "\"r16sint\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::R16Unorm).unwrap(), + "\"r16unorm\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::R16Snorm).unwrap(), + "\"r16snorm\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::R16Float).unwrap(), + "\"r16float\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Rg8Unorm).unwrap(), + "\"rg8unorm\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Rg8Snorm).unwrap(), + "\"rg8snorm\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Rg8Uint).unwrap(), + "\"rg8uint\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Rg8Sint).unwrap(), + "\"rg8sint\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::R32Uint).unwrap(), + "\"r32uint\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::R32Sint).unwrap(), + "\"r32sint\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::R32Float).unwrap(), + "\"r32float\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Rg16Uint).unwrap(), + "\"rg16uint\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Rg16Sint).unwrap(), + "\"rg16sint\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Rg16Unorm).unwrap(), + "\"rg16unorm\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Rg16Snorm).unwrap(), + "\"rg16snorm\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Rg16Float).unwrap(), + "\"rg16float\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Rgba8Unorm).unwrap(), + "\"rgba8unorm\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Rgba8UnormSrgb).unwrap(), + "\"rgba8unorm-srgb\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Rgba8Snorm).unwrap(), + "\"rgba8snorm\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Rgba8Uint).unwrap(), + "\"rgba8uint\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Rgba8Sint).unwrap(), + "\"rgba8sint\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Bgra8Unorm).unwrap(), + "\"bgra8unorm\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Bgra8UnormSrgb).unwrap(), + "\"bgra8unorm-srgb\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Rgb10a2Uint).unwrap(), + "\"rgb10a2uint\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Rgb10a2Unorm).unwrap(), + "\"rgb10a2unorm\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Rg11b10Ufloat).unwrap(), + "\"rg11b10ufloat\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::R64Uint).unwrap(), + "\"r64uint\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Rg32Uint).unwrap(), + "\"rg32uint\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Rg32Sint).unwrap(), + "\"rg32sint\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Rg32Float).unwrap(), + "\"rg32float\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Rgba16Uint).unwrap(), + "\"rgba16uint\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Rgba16Sint).unwrap(), + "\"rgba16sint\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Rgba16Unorm).unwrap(), + "\"rgba16unorm\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Rgba16Snorm).unwrap(), + "\"rgba16snorm\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Rgba16Float).unwrap(), + "\"rgba16float\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Rgba32Uint).unwrap(), + "\"rgba32uint\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Rgba32Sint).unwrap(), + "\"rgba32sint\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Rgba32Float).unwrap(), + "\"rgba32float\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Stencil8).unwrap(), + "\"stencil8\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Depth32Float).unwrap(), + "\"depth32float\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Depth16Unorm).unwrap(), + "\"depth16unorm\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Depth32FloatStencil8).unwrap(), + "\"depth32float-stencil8\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Depth24Plus).unwrap(), + "\"depth24plus\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Depth24PlusStencil8).unwrap(), + "\"depth24plus-stencil8\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Rgb9e5Ufloat).unwrap(), + "\"rgb9e5ufloat\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Bc1RgbaUnorm).unwrap(), + "\"bc1-rgba-unorm\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Bc1RgbaUnormSrgb).unwrap(), + "\"bc1-rgba-unorm-srgb\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Bc2RgbaUnorm).unwrap(), + "\"bc2-rgba-unorm\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Bc2RgbaUnormSrgb).unwrap(), + "\"bc2-rgba-unorm-srgb\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Bc3RgbaUnorm).unwrap(), + "\"bc3-rgba-unorm\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Bc3RgbaUnormSrgb).unwrap(), + "\"bc3-rgba-unorm-srgb\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Bc4RUnorm).unwrap(), + "\"bc4-r-unorm\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Bc4RSnorm).unwrap(), + "\"bc4-r-snorm\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Bc5RgUnorm).unwrap(), + "\"bc5-rg-unorm\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Bc5RgSnorm).unwrap(), + "\"bc5-rg-snorm\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Bc6hRgbUfloat).unwrap(), + "\"bc6h-rgb-ufloat\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Bc6hRgbFloat).unwrap(), + "\"bc6h-rgb-float\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Bc7RgbaUnorm).unwrap(), + "\"bc7-rgba-unorm\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Bc7RgbaUnormSrgb).unwrap(), + "\"bc7-rgba-unorm-srgb\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Etc2Rgb8Unorm).unwrap(), + "\"etc2-rgb8unorm\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Etc2Rgb8UnormSrgb).unwrap(), + "\"etc2-rgb8unorm-srgb\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Etc2Rgb8A1Unorm).unwrap(), + "\"etc2-rgb8a1unorm\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Etc2Rgb8A1UnormSrgb).unwrap(), + "\"etc2-rgb8a1unorm-srgb\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Etc2Rgba8Unorm).unwrap(), + "\"etc2-rgba8unorm\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::Etc2Rgba8UnormSrgb).unwrap(), + "\"etc2-rgba8unorm-srgb\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::EacR11Unorm).unwrap(), + "\"eac-r11unorm\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::EacR11Snorm).unwrap(), + "\"eac-r11snorm\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::EacRg11Unorm).unwrap(), + "\"eac-rg11unorm\"".to_string() + ); + assert_eq!( + serde_json::to_string(&TextureFormat::EacRg11Snorm).unwrap(), + "\"eac-rg11snorm\"".to_string() + ); + } + + #[test] + fn texture_format_deserialize() { + assert_eq!( + serde_json::from_str::<TextureFormat>("\"r8unorm\"").unwrap(), + TextureFormat::R8Unorm + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"r8snorm\"").unwrap(), + TextureFormat::R8Snorm + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"r8uint\"").unwrap(), + TextureFormat::R8Uint + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"r8sint\"").unwrap(), + TextureFormat::R8Sint + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"r16uint\"").unwrap(), + TextureFormat::R16Uint + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"r16sint\"").unwrap(), + TextureFormat::R16Sint + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"r16unorm\"").unwrap(), + TextureFormat::R16Unorm + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"r16snorm\"").unwrap(), + TextureFormat::R16Snorm + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"r16float\"").unwrap(), + TextureFormat::R16Float + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"rg8unorm\"").unwrap(), + TextureFormat::Rg8Unorm + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"rg8snorm\"").unwrap(), + TextureFormat::Rg8Snorm + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"rg8uint\"").unwrap(), + TextureFormat::Rg8Uint + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"rg8sint\"").unwrap(), + TextureFormat::Rg8Sint + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"r32uint\"").unwrap(), + TextureFormat::R32Uint + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"r32sint\"").unwrap(), + TextureFormat::R32Sint + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"r32float\"").unwrap(), + TextureFormat::R32Float + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"rg16uint\"").unwrap(), + TextureFormat::Rg16Uint + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"rg16sint\"").unwrap(), + TextureFormat::Rg16Sint + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"rg16unorm\"").unwrap(), + TextureFormat::Rg16Unorm + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"rg16snorm\"").unwrap(), + TextureFormat::Rg16Snorm + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"rg16float\"").unwrap(), + TextureFormat::Rg16Float + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"rgba8unorm\"").unwrap(), + TextureFormat::Rgba8Unorm + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"rgba8unorm-srgb\"").unwrap(), + TextureFormat::Rgba8UnormSrgb + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"rgba8snorm\"").unwrap(), + TextureFormat::Rgba8Snorm + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"rgba8uint\"").unwrap(), + TextureFormat::Rgba8Uint + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"rgba8sint\"").unwrap(), + TextureFormat::Rgba8Sint + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"bgra8unorm\"").unwrap(), + TextureFormat::Bgra8Unorm + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"bgra8unorm-srgb\"").unwrap(), + TextureFormat::Bgra8UnormSrgb + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"rgb10a2uint\"").unwrap(), + TextureFormat::Rgb10a2Uint + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"rgb10a2unorm\"").unwrap(), + TextureFormat::Rgb10a2Unorm + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"rg11b10ufloat\"").unwrap(), + TextureFormat::Rg11b10Ufloat + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"r64uint\"").unwrap(), + TextureFormat::R64Uint + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"rg32uint\"").unwrap(), + TextureFormat::Rg32Uint + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"rg32sint\"").unwrap(), + TextureFormat::Rg32Sint + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"rg32float\"").unwrap(), + TextureFormat::Rg32Float + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"rgba16uint\"").unwrap(), + TextureFormat::Rgba16Uint + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"rgba16sint\"").unwrap(), + TextureFormat::Rgba16Sint + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"rgba16unorm\"").unwrap(), + TextureFormat::Rgba16Unorm + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"rgba16snorm\"").unwrap(), + TextureFormat::Rgba16Snorm + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"rgba16float\"").unwrap(), + TextureFormat::Rgba16Float + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"rgba32uint\"").unwrap(), + TextureFormat::Rgba32Uint + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"rgba32sint\"").unwrap(), + TextureFormat::Rgba32Sint + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"rgba32float\"").unwrap(), + TextureFormat::Rgba32Float + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"stencil8\"").unwrap(), + TextureFormat::Stencil8 + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"depth32float\"").unwrap(), + TextureFormat::Depth32Float + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"depth16unorm\"").unwrap(), + TextureFormat::Depth16Unorm + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"depth32float-stencil8\"").unwrap(), + TextureFormat::Depth32FloatStencil8 + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"depth24plus\"").unwrap(), + TextureFormat::Depth24Plus + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"depth24plus-stencil8\"").unwrap(), + TextureFormat::Depth24PlusStencil8 + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"rgb9e5ufloat\"").unwrap(), + TextureFormat::Rgb9e5Ufloat + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"bc1-rgba-unorm\"").unwrap(), + TextureFormat::Bc1RgbaUnorm + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"bc1-rgba-unorm-srgb\"").unwrap(), + TextureFormat::Bc1RgbaUnormSrgb + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"bc2-rgba-unorm\"").unwrap(), + TextureFormat::Bc2RgbaUnorm + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"bc2-rgba-unorm-srgb\"").unwrap(), + TextureFormat::Bc2RgbaUnormSrgb + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"bc3-rgba-unorm\"").unwrap(), + TextureFormat::Bc3RgbaUnorm + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"bc3-rgba-unorm-srgb\"").unwrap(), + TextureFormat::Bc3RgbaUnormSrgb + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"bc4-r-unorm\"").unwrap(), + TextureFormat::Bc4RUnorm + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"bc4-r-snorm\"").unwrap(), + TextureFormat::Bc4RSnorm + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"bc5-rg-unorm\"").unwrap(), + TextureFormat::Bc5RgUnorm + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"bc5-rg-snorm\"").unwrap(), + TextureFormat::Bc5RgSnorm + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"bc6h-rgb-ufloat\"").unwrap(), + TextureFormat::Bc6hRgbUfloat + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"bc6h-rgb-float\"").unwrap(), + TextureFormat::Bc6hRgbFloat + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"bc7-rgba-unorm\"").unwrap(), + TextureFormat::Bc7RgbaUnorm + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"bc7-rgba-unorm-srgb\"").unwrap(), + TextureFormat::Bc7RgbaUnormSrgb + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"etc2-rgb8unorm\"").unwrap(), + TextureFormat::Etc2Rgb8Unorm + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"etc2-rgb8unorm-srgb\"").unwrap(), + TextureFormat::Etc2Rgb8UnormSrgb + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"etc2-rgb8a1unorm\"").unwrap(), + TextureFormat::Etc2Rgb8A1Unorm + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"etc2-rgb8a1unorm-srgb\"").unwrap(), + TextureFormat::Etc2Rgb8A1UnormSrgb + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"etc2-rgba8unorm\"").unwrap(), + TextureFormat::Etc2Rgba8Unorm + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"etc2-rgba8unorm-srgb\"").unwrap(), + TextureFormat::Etc2Rgba8UnormSrgb + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"eac-r11unorm\"").unwrap(), + TextureFormat::EacR11Unorm + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"eac-r11snorm\"").unwrap(), + TextureFormat::EacR11Snorm + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"eac-rg11unorm\"").unwrap(), + TextureFormat::EacRg11Unorm + ); + assert_eq!( + serde_json::from_str::<TextureFormat>("\"eac-rg11snorm\"").unwrap(), + TextureFormat::EacRg11Snorm + ); + } +} diff --git a/third_party/rust/wgpu-types/src/tokens.rs b/third_party/rust/wgpu-types/src/tokens.rs @@ -1,3 +1,5 @@ +use crate::link_to_wgpu_item; + /// Token of the user agreeing to access experimental features. #[derive(Debug, Default, Copy, Clone)] pub struct ExperimentalFeatures { @@ -7,7 +9,7 @@ pub struct ExperimentalFeatures { impl ExperimentalFeatures { /// Uses of [`Features`] prefixed with "EXPERIMENTAL" are disallowed. /// - /// [`Features`]: ../wgpu/struct.Features.html + #[doc = link_to_wgpu_item!(struct Features)] pub const fn disabled() -> Self { Self { enabled: false } } @@ -30,7 +32,7 @@ impl ExperimentalFeatures { /// apis and those may be hit by calling otherwise safe code. /// - You agree to report any such bugs to us, if you find them. /// - /// [`Features`]: ../wgpu/struct.Features.html + #[doc = link_to_wgpu_item!(struct Features)] /// [`api-specs`]: https://github.com/gfx-rs/wgpu/tree/trunk/docs/api-specs pub const unsafe fn enabled() -> Self { Self { enabled: true } @@ -41,3 +43,32 @@ impl ExperimentalFeatures { self.enabled } } + +/// Token of the user agreeing to use [`LoadOp::DontCare`](crate::LoadOp::DontCare). +#[derive(Debug, Default, Copy, Clone, Hash, PartialEq, Eq)] +pub struct LoadOpDontCare { + // Private to prevent construction outside of the unsafe + // enabled() function. + _private: (), +} + +impl LoadOpDontCare { + /// Using [`LoadOp::DontCare`](crate::LoadOp::DontCare) will result + /// in the render target having undefined contents at the start of the render pass. + /// This may lead to undefined behavior if you read from the any of the + /// render target pixels without first writing to them. + /// + /// Blending also becomes undefined behavior if the source + /// pixels are undefined. + /// + /// All pixels in the render target must be written to before + /// any blending or a [`StoreOp::Store`](crate::StoreOp::Store) occurs. + /// + /// # Safety + /// + /// - You acknowledge that using `LoadOp::DontCare` may lead to undefined behavior + /// if the above conditions are not met. + pub const unsafe fn enabled() -> Self { + Self { _private: () } + } +} diff --git a/third_party/rust/wgpu-types/src/vertex.rs b/third_party/rust/wgpu-types/src/vertex.rs @@ -0,0 +1,282 @@ +//! Types for defining vertex attributes and their buffers. + +#[cfg(any(feature = "serde", test))] +use serde::{Deserialize, Serialize}; + +use crate::{link_to_wgpu_docs, link_to_wgpu_item}; + +#[cfg(doc)] +use crate::Features; + +/// Whether a vertex buffer is indexed by vertex or by instance. +/// +/// Consider a call to [`RenderPass::draw`] like this: +/// +/// ```ignore +/// render_pass.draw(vertices, instances) +/// ``` +/// +/// where `vertices` is a `Range<u32>` of vertex indices, and +/// `instances` is a `Range<u32>` of instance indices. +/// +/// For this call, `wgpu` invokes the vertex shader entry point once +/// for every possible `(v, i)` pair, where `v` is drawn from +/// `vertices` and `i` is drawn from `instances`. These invocations +/// may happen in any order, and will usually run in parallel. +/// +/// Each vertex buffer has a step mode, established by the +/// [`step_mode`] field of its [`VertexBufferLayout`], given when the +/// pipeline was created. Buffers whose step mode is [`Vertex`] use +/// `v` as the index into their contents, whereas buffers whose step +/// mode is [`Instance`] use `i`. The indicated buffer element then +/// contributes zero or more attribute values for the `(v, i)` vertex +/// shader invocation to use, based on the [`VertexBufferLayout`]'s +/// [`attributes`] list. +/// +/// You can visualize the results from all these vertex shader +/// invocations as a matrix with a row for each `i` from `instances`, +/// and with a column for each `v` from `vertices`. In one sense, `v` +/// and `i` are symmetrical: both are used to index vertex buffers and +/// provide attribute values. But the key difference between `v` and +/// `i` is that line and triangle primitives are built from the values +/// of each row, along which `i` is constant and `v` varies, not the +/// columns. +/// +/// An indexed draw call works similarly: +/// +/// ```ignore +/// render_pass.draw_indexed(indices, base_vertex, instances) +/// ``` +/// +/// The only difference is that `v` values are drawn from the contents +/// of the index buffer—specifically, the subrange of the index +/// buffer given by `indices`—instead of simply being sequential +/// integers, as they are in a `draw` call. +/// +/// A non-instanced call, where `instances` is `0..1`, is simply a +/// matrix with only one row. +/// +/// Corresponds to [WebGPU `GPUVertexStepMode`]( +/// https://gpuweb.github.io/gpuweb/#enumdef-gpuvertexstepmode). +/// +#[doc = link_to_wgpu_docs!(["`RenderPass::draw`"]: "struct.RenderPass.html#method.draw")] +#[doc = link_to_wgpu_item!(struct VertexBufferLayout)] +#[doc = link_to_wgpu_docs!(["`step_mode`"]: "struct.VertexBufferLayout.html#structfield.step_mode")] +#[doc = link_to_wgpu_docs!(["`attributes`"]: "struct.VertexBufferLayout.html#structfield.attributes")] +/// [`Vertex`]: VertexStepMode::Vertex +/// [`Instance`]: VertexStepMode::Instance +#[repr(C)] +#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] +pub enum VertexStepMode { + /// Vertex data is advanced every vertex. + #[default] + Vertex = 0, + /// Vertex data is advanced every instance. + Instance = 1, +} + +/// Vertex inputs (attributes) to shaders. +/// +/// These are used to specify the individual attributes within a [`VertexBufferLayout`]. +/// See its documentation for an example. +/// +/// The [`vertex_attr_array!`] macro can help create these with appropriate offsets. +/// +/// Corresponds to [WebGPU `GPUVertexAttribute`]( +/// https://gpuweb.github.io/gpuweb/#dictdef-gpuvertexattribute). +/// +#[doc = link_to_wgpu_docs!(["`vertex_attr_array!`"]: "macro.vertex_attr_array.html")] +#[doc = link_to_wgpu_item!(struct VertexBufferLayout)] +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct VertexAttribute { + /// Format of the input + pub format: VertexFormat, + /// Byte offset of the start of the input + pub offset: crate::BufferAddress, + /// Location for this input. Must match the location in the shader. + pub shader_location: crate::ShaderLocation, +} + +/// Vertex Format for a [`VertexAttribute`] (input). +/// +/// Corresponds to [WebGPU `GPUVertexFormat`]( +/// https://gpuweb.github.io/gpuweb/#enumdef-gpuvertexformat). +#[repr(C)] +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))] +pub enum VertexFormat { + /// One unsigned byte (u8). `u32` in shaders. + Uint8 = 0, + /// Two unsigned bytes (u8). `vec2<u32>` in shaders. + Uint8x2 = 1, + /// Four unsigned bytes (u8). `vec4<u32>` in shaders. + Uint8x4 = 2, + /// One signed byte (i8). `i32` in shaders. + Sint8 = 3, + /// Two signed bytes (i8). `vec2<i32>` in shaders. + Sint8x2 = 4, + /// Four signed bytes (i8). `vec4<i32>` in shaders. + Sint8x4 = 5, + /// One unsigned byte (u8). [0, 255] converted to float [0, 1] `f32` in shaders. + Unorm8 = 6, + /// Two unsigned bytes (u8). [0, 255] converted to float [0, 1] `vec2<f32>` in shaders. + Unorm8x2 = 7, + /// Four unsigned bytes (u8). [0, 255] converted to float [0, 1] `vec4<f32>` in shaders. + Unorm8x4 = 8, + /// One signed byte (i8). [−127, 127] converted to float [−1, 1] `f32` in shaders. + Snorm8 = 9, + /// Two signed bytes (i8). [−127, 127] converted to float [−1, 1] `vec2<f32>` in shaders. + Snorm8x2 = 10, + /// Four signed bytes (i8). [−127, 127] converted to float [−1, 1] `vec4<f32>` in shaders. + Snorm8x4 = 11, + /// One unsigned short (u16). `u32` in shaders. + Uint16 = 12, + /// Two unsigned shorts (u16). `vec2<u32>` in shaders. + Uint16x2 = 13, + /// Four unsigned shorts (u16). `vec4<u32>` in shaders. + Uint16x4 = 14, + /// One signed short (u16). `i32` in shaders. + Sint16 = 15, + /// Two signed shorts (i16). `vec2<i32>` in shaders. + Sint16x2 = 16, + /// Four signed shorts (i16). `vec4<i32>` in shaders. + Sint16x4 = 17, + /// One unsigned short (u16). [0, 65535] converted to float [0, 1] `f32` in shaders. + Unorm16 = 18, + /// Two unsigned shorts (u16). [0, 65535] converted to float [0, 1] `vec2<f32>` in shaders. + Unorm16x2 = 19, + /// Four unsigned shorts (u16). [0, 65535] converted to float [0, 1] `vec4<f32>` in shaders. + Unorm16x4 = 20, + /// One signed short (i16). [−32767, 32767] converted to float [−1, 1] `f32` in shaders. + Snorm16 = 21, + /// Two signed shorts (i16). [−32767, 32767] converted to float [−1, 1] `vec2<f32>` in shaders. + Snorm16x2 = 22, + /// Four signed shorts (i16). [−32767, 32767] converted to float [−1, 1] `vec4<f32>` in shaders. + Snorm16x4 = 23, + /// One half-precision float (no Rust equiv). `f32` in shaders. + Float16 = 24, + /// Two half-precision floats (no Rust equiv). `vec2<f32>` in shaders. + Float16x2 = 25, + /// Four half-precision floats (no Rust equiv). `vec4<f32>` in shaders. + Float16x4 = 26, + /// One single-precision float (f32). `f32` in shaders. + Float32 = 27, + /// Two single-precision floats (f32). `vec2<f32>` in shaders. + Float32x2 = 28, + /// Three single-precision floats (f32). `vec3<f32>` in shaders. + Float32x3 = 29, + /// Four single-precision floats (f32). `vec4<f32>` in shaders. + Float32x4 = 30, + /// One unsigned int (u32). `u32` in shaders. + Uint32 = 31, + /// Two unsigned ints (u32). `vec2<u32>` in shaders. + Uint32x2 = 32, + /// Three unsigned ints (u32). `vec3<u32>` in shaders. + Uint32x3 = 33, + /// Four unsigned ints (u32). `vec4<u32>` in shaders. + Uint32x4 = 34, + /// One signed int (i32). `i32` in shaders. + Sint32 = 35, + /// Two signed ints (i32). `vec2<i32>` in shaders. + Sint32x2 = 36, + /// Three signed ints (i32). `vec3<i32>` in shaders. + Sint32x3 = 37, + /// Four signed ints (i32). `vec4<i32>` in shaders. + Sint32x4 = 38, + /// One double-precision float (f64). `f32` in shaders. Requires [`Features::VERTEX_ATTRIBUTE_64BIT`]. + Float64 = 39, + /// Two double-precision floats (f64). `vec2<f32>` in shaders. Requires [`Features::VERTEX_ATTRIBUTE_64BIT`]. + Float64x2 = 40, + /// Three double-precision floats (f64). `vec3<f32>` in shaders. Requires [`Features::VERTEX_ATTRIBUTE_64BIT`]. + Float64x3 = 41, + /// Four double-precision floats (f64). `vec4<f32>` in shaders. Requires [`Features::VERTEX_ATTRIBUTE_64BIT`]. + Float64x4 = 42, + /// Three unsigned 10-bit integers and one 2-bit integer, packed into a 32-bit integer (u32). [0, 1024] converted to float [0, 1] `vec4<f32>` in shaders. + #[cfg_attr(feature = "serde", serde(rename = "unorm10-10-10-2"))] + Unorm10_10_10_2 = 43, + /// Four unsigned 8-bit integers, packed into a 32-bit integer (u32). [0, 255] converted to float [0, 1] `vec4<f32>` in shaders. + #[cfg_attr(feature = "serde", serde(rename = "unorm8x4-bgra"))] + Unorm8x4Bgra = 44, +} + +impl VertexFormat { + /// Returns the byte size of the format. + #[must_use] + pub const fn size(&self) -> u64 { + match self { + Self::Uint8 | Self::Sint8 | Self::Unorm8 | Self::Snorm8 => 1, + Self::Uint8x2 + | Self::Sint8x2 + | Self::Unorm8x2 + | Self::Snorm8x2 + | Self::Uint16 + | Self::Sint16 + | Self::Unorm16 + | Self::Snorm16 + | Self::Float16 => 2, + Self::Uint8x4 + | Self::Sint8x4 + | Self::Unorm8x4 + | Self::Snorm8x4 + | Self::Uint16x2 + | Self::Sint16x2 + | Self::Unorm16x2 + | Self::Snorm16x2 + | Self::Float16x2 + | Self::Float32 + | Self::Uint32 + | Self::Sint32 + | Self::Unorm10_10_10_2 + | Self::Unorm8x4Bgra => 4, + Self::Uint16x4 + | Self::Sint16x4 + | Self::Unorm16x4 + | Self::Snorm16x4 + | Self::Float16x4 + | Self::Float32x2 + | Self::Uint32x2 + | Self::Sint32x2 + | Self::Float64 => 8, + Self::Float32x3 | Self::Uint32x3 | Self::Sint32x3 => 12, + Self::Float32x4 | Self::Uint32x4 | Self::Sint32x4 | Self::Float64x2 => 16, + Self::Float64x3 => 24, + Self::Float64x4 => 32, + } + } + + /// Returns the size read by an acceleration structure build of the vertex format. This is + /// slightly different from [`Self::size`] because the alpha component of 4-component formats + /// are not read in an acceleration structure build, allowing for a smaller stride. + #[must_use] + pub const fn min_acceleration_structure_vertex_stride(&self) -> u64 { + match self { + Self::Float16x2 | Self::Snorm16x2 => 4, + Self::Float32x3 => 12, + Self::Float32x2 => 8, + // This is the minimum value from DirectX + // > A16 component is ignored, other data can be packed there, such as setting vertex stride to 6 bytes + // + // https://microsoft.github.io/DirectX-Specs/d3d/Raytracing.html#d3d12_raytracing_geometry_triangles_desc + // + // Vulkan does not express a minimum stride. + Self::Float16x4 | Self::Snorm16x4 => 6, + _ => unreachable!(), + } + } + + /// Returns the alignment required for `wgpu::BlasTriangleGeometry::vertex_stride` + #[must_use] + pub const fn acceleration_structure_stride_alignment(&self) -> u64 { + match self { + Self::Float16x4 | Self::Float16x2 | Self::Snorm16x4 | Self::Snorm16x2 => 2, + Self::Float32x2 | Self::Float32x3 => 4, + _ => unreachable!(), + } + } +}