diff --git a/CHANGELOG.md b/CHANGELOG.md index 162bc53c0..3aea6d9c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,9 @@ - fix SPIR-V generation from WGSL, which was broken due to "Kernel" capability - validate buffer storage classes +## Unreleased + - Added support for storage texture arrays for Vulkan and Metal. + ## v0.8 (2021-04-29) - Naga is used by default to translate shaders, SPIRV-Cross is optional behind `cross` feature - Features: diff --git a/wgpu-core/src/device/mod.rs b/wgpu-core/src/device/mod.rs index af97401de..39a9ea0ba 100644 --- a/wgpu-core/src/device/mod.rs +++ b/wgpu-core/src/device/mod.rs @@ -987,7 +987,7 @@ impl Device { Bt::Sampler { .. } => (None, false), Bt::Texture { .. } => (Some(wgt::Features::SAMPLED_TEXTURE_BINDING_ARRAY), false), Bt::StorageTexture { access, .. } => ( - None, + Some(wgt::Features::STORAGE_TEXTURE_BINDING_ARRAY), match access { wgt::StorageTextureAccess::ReadOnly => false, wgt::StorageTextureAccess::WriteOnly => true, @@ -1309,104 +1309,13 @@ impl Device { .views .use_extend(&*texture_view_guard, id, (), ()) .map_err(|_| Error::InvalidTextureView(id))?; - let format_info = view.desc.format.describe(); - let (pub_usage, internal_use) = match decl.ty { - wgt::BindingType::Texture { - sample_type, - view_dimension, - multisampled, - } => { - use wgt::TextureSampleType as Tst; - if multisampled != (view.samples != 1) { - return Err(Error::InvalidTextureMultisample { - binding, - layout_multisampled: multisampled, - view_samples: view.samples, - }); - } - match (sample_type, format_info.sample_type, view.format_features.filterable ) { - (Tst::Uint, Tst::Uint, ..) | - (Tst::Sint, Tst::Sint, ..) | - (Tst::Depth, Tst::Depth, ..) | - // if we expect non-filterable, accept anything float - (Tst::Float { filterable: false }, Tst::Float { .. }, ..) | - // if we expect filterable, require it - (Tst::Float { filterable: true }, Tst::Float { filterable: true }, ..) | - // if we expect filterable, also accept Float that is defined as unfilterable if filterable feature is explicitly enabled - // (only hit if wgt::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES is enabled) - (Tst::Float { filterable: true }, Tst::Float { .. }, true) | - // if we expect float, also accept depth - (Tst::Float { .. }, Tst::Depth, ..) => {} - _ => { - return Err(Error::InvalidTextureSampleType { - binding, - layout_sample_type: sample_type, - view_format: view.desc.format, - }) - } - } - if view_dimension != view.desc.dimension { - return Err(Error::InvalidTextureDimension { - binding, - layout_dimension: view_dimension, - view_dimension: view.desc.dimension, - }); - } - (wgt::TextureUsage::SAMPLED, view.sampled_internal_use) - } - wgt::BindingType::StorageTexture { - access, - format, - view_dimension, - } => { - if format != view.desc.format { - return Err(Error::InvalidStorageTextureFormat { - binding, - layout_format: format, - view_format: view.desc.format, - }); - } - if view_dimension != view.desc.dimension { - return Err(Error::InvalidTextureDimension { - binding, - layout_dimension: view_dimension, - view_dimension: view.desc.dimension, - }); - } - let internal_use = match access { - wgt::StorageTextureAccess::ReadOnly => { - hal::TextureUse::STORAGE_LOAD - } - wgt::StorageTextureAccess::WriteOnly => { - hal::TextureUse::STORAGE_STORE - } - wgt::StorageTextureAccess::ReadWrite => { - if !view.format_features.flags.contains( - wgt::TextureFormatFeatureFlags::STORAGE_READ_WRITE, - ) { - return Err(Error::StorageReadWriteNotSupported( - view.desc.format, - )); - } + let (pub_usage, internal_use) = Self::texture_use_parameters( + binding, + decl, + view, + "SampledTexture, ReadonlyStorageTexture or WriteonlyStorageTexture", + )?; - hal::TextureUse::STORAGE_STORE | hal::TextureUse::STORAGE_LOAD - } - }; - (wgt::TextureUsage::STORAGE, internal_use) - } - _ => return Err(Error::WrongBindingType { - binding, - actual: decl.ty, - expected: - "SampledTexture, ReadonlyStorageTexture or WriteonlyStorageTexture", - }), - }; - - if hal::FormatAspect::from(view.desc.format) - .contains(hal::FormatAspect::DEPTH | hal::FormatAspect::STENCIL) - { - return Err(Error::DepthStencilAspect); - } match view.source { resource::TextureViewSource::Native(ref source_id) => { // Careful here: the texture may no longer have its own ref count, @@ -1454,18 +1363,10 @@ impl Device { .views .use_extend(&*texture_view_guard, id, (), ()) .map_err(|_| Error::InvalidTextureView(id))?; - let (pub_usage, internal_use) = match decl.ty { - wgt::BindingType::Texture { .. } => { - (wgt::TextureUsage::SAMPLED, view.sampled_internal_use) - } - _ => { - return Err(Error::WrongBindingType { - binding, - actual: decl.ty, - expected: "SampledTextureArray", - }) - } - }; + let (pub_usage, internal_use) = + Self::texture_use_parameters(binding, decl, view, + "SampledTextureArray, ReadonlyStorageTextureArray or WriteonlyStorageTextureArray" +)?; match view.source { resource::TextureViewSource::Native(ref source_id) => { @@ -1538,6 +1439,107 @@ impl Device { }) } + fn texture_use_parameters( + binding: u32, + decl: &wgt::BindGroupLayoutEntry, + view: &crate::resource::TextureView, + expected: &'static str, + ) -> Result<(wgt::TextureUsage, hal::TextureUse), binding_model::CreateBindGroupError> { + use crate::binding_model::CreateBindGroupError as Error; + if hal::FormatAspect::from(view.desc.format) + .contains(hal::FormatAspect::DEPTH | hal::FormatAspect::STENCIL) + { + return Err(Error::DepthStencilAspect); + } + let format_info = view.desc.format.describe(); + match decl.ty { + wgt::BindingType::Texture { + sample_type, + view_dimension, + multisampled, + } => { + use wgt::TextureSampleType as Tst; + if multisampled != (view.samples != 1) { + return Err(Error::InvalidTextureMultisample { + binding, + layout_multisampled: multisampled, + view_samples: view.samples, + }); + } + match (sample_type, format_info.sample_type, view.format_features.filterable) { + (Tst::Uint, Tst::Uint, ..) | + (Tst::Sint, Tst::Sint, ..) | + (Tst::Depth, Tst::Depth, ..) | + // if we expect non-filterable, accept anything float + (Tst::Float { filterable: false }, Tst::Float { .. }, ..) | + // if we expect filterable, require it + (Tst::Float { filterable: true }, Tst::Float { filterable: true }, ..) | + // if we expect filterable, also accept Float that is defined as unfilterable if filterable feature is explicitly enabled + // (only hit if wgt::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES is enabled) + (Tst::Float { filterable: true }, Tst::Float { .. }, true) | + // if we expect float, also accept depth + (Tst::Float { .. }, Tst::Depth, ..) => {} + _ => { + return Err(Error::InvalidTextureSampleType { + binding, + layout_sample_type: sample_type, + view_format: view.desc.format, + }) + } + } + if view_dimension != view.desc.dimension { + return Err(Error::InvalidTextureDimension { + binding, + layout_dimension: view_dimension, + view_dimension: view.desc.dimension, + }); + } + Ok((wgt::TextureUsage::SAMPLED, view.sampled_internal_use)) + } + wgt::BindingType::StorageTexture { + access, + format, + view_dimension, + } => { + if format != view.desc.format { + return Err(Error::InvalidStorageTextureFormat { + binding, + layout_format: format, + view_format: view.desc.format, + }); + } + if view_dimension != view.desc.dimension { + return Err(Error::InvalidTextureDimension { + binding, + layout_dimension: view_dimension, + view_dimension: view.desc.dimension, + }); + } + let internal_use = match access { + wgt::StorageTextureAccess::ReadOnly => hal::TextureUse::STORAGE_LOAD, + wgt::StorageTextureAccess::WriteOnly => hal::TextureUse::STORAGE_STORE, + wgt::StorageTextureAccess::ReadWrite => { + if !view + .format_features + .flags + .contains(wgt::TextureFormatFeatureFlags::STORAGE_READ_WRITE) + { + return Err(Error::StorageReadWriteNotSupported(view.desc.format)); + } + + hal::TextureUse::STORAGE_STORE | hal::TextureUse::STORAGE_LOAD + } + }; + Ok((wgt::TextureUsage::STORAGE, internal_use)) + } + _ => Err(Error::WrongBindingType { + binding, + actual: decl.ty, + expected, + }), + } + } + fn create_pipeline_layout( &self, self_id: id::DeviceId, diff --git a/wgpu-hal/src/metal/adapter.rs b/wgpu-hal/src/metal/adapter.rs index a1c49a3b2..0faebe07d 100644 --- a/wgpu-hal/src/metal/adapter.rs +++ b/wgpu-hal/src/metal/adapter.rs @@ -817,6 +817,22 @@ impl super::PrivateCapabilities { } else { Self::version_at_least(major, minor, 11, 0) }, + supports_arrays_of_textures: Self::supports_any( + &device, + &[ + MTLFeatureSet::iOS_GPUFamily3_v2, + MTLFeatureSet::iOS_GPUFamily4_v1, + MTLFeatureSet::iOS_GPUFamily5_v1, + MTLFeatureSet::tvOS_GPUFamily2_v1, + MTLFeatureSet::macOS_GPUFamily1_v3, + MTLFeatureSet::macOS_GPUFamily2_v1, + ], + ), + supports_arrays_of_textures_write: device.supports_family(MTLGPUFamily::Apple6) + || device.supports_family(MTLGPUFamily::Mac1) + || device.supports_family(MTLGPUFamily::Mac2) + || device.supports_family(MTLGPUFamily::MacCatalyst1) + || device.supports_family(MTLGPUFamily::MacCatalyst2), } } @@ -830,8 +846,21 @@ impl super::PrivateCapabilities { | F::VERTEX_WRITABLE_STORAGE; features.set( - F::SAMPLED_TEXTURE_BINDING_ARRAY | F::SAMPLED_TEXTURE_ARRAY_DYNAMIC_INDEXING, - self.msl_version >= MTLLanguageVersion::V2_0, + F::SAMPLED_TEXTURE_BINDING_ARRAY + | F::SAMPLED_TEXTURE_ARRAY_DYNAMIC_INDEXING + | F::SAMPLED_TEXTURE_ARRAY_NON_UNIFORM_INDEXING, + self.msl_version >= MTLLanguageVersion::V2_0 && self.supports_arrays_of_textures, + ); + //// XXX: this is technically not true, as read-only storage images can be used in arrays + //// on precisely the same conditions that sampled textures can. But texel fetch from a + //// sampled texture is a thing; should we bother introducing another feature flag? + features.set( + F::STORAGE_TEXTURE_BINDING_ARRAY + | F::STORAGE_TEXTURE_ARRAY_DYNAMIC_INDEXING + | F::STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING, + self.msl_version >= MTLLanguageVersion::V2_2 + && self.supports_arrays_of_textures + && self.supports_arrays_of_textures_write, ); features.set( F::ADDRESS_MODE_CLAMP_TO_BORDER, diff --git a/wgpu-hal/src/metal/mod.rs b/wgpu-hal/src/metal/mod.rs index b7384286d..2a9ea1254 100644 --- a/wgpu-hal/src/metal/mod.rs +++ b/wgpu-hal/src/metal/mod.rs @@ -192,6 +192,8 @@ struct PrivateCapabilities { can_set_maximum_drawables_count: bool, can_set_display_sync: bool, can_set_next_drawable_timeout: bool, + supports_arrays_of_textures: bool, + supports_arrays_of_textures_write: bool, } #[derive(Clone, Debug)] diff --git a/wgpu-hal/src/vulkan/adapter.rs b/wgpu-hal/src/vulkan/adapter.rs index 84faba59d..c199a1ce6 100644 --- a/wgpu-hal/src/vulkan/adapter.rs +++ b/wgpu-hal/src/vulkan/adapter.rs @@ -138,7 +138,11 @@ impl PhysicalDeviceFeatures { wgt::Features::SAMPLED_TEXTURE_ARRAY_NON_UNIFORM_INDEXING, ), ) - //.shader_storage_image_array_non_uniform_indexing( + .shader_storage_image_array_non_uniform_indexing( + requested_features.contains( + wgt::Features::STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING, + ), + ) //.shader_storage_buffer_array_non_uniform_indexing( .shader_uniform_buffer_array_non_uniform_indexing( requested_features @@ -164,7 +168,11 @@ impl PhysicalDeviceFeatures { wgt::Features::SAMPLED_TEXTURE_ARRAY_NON_UNIFORM_INDEXING, ), ) - //.shader_storage_image_array_non_uniform_indexing( + .shader_storage_image_array_non_uniform_indexing( + requested_features.contains( + wgt::Features::STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING, + ), + ) //.shader_storage_buffer_array_non_uniform_indexing( .shader_uniform_buffer_array_non_uniform_indexing( requested_features @@ -199,6 +207,7 @@ impl PhysicalDeviceFeatures { | F::PUSH_CONSTANTS | F::ADDRESS_MODE_CLAMP_TO_BORDER | F::SAMPLED_TEXTURE_BINDING_ARRAY + | F::STORAGE_TEXTURE_BINDING_ARRAY | F::BUFFER_BINDING_ARRAY; let mut dl_flags = Df::all(); @@ -244,6 +253,10 @@ impl PhysicalDeviceFeatures { F::SAMPLED_TEXTURE_ARRAY_DYNAMIC_INDEXING, self.core.shader_sampled_image_array_dynamic_indexing != 0, ); + features.set( + F::STORAGE_TEXTURE_ARRAY_DYNAMIC_INDEXING, + self.core.shader_storage_image_array_dynamic_indexing != 0, + ); features.set( F::STORAGE_BUFFER_ARRAY_DYNAMIC_INDEXING, self.core.shader_storage_buffer_array_dynamic_indexing != 0, @@ -270,7 +283,9 @@ impl PhysicalDeviceFeatures { if vulkan_1_2.shader_sampled_image_array_non_uniform_indexing != 0 { features |= F::SAMPLED_TEXTURE_ARRAY_NON_UNIFORM_INDEXING; } - //if vulkan_1_2.shader_storage_image_array_non_uniform_indexing != 0 { + if vulkan_1_2.shader_storage_image_array_non_uniform_indexing != 0 { + features |= F::STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING; + } //if vulkan_1_2.shader_storage_buffer_array_non_uniform_indexing != 0 { if vulkan_1_2.shader_uniform_buffer_array_non_uniform_indexing != 0 { features |= F::UNIFORM_BUFFER_ARRAY_NON_UNIFORM_INDEXING; @@ -289,7 +304,9 @@ impl PhysicalDeviceFeatures { if descriptor_indexing.shader_sampled_image_array_non_uniform_indexing != 0 { features |= F::SAMPLED_TEXTURE_ARRAY_NON_UNIFORM_INDEXING; } - //if descriptor_indexing.shader_storage_image_array_non_uniform_indexing != 0 { + if descriptor_indexing.shader_storage_image_array_non_uniform_indexing != 0 { + features |= F::STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING; + } //if descriptor_indexing.shader_storage_buffer_array_non_uniform_indexing != 0 { if descriptor_indexing.shader_uniform_buffer_array_non_uniform_indexing != 0 { features |= F::UNIFORM_BUFFER_ARRAY_NON_UNIFORM_INDEXING; diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index 9c51c43b4..05b24a762 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -537,6 +537,56 @@ bitflags::bitflags! { /// /// This is a native-only feature. const VERTEX_WRITABLE_STORAGE = 0x0000_0020_0000_0000; + /// Allows the user to create uniform arrays of storage textures in shaders: + /// + /// eg. `uniform image2D textures[10]`. + /// + /// This capability allows them to exist and to be indexed by compile time constant + /// values. + /// + /// Supported platforms: + /// - Metal (with MSL 2.2+ on macOS 10.13+) + /// - Vulkan + /// + /// This is a native only feature. + const STORAGE_TEXTURE_BINDING_ARRAY = 0x0000_0040_0000_0000; + /// Allows shaders to index storage texture arrays with dynamically uniform values: + /// + /// eg. `texture_array[uniform_value]` + /// + /// This capability means the hardware will also support STORAGE_TEXTURE_BINDING_ARRAY. + /// + /// Supported platforms: + /// - Metal (with MSL 2.2+ on macOS 10.13+) + /// - Vulkan's shaderSampledImageArrayDynamicIndexing feature + /// + /// This is a native only feature. + const STORAGE_TEXTURE_ARRAY_DYNAMIC_INDEXING = 0x0000_0080_0000_0000; + /// Allows shaders to index storage texture arrays with dynamically non-uniform values: + /// + /// eg. `texture_array[vertex_data]` + /// + /// In order to use this capability, the corresponding GLSL extension must be enabled like so: + /// + /// `#extension GL_EXT_nonuniform_qualifier : require` + /// + /// and then used either as `nonuniformEXT` qualifier in variable declaration: + /// + /// eg. `layout(location = 0) nonuniformEXT flat in int vertex_data;` + /// + /// or as `nonuniformEXT` constructor: + /// + /// eg. `texture_array[nonuniformEXT(vertex_data)]` + /// + /// This capability means the hardware will also support STORAGE_TEXTURE_ARRAY_DYNAMIC_INDEXING + /// and STORAGE_TEXTURE_BINDING_ARRAY. + /// + /// Supported platforms: + /// - Metal (with MSL 2.2+ on macOS 10.13+) + /// - Vulkan 1.2+ (or VK_EXT_descriptor_indexing)'s shaderSampledImageArrayNonUniformIndexing feature) + /// + /// This is a native only feature. + const STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING = 0x0000_0100_0000_0000; /// Enables clear to zero for buffers & images. /// /// Supported platforms: