Added storage texture array support.

I also took the liberty to refactor texture format checking a bit,
and tighten up sampled texture support detection for Metal as well.
This commit is contained in:
Alex S 2021-06-18 19:20:37 +03:00
parent ca291bfc4f
commit 52fab481e6
5 changed files with 218 additions and 116 deletions

View File

@ -982,7 +982,7 @@ impl<A: HalApi> Device<A> {
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,
@ -1304,104 +1304,13 @@ impl<A: HalApi> Device<A> {
.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,
@ -1449,18 +1358,10 @@ impl<A: HalApi> Device<A> {
.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) => {
@ -1533,6 +1434,109 @@ impl<A: HalApi> Device<A> {
})
}
fn texture_use_parameters(
binding: u32,
decl: &wgt::BindGroupLayoutEntry,
view: &crate::resource::TextureView<A>,
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))
}
_ => {
return Err(Error::WrongBindingType {
binding,
actual: decl.ty,
expected,
})
}
}
}
fn create_pipeline_layout(
&self,
self_id: id::DeviceId,

View File

@ -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,

View File

@ -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)]

View File

@ -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;

View File

@ -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: