diff --git a/CHANGELOG.md b/CHANGELOG.md index 881c879560..1a4fe90827 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ Previously, `DeviceExt::create_texture_with_data` only allowed data to be provid #### General - Added `DownlevelFlags::VERTEX_AND_INSTANCE_INDEX_RESPECTS_RESPECTIVE_FIRST_VALUE_IN_INDIRECT_DRAW` to know if `@builtin(vertex_index)` and `@builtin(instance_index)` will respect the `first_vertex` / `first_instance` in indirect calls. If this is not present, both will always start counting from 0. Currently enabled on all backends except DX12. By @cwfitzgerald in [#4722](https://github.com/gfx-rs/wgpu/pull/4722) - No longer validate surfaces against their allowed extent range on configure. This caused warnings that were almost impossible to avoid. As before, the resulting behavior depends on the compositor. By @wumpf in [#????](https://github.com/gfx-rs/wgpu/pull/????) +- Added support for the float32-filterable feature. By @almarklein in [#4759](https://github.com/gfx-rs/wgpu/pull/4759) #### OpenGL - `@builtin(instance_index)` now properly reflects the range provided in the draw call instead of always counting from 0. By @cwfitzgerald in [#4722](https://github.com/gfx-rs/wgpu/pull/4722). diff --git a/deno_webgpu/01_webgpu.js b/deno_webgpu/01_webgpu.js index 92157e5490..3f7b1ed570 100644 --- a/deno_webgpu/01_webgpu.js +++ b/deno_webgpu/01_webgpu.js @@ -4980,6 +4980,7 @@ webidl.converters["GPUFeatureName"] = webidl.createEnumConverter( "texture-compression-astc", "rg11b10ufloat-renderable", "bgra8unorm-storage", + "float32-filterable", // extended from spec diff --git a/deno_webgpu/lib.rs b/deno_webgpu/lib.rs index b69157df52..7d801e8ae4 100644 --- a/deno_webgpu/lib.rs +++ b/deno_webgpu/lib.rs @@ -266,6 +266,9 @@ fn deserialize_features(features: &wgpu_types::Features) -> Vec<&'static str> { if features.contains(wgpu_types::Features::BGRA8UNORM_STORAGE) { return_features.push("bgra8unorm-storage"); } + if features.contains(wgpu_types::Features::FLOAT32_FILTERABLE) { + return_features.push("float32-filterable"); + } // extended from spec @@ -498,6 +501,10 @@ impl From for wgpu_types::Features { wgpu_types::Features::BGRA8UNORM_STORAGE, required_features.0.contains("bgra8unorm-storage"), ); + features.set( + wgpu_types::Features::FLOAT32_FILTERABLE, + required_features.0.contains("float32-filterable"), + ); // extended from spec diff --git a/deno_webgpu/webgpu.idl b/deno_webgpu/webgpu.idl index 0b6b04eb4e..bf4da0124b 100644 --- a/deno_webgpu/webgpu.idl +++ b/deno_webgpu/webgpu.idl @@ -103,6 +103,7 @@ enum GPUFeatureName { "shader-f16", "rg11b10ufloat-renderable", "bgra8unorm-storage", + "float32-filterable", // extended from spec diff --git a/tests/tests/bgra8unorm_storage.rs b/tests/tests/bgra8unorm_storage.rs index 0caf7f80e3..942b6c5a8b 100644 --- a/tests/tests/bgra8unorm_storage.rs +++ b/tests/tests/bgra8unorm_storage.rs @@ -1,4 +1,4 @@ -//! Tests for texture copy bounds checks. +//! Tests for BGRA8UNORM_STORAGE feature use std::borrow::Cow; diff --git a/tests/tests/float32_filterable.rs b/tests/tests/float32_filterable.rs new file mode 100644 index 0000000000..c170deda9b --- /dev/null +++ b/tests/tests/float32_filterable.rs @@ -0,0 +1,75 @@ +//! Tests for FLOAT32_FILTERABLE feature. + +use wgpu_test::{fail, gpu_test, GpuTestConfiguration, TestParameters}; + +fn create_texture_binding(device: &wgpu::Device, format: wgpu::TextureFormat, filterable: bool) { + let texture = device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: 256, + height: 256, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + view_formats: &[], + }); + + let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + + let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable }, + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + }, + count: None, + }], + }); + + let _bg = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &bgl, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&view), + }], + }); +} + +#[gpu_test] +static FLOAT32_FILTERABLE_WITHOUT_FEATURE: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default()) + .run_sync(|ctx| { + let device = &ctx.device; + // Unorm textures are always filterable + create_texture_binding(device, wgpu::TextureFormat::R8Unorm, true); + create_texture_binding(device, wgpu::TextureFormat::R8Unorm, false); + // As are float16 textures + create_texture_binding(device, wgpu::TextureFormat::R16Float, true); + create_texture_binding(device, wgpu::TextureFormat::R16Float, false); + // Float 32 textures can be used as non-filterable only + create_texture_binding(device, wgpu::TextureFormat::R32Float, false); + // This is supposed to fail, since we have not activated the feature + fail(&ctx.device, || { + create_texture_binding(device, wgpu::TextureFormat::R32Float, true); + }); + }); + +#[gpu_test] +static FLOAT32_FILTERABLE_WITH_FEATURE: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().features(wgpu::Features::FLOAT32_FILTERABLE)) + .run_sync(|ctx| { + let device = &ctx.device; + // With the feature enabled, it does work! + create_texture_binding(device, wgpu::TextureFormat::R32Float, true); + create_texture_binding(device, wgpu::TextureFormat::Rg32Float, true); + create_texture_binding(device, wgpu::TextureFormat::Rgba32Float, true); + }); diff --git a/tests/tests/root.rs b/tests/tests/root.rs index 5fd119b2c9..e3f116b0c7 100644 --- a/tests/tests/root.rs +++ b/tests/tests/root.rs @@ -15,6 +15,7 @@ mod create_surface_error; mod device; mod encoder; mod external_texture; +mod float32_filterable; mod instance; mod life_cycle; mod mem_leaks; diff --git a/wgpu-core/src/device/resource.rs b/wgpu-core/src/device/resource.rs index fb48115ca8..b6ee9aec07 100644 --- a/wgpu-core/src/device/resource.rs +++ b/wgpu-core/src/device/resource.rs @@ -2064,7 +2064,7 @@ impl Device { .views .add_single(&*texture_view_guard, id) .ok_or(Error::InvalidTextureView(id))?; - let (pub_usage, internal_use) = Self::texture_use_parameters( + let (pub_usage, internal_use) = self.texture_use_parameters( binding, decl, view, @@ -2095,7 +2095,7 @@ impl Device { .add_single(&*texture_view_guard, id) .ok_or(Error::InvalidTextureView(id))?; let (pub_usage, internal_use) = - Self::texture_use_parameters(binding, decl, view, + self.texture_use_parameters(binding, decl, view, "SampledTextureArray, ReadonlyStorageTextureArray or WriteonlyStorageTextureArray")?; Self::create_texture_binding( view, @@ -2197,6 +2197,7 @@ impl Device { } pub(crate) fn texture_use_parameters( + self: &Arc, binding: u32, decl: &wgt::BindGroupLayoutEntry, view: &TextureView, @@ -2227,7 +2228,7 @@ impl Device { let compat_sample_type = view .desc .format - .sample_type(Some(view.desc.range.aspect)) + .sample_type(Some(view.desc.range.aspect), Some(self.features)) .unwrap(); match (sample_type, compat_sample_type) { (Tst::Uint, Tst::Uint) | @@ -3198,6 +3199,24 @@ impl Device { Ok(pipeline) } + pub(crate) fn get_texture_format_features( + &self, + adapter: &Adapter, + format: TextureFormat, + ) -> wgt::TextureFormatFeatures { + // Variant of adapter.get_texture_format_features that takes device features into account + use wgt::TextureFormatFeatureFlags as tfsc; + let mut format_features = adapter.get_texture_format_features(format); + if (format == TextureFormat::R32Float + || format == TextureFormat::Rg32Float + || format == TextureFormat::Rgba32Float) + && !self.features.contains(wgt::Features::FLOAT32_FILTERABLE) + { + format_features.flags.set(tfsc::FILTERABLE, false); + } + format_features + } + pub(crate) fn describe_format_features( &self, adapter: &Adapter, @@ -3213,7 +3232,7 @@ impl Device { let downlevel = !self.downlevel.is_webgpu_compliant(); if using_device_features || downlevel { - Ok(adapter.get_texture_format_features(format)) + Ok(self.get_texture_format_features(adapter, format)) } else { Ok(format.guaranteed_format_features(self.features)) } diff --git a/wgpu-hal/src/dx11/adapter.rs b/wgpu-hal/src/dx11/adapter.rs index 41b4b4e573..3d465ae21f 100644 --- a/wgpu-hal/src/dx11/adapter.rs +++ b/wgpu-hal/src/dx11/adapter.rs @@ -155,6 +155,10 @@ impl super::Adapter { // bgra8unorm-storage is never supported on dx11 according to: // https://learn.microsoft.com/en-us/windows/win32/direct3ddxgi/format-support-for-direct3d-11-0-feature-level-hardware#dxgi_format_b8g8r8a8_unormfcs-87 + // float32-filterable should always be available on dx11 + // https://learn.microsoft.com/en-us/windows/win32/direct3ddxgi/format-support-for-direct3d-11-0-feature-level-hardware#dxgi_format_r32g32b32a32_floatfcs-2 + features.set(wgt::Features::FLOAT32_FILTERABLE, true); + // // Fill out limits and alignments // diff --git a/wgpu-hal/src/dx12/adapter.rs b/wgpu-hal/src/dx12/adapter.rs index c28e88c658..1db9b0877d 100644 --- a/wgpu-hal/src/dx12/adapter.rs +++ b/wgpu-hal/src/dx12/adapter.rs @@ -294,6 +294,9 @@ impl super::Adapter { bgra8unorm_storage_supported, ); + // float32-filterable should always be available on d3d12 + features.set(wgt::Features::FLOAT32_FILTERABLE, true); + // TODO: Determine if IPresentationManager is supported let presentation_timer = auxil::dxgi::time::PresentationTimer::new_dxgi(); diff --git a/wgpu-hal/src/gles/adapter.rs b/wgpu-hal/src/gles/adapter.rs index 0e8285240a..8c35e452c1 100644 --- a/wgpu-hal/src/gles/adapter.rs +++ b/wgpu-hal/src/gles/adapter.rs @@ -549,6 +549,13 @@ impl super::Adapter { ); } + features.set( + wgt::Features::FLOAT32_FILTERABLE, + extensions.contains("GL_ARB_color_buffer_float") + || extensions.contains("GL_EXT_color_buffer_float") + || extensions.contains("OES_texture_float_linear"), + ); + // We *might* be able to emulate bgra8unorm-storage but currently don't attempt to. let mut private_caps = super::PrivateCapabilities::empty(); @@ -594,14 +601,6 @@ impl super::Adapter { super::PrivateCapabilities::COLOR_BUFFER_FLOAT, color_buffer_float, ); - private_caps.set( - super::PrivateCapabilities::TEXTURE_FLOAT_LINEAR, - if full_ver.is_some() { - color_buffer_float - } else { - extensions.contains("OES_texture_float_linear") - }, - ); private_caps.set(super::PrivateCapabilities::QUERY_BUFFERS, query_buffers); private_caps.set(super::PrivateCapabilities::QUERY_64BIT, full_ver.is_some()); private_caps.set( @@ -1022,8 +1021,7 @@ impl crate::Adapter for super::Adapter { | Tfc::MULTISAMPLE_RESOLVE, ); - let texture_float_linear = - private_caps_fn(super::PrivateCapabilities::TEXTURE_FLOAT_LINEAR, filterable); + let texture_float_linear = feature_fn(wgt::Features::FLOAT32_FILTERABLE, filterable); match format { Tf::R8Unorm => filterable_renderable, diff --git a/wgpu-hal/src/gles/command.rs b/wgpu-hal/src/gles/command.rs index 133e99598e..28dbf1688d 100644 --- a/wgpu-hal/src/gles/command.rs +++ b/wgpu-hal/src/gles/command.rs @@ -605,7 +605,7 @@ impl crate::CommandEncoder for super::CommandEncoder { if !cat.ops.contains(crate::AttachmentOps::LOAD) { let c = &cat.clear_value; self.cmd_buffer.commands.push( - match cat.target.view.format.sample_type(None).unwrap() { + match cat.target.view.format.sample_type(None, None).unwrap() { wgt::TextureSampleType::Float { .. } => C::ClearColorF { draw_buffer: i as u32, color: [c.r as f32, c.g as f32, c.b as f32, c.a as f32], diff --git a/wgpu-hal/src/gles/device.rs b/wgpu-hal/src/gles/device.rs index e610d37fb5..35c6f910de 100644 --- a/wgpu-hal/src/gles/device.rs +++ b/wgpu-hal/src/gles/device.rs @@ -746,7 +746,7 @@ impl crate::Device for super::Device { unsafe { gl.bind_texture(target, Some(raw)) }; //Note: this has to be done before defining the storage! - match desc.format.sample_type(None) { + match desc.format.sample_type(None, Some(self.shared.features)) { Some( wgt::TextureSampleType::Float { filterable: false } | wgt::TextureSampleType::Uint diff --git a/wgpu-hal/src/gles/mod.rs b/wgpu-hal/src/gles/mod.rs index df5ca4e9e8..9525e45d13 100644 --- a/wgpu-hal/src/gles/mod.rs +++ b/wgpu-hal/src/gles/mod.rs @@ -186,8 +186,6 @@ bitflags::bitflags! { const COLOR_BUFFER_HALF_FLOAT = 1 << 8; /// Supports `f11/f10` and `f32` color buffers const COLOR_BUFFER_FLOAT = 1 << 9; - /// Supports linear flitering `f32` textures. - const TEXTURE_FLOAT_LINEAR = 1 << 10; /// Supports query buffer objects. const QUERY_BUFFERS = 1 << 11; /// Supports 64 bit queries via `glGetQueryObjectui64v` diff --git a/wgpu-hal/src/metal/adapter.rs b/wgpu-hal/src/metal/adapter.rs index 1d3e2e5016..c398b28583 100644 --- a/wgpu-hal/src/metal/adapter.rs +++ b/wgpu-hal/src/metal/adapter.rs @@ -604,6 +604,9 @@ impl super::PrivateCapabilities { 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) + && 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, @@ -821,6 +824,7 @@ impl super::PrivateCapabilities { | F::DEPTH32FLOAT_STENCIL8 | F::BGRA8UNORM_STORAGE; + features.set(F::FLOAT32_FILTERABLE, self.supports_float_filtering); features.set( F::INDIRECT_FIRST_INSTANCE | F::MULTI_DRAW_INDIRECT, self.indirect_draw_dispatch, diff --git a/wgpu-hal/src/metal/mod.rs b/wgpu-hal/src/metal/mod.rs index 0ddf96ed4a..8890092d31 100644 --- a/wgpu-hal/src/metal/mod.rs +++ b/wgpu-hal/src/metal/mod.rs @@ -191,6 +191,7 @@ struct PrivateCapabilities { function_specialization: bool, depth_clip_mode: bool, texture_cube_array: bool, + supports_float_filtering: bool, format_depth24_stencil8: bool, format_depth32_stencil8_filter: bool, format_depth32_stencil8_none: bool, diff --git a/wgpu-hal/src/vulkan/adapter.rs b/wgpu-hal/src/vulkan/adapter.rs index e0f465db52..dc67e85a5e 100644 --- a/wgpu-hal/src/vulkan/adapter.rs +++ b/wgpu-hal/src/vulkan/adapter.rs @@ -536,6 +536,10 @@ impl PhysicalDeviceFeatures { supports_bgra8unorm_storage(instance, phd, caps.device_api_version), ); + features.set( + F::FLOAT32_FILTERABLE, + is_float32_filterable_supported(instance, phd), + ); features.set( F::TEXTURE_FORMAT_NV12, (caps.device_api_version >= vk::API_VERSION_1_1 @@ -1568,8 +1572,8 @@ impl crate::Adapter for super::Adapter { .framebuffer_stencil_sample_counts .min(limits.sampled_image_stencil_sample_counts) } else { - match format.sample_type(None) { - Some(wgt::TextureSampleType::Float { filterable: _ }) => limits + match format.sample_type(None, None) { + Some(wgt::TextureSampleType::Float { .. }) => limits .framebuffer_color_sample_counts .min(limits.sampled_image_color_sample_counts), Some(wgt::TextureSampleType::Sint) | Some(wgt::TextureSampleType::Uint) => { @@ -1760,6 +1764,21 @@ fn is_format_16bit_norm_supported(instance: &ash::Instance, phd: vk::PhysicalDev r16unorm && r16snorm && rg16unorm && rg16snorm && rgba16unorm && rgba16snorm } +fn is_float32_filterable_supported(instance: &ash::Instance, phd: vk::PhysicalDevice) -> bool { + let tiling = vk::ImageTiling::OPTIMAL; + let features = vk::FormatFeatureFlags::SAMPLED_IMAGE_FILTER_LINEAR; + let r_float = supports_format(instance, phd, vk::Format::R32_SFLOAT, tiling, features); + let rg_float = supports_format(instance, phd, vk::Format::R32G32_SFLOAT, tiling, features); + let rgba_float = supports_format( + instance, + phd, + vk::Format::R32G32B32A32_SFLOAT, + tiling, + features, + ); + r_float && rg_float && rgba_float +} + fn supports_format( instance: &ash::Instance, phd: vk::PhysicalDevice, diff --git a/wgpu-hal/src/vulkan/conv.rs b/wgpu-hal/src/vulkan/conv.rs index e2d33b20e7..70dbb5714d 100644 --- a/wgpu-hal/src/vulkan/conv.rs +++ b/wgpu-hal/src/vulkan/conv.rs @@ -200,7 +200,7 @@ impl crate::ColorAttachment<'_, super::Api> { .view .attachment .view_format - .sample_type(None) + .sample_type(None, None) .unwrap() { wgt::TextureSampleType::Float { .. } => vk::ClearColorValue { diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index 56b8ee4d0c..bdef49a60c 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -337,7 +337,18 @@ bitflags::bitflags! { // ? const NORM16_FILTERABLE = 1 << 17; (https://github.com/gpuweb/gpuweb/issues/3839) // ? const NORM16_RESOLVE = 1 << 18; (https://github.com/gpuweb/gpuweb/issues/3839) - // TODO const FLOAT32_FILTERABLE = 1 << 19; + + /// Allows textures with formats "r32float", "rg32float", and "rgba32float" to be filterable. + /// + /// Supported Platforms: + /// - Vulkan (mainly on Desktop GPUs) + /// - DX12 + /// - Metal on macOS or Apple9+ GPUs, optional on iOS/iPadOS with Apple7/8 GPUs + /// - GL with one of `GL_ARB_color_buffer_float`/`GL_EXT_color_buffer_float`/`OES_texture_float_linear` + /// + /// This is a web and native feature. + const FLOAT32_FILTERABLE = 1 << 19; + // ? const FLOAT32_BLENDABLE = 1 << 20; (https://github.com/gpuweb/gpuweb/issues/3556) // ? const 32BIT_FORMAT_MULTISAMPLE = 1 << 21; (https://github.com/gpuweb/gpuweb/issues/3844) // ? const 32BIT_FORMAT_RESOLVE = 1 << 22; (https://github.com/gpuweb/gpuweb/issues/3844) @@ -3218,10 +3229,16 @@ impl TextureFormat { Self::Astc { .. } => ( noaa, basic), }; - let is_filterable = - self.sample_type(None) == Some(TextureSampleType::Float { filterable: true }); + // 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_filterable); + flags.set(TextureFormatFeatureFlags::BLENDABLE, is_blendable); TextureFormatFeatures { allowed_usages, @@ -3233,9 +3250,17 @@ impl TextureFormat { /// /// Returns `None` only if the format is combined depth-stencil /// and `TextureAspect::All` or no `aspect` was provided - pub fn sample_type(&self, aspect: Option) -> Option { + pub fn sample_type( + &self, + aspect: Option, + device_features: Option, + ) -> Option { 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; @@ -3256,7 +3281,7 @@ impl TextureFormat { | Self::Rgb10a2Unorm | Self::Rg11b10Float => Some(float), - Self::R32Float | Self::Rg32Float | Self::Rgba32Float => Some(unfilterable_float), + Self::R32Float | Self::Rg32Float | Self::Rgba32Float => Some(float32_sample_type), Self::R8Uint | Self::Rg8Uint diff --git a/wgpu/src/backend/web.rs b/wgpu/src/backend/web.rs index 05e05db8de..b9bb04ffb0 100644 --- a/wgpu/src/backend/web.rs +++ b/wgpu/src/backend/web.rs @@ -661,7 +661,7 @@ fn map_map_mode(mode: crate::MapMode) -> u32 { } } -const FEATURES_MAPPING: [(wgt::Features, web_sys::GpuFeatureName); 10] = [ +const FEATURES_MAPPING: [(wgt::Features, web_sys::GpuFeatureName); 11] = [ //TODO: update the name ( wgt::Features::DEPTH_CLIP_CONTROL, @@ -703,6 +703,10 @@ const FEATURES_MAPPING: [(wgt::Features, web_sys::GpuFeatureName); 10] = [ wgt::Features::BGRA8UNORM_STORAGE, web_sys::GpuFeatureName::Bgra8unormStorage, ), + ( + wgt::Features::FLOAT32_FILTERABLE, + web_sys::GpuFeatureName::Float32Filterable, + ), ]; fn map_wgt_features(supported_features: web_sys::GpuSupportedFeatures) -> wgt::Features {