fp32 texture formats are not necessarily filterable (#9855)

* fp32 texture formats are not necessarily filterable

Mobile devices don't always support filtering of fp32 textures.
This PR adds a query at the driver level.

High precision EVSM is now conditional to this feature being supported.

* use MTLDevice supports32BitFloatFiltering if available
This commit is contained in:
Mathias Agopian
2026-03-30 17:29:11 -07:00
committed by GitHub
parent a007b59f39
commit f9573a112d
16 changed files with 119 additions and 8 deletions

View File

@@ -2115,9 +2115,10 @@ public class View {
public int msaaSamples = 1;
/**
* Whether to use a 32-bits or 16-bits texture format for VSM shadow maps. 32-bits
* precision is rarely needed, but it does reduces light leaks as well as "fading"
* precision is rarely needed, but it does reduce light leaks as well as "fading"
* of the shadows in some situations. Setting highPrecision to true for a single
* shadow map will double the memory usage of all shadow maps.
* This may not be supported on all mobile devices.
*/
public boolean highPrecision = false;
/**

View File

@@ -48,7 +48,7 @@ class ostream;
/**
* Types and enums used by filament's driver.
*
* Effectively these types are public but should not be used directly. Instead use public classes
* Effectively these types are public but should not be used directly. Instead, use public classes
* internal redeclaration of these types.
* For e.g. Use Texture::Sampler instead of filament::SamplerType.
*/
@@ -1147,6 +1147,19 @@ constexpr bool isDepthFormat(TextureFormat format) noexcept {
}
}
//! returns whether this format a 32-bit float format
constexpr bool isFp32ColorFormat(TextureFormat format) noexcept {
switch (format) {
case TextureFormat::R32F:
case TextureFormat::RG32F:
case TextureFormat::RGB32F:
case TextureFormat::RGBA32F:
return true;
default:
return false;
}
}
constexpr bool isStencilFormat(TextureFormat format) noexcept {
switch (format) {
case TextureFormat::STENCIL8:

View File

@@ -398,6 +398,7 @@ DECL_DRIVER_API_SYNCHRONOUS_N(void, getPlatformSync, backend::SyncHandle, sh,
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isTextureFormatSupported, backend::TextureFormat, format)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isTextureSwizzleSupported)
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isTextureFormatMipmappable, backend::TextureFormat, format)
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isTextureFormatFilterable, backend::TextureFormat, format)
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isRenderTargetFormatSupported, backend::TextureFormat, format)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isFrameBufferFetchSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isFrameBufferFetchMultiSampleSupported)

View File

@@ -1259,6 +1259,21 @@ bool MetalDriver::isTextureFormatMipmappable(TextureFormat format) {
}
}
bool MetalDriver::isTextureFormatFilterable(TextureFormat format) {
if (isFp32ColorFormat(format)) {
if (@available(macOS 11.0, iOS 14.0, *)) {
return mContext->device.supports32BitFloatFiltering;
}
return mContext->highestSupportedGpuFamily.apple >= 7 ||
mContext->highestSupportedGpuFamily.mac >= 1;
}
if (isUnsignedIntFormat(format) || isSignedIntFormat(format) ||
isDepthFormat(format) || isStencilFormat(format)) {
return false;
}
return true;
}
bool MetalDriver::isRenderTargetFormatSupported(TextureFormat format) {
MTLPixelFormat mtlFormat = getMetalFormat(mContext, format);
// RGB9E5 isn't supported on Mac as a color render target.

View File

@@ -194,6 +194,10 @@ bool NoopDriver::isTextureFormatMipmappable(TextureFormat format) {
return true;
}
bool NoopDriver::isTextureFormatFilterable(TextureFormat format) {
return true;
}
bool NoopDriver::isRenderTargetFormatSupported(TextureFormat format) {
return true;
}

View File

@@ -183,9 +183,16 @@ void GLDescriptorSet::update(OpenGLState& gl, HandleAllocatorGL& handleAllocator
params.wrapT = SamplerWrapMode::CLAMP_TO_EDGE;
params.wrapR = SamplerWrapMode::CLAMP_TO_EDGE;
}
// GLES3.x specification forbids depth textures to be filtered.
if (t && isDepthFormat(t->format)
&& params.compareMode == SamplerCompareMode::NONE) {
bool const depthTextureNoPcf = isDepthFormat(t->format) &&
params.compareMode == SamplerCompareMode::NONE;
// GLES3.x may not support filtering of fp32 textures
bool const fp32TextureNotFilterable = isFp32ColorFormat(t->format) &&
!gl.context().ext.OES_texture_float_linear;
if (t && (depthTextureNoPcf || fp32TextureNotFilterable)) {
params.filterMag = SamplerMagFilter::NEAREST;
switch (params.filterMin) {
case SamplerMinFilter::LINEAR:

View File

@@ -675,6 +675,8 @@ void OpenGLContext::initExtensionsGLES(Extensions* ext, GLint major, GLint minor
ext->OES_EGL_image_external_essl3 = exts.has("GL_OES_EGL_image_external_essl3"sv);
ext->OES_rgb8_rgba8 = exts.has("GL_OES_rgb8_rgba8"sv);
ext->OES_standard_derivatives = exts.has("GL_OES_standard_derivatives"sv);
ext->OES_texture_float_linear = exts.has("GL_OES_texture_float_linear"sv);
ext->OES_texture_half_float_linear = exts.has("GL_OES_texture_half_float_linear"sv);
ext->OES_texture_npot = exts.has("GL_OES_texture_npot"sv);
ext->OES_vertex_array_object = exts.has("GL_OES_vertex_array_object"sv);
ext->OVR_multiview2 = exts.has("GL_OVR_multiview2"sv);
@@ -682,14 +684,15 @@ void OpenGLContext::initExtensionsGLES(Extensions* ext, GLint major, GLint minor
ext->WEBGL_compressed_texture_s3tc = exts.has("WEBGL_compressed_texture_s3tc"sv);
ext->WEBGL_compressed_texture_s3tc_srgb = exts.has("WEBGL_compressed_texture_s3tc_srgb"sv);
// ES 3.2 implies EXT_color_buffer_float
// ES 3.2 implies EXT_color_buffer_float (but not necessarily filterable)
if (major > 3 || (major == 3 && minor >= 2)) {
ext->EXT_color_buffer_float = true;
}
// ES 3.x implies EXT_discard_framebuffer and OES_vertex_array_object
// ES 3.x implies EXT_discard_framebuffer, OES_vertex_array_object and OES_texture_half_float_linear
if (major >= 3) {
ext->EXT_discard_framebuffer = true;
ext->OES_vertex_array_object = true;
ext->OES_texture_half_float_linear = true;
}
}
@@ -740,6 +743,8 @@ void OpenGLContext::initExtensionsGL(Extensions* ext, GLint major, GLint minor)
ext->OES_EGL_image_external_essl3 = false;
ext->OES_rgb8_rgba8 = true;
ext->OES_standard_derivatives = true;
ext->OES_texture_float_linear = true;
ext->OES_texture_half_float_linear = true;
ext->OES_texture_npot = true;
ext->OES_vertex_array_object = true;
ext->OVR_multiview2 = exts.has("GL_OVR_multiview2"sv);

View File

@@ -208,6 +208,8 @@ public:
bool OES_packed_depth_stencil;
bool OES_rgb8_rgba8;
bool OES_standard_derivatives;
bool OES_texture_float_linear;
bool OES_texture_half_float_linear;
bool OES_texture_npot;
bool OES_vertex_array_object;
bool OVR_multiview2;

View File

@@ -2775,6 +2775,39 @@ bool OpenGLDriver::isTextureFormatMipmappable(TextureFormat const format) {
}
}
bool OpenGLDriver::isTextureFormatFilterable(TextureFormat format) {
auto const& gl = getBackendState();
switch (format) {
case TextureFormat::R8:
case TextureFormat::RG8:
case TextureFormat::RGB8:
case TextureFormat::RGB565:
case TextureFormat::RGBA4:
case TextureFormat::RGB5_A1:
case TextureFormat::RGBA8:
case TextureFormat::RGBA8_SNORM:
case TextureFormat::RGB10_A2:
case TextureFormat::SRGB8:
case TextureFormat::SRGB8_A8:
case TextureFormat::R11F_G11F_B10F:
case TextureFormat::RGB9_E5:
return true;
case TextureFormat::R16F:
case TextureFormat::RG16F:
case TextureFormat::RGB16F:
case TextureFormat::RGBA16F:
return gl.ext.OES_texture_half_float_linear;
case TextureFormat::R32F:
case TextureFormat::RG32F:
case TextureFormat::RGB32F:
case TextureFormat::RGBA32F:
return gl.ext.OES_texture_float_linear;
default:
return false;
}
return true;
}
bool OpenGLDriver::isRenderTargetFormatSupported(TextureFormat const format) {
// Supported formats per http://docs.gl/es3/glRenderbufferStorage, note that desktop OpenGL may
// support more formats, but it requires querying GL_INTERNALFORMAT_SUPPORTED which is not

View File

@@ -1548,6 +1548,16 @@ bool VulkanDriver::isTextureFormatMipmappable(TextureFormat format) {
}
}
bool VulkanDriver::isTextureFormatFilterable(TextureFormat format) {
VkFormat vkformat = fvkutils::getVkFormat(format);
if (vkformat == VK_FORMAT_UNDEFINED) {
return false;
}
VkFormatProperties info;
vkGetPhysicalDeviceFormatProperties(mPlatform->getPhysicalDevice(), vkformat, &info);
return (info.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT) != 0;
}
bool VulkanDriver::isRenderTargetFormatSupported(TextureFormat format) {
VkFormat vkformat = fvkutils::getVkFormat(format);
if (vkformat == VK_FORMAT_UNDEFINED) {

View File

@@ -822,6 +822,17 @@ bool WebGPUDriver::isTextureFormatMipmappable(const TextureFormat format) {
return WebGPUTexture::supportsMultipleMipLevelsViaStorageBinding(webGpuFormat);
}
bool WebGPUDriver::isTextureFormatFilterable(TextureFormat format) {
if (isFp32ColorFormat(format)) {
return mDevice.HasFeature(wgpu::FeatureName::Float32Filterable);
}
if (isUnsignedIntFormat(format) || isSignedIntFormat(format) ||
isDepthFormat(format) || isStencilFormat(format)) {
return false;
}
return true;
}
bool WebGPUDriver::isRenderTargetFormatSupported(const TextureFormat format) {
//todo
return true;

View File

@@ -728,9 +728,10 @@ struct VsmShadowOptions {
/**
* Whether to use a 32-bits or 16-bits texture format for VSM shadow maps. 32-bits
* precision is rarely needed, but it does reduces light leaks as well as "fading"
* precision is rarely needed, but it does reduce light leaks as well as "fading"
* of the shadows in some situations. Setting highPrecision to true for a single
* shadow map will double the memory usage of all shadow maps.
* This may not be supported on all mobile devices.
*/
bool highPrecision = false;

View File

@@ -106,6 +106,8 @@ FView::FView(FEngine& engine)
{
DriverApi& driver = engine.getDriverApi();
mIsHighPrecisionEvsmSupported = driver.isTextureFormatFilterable(TextureFormat::RGBA32F);
mFeatureLevel = engine.getSupportedFeatureLevel();
auto const& layout = engine.getPerRenderableDescriptorSetLayout();
@@ -1483,6 +1485,9 @@ void FView::setAmbientOcclusionOptions(AmbientOcclusionOptions options) noexcept
}
void FView::setVsmShadowOptions(VsmShadowOptions options) noexcept {
options.msaaSamples = std::max(uint8_t(0), options.msaaSamples);
if (!mIsHighPrecisionEvsmSupported) {
options.highPrecision = false;
}
mVsmShadowOptions = options;
}

View File

@@ -586,6 +586,7 @@ private:
bool mCulling = true;
bool mFrontFaceWindingInverted = false;
bool mIsTransparentPickingEnabled = false;
bool mIsHighPrecisionEvsmSupported = true;
FRenderTarget* mRenderTarget = nullptr;

View File

@@ -69,6 +69,7 @@ public:
MOCK_METHOD(bool, isTextureFormatSupported, (backend::TextureFormat format), (override));
MOCK_METHOD(bool, isTextureSwizzleSupported, (), (override));
MOCK_METHOD(bool, isTextureFormatMipmappable, (backend::TextureFormat format), (override));
MOCK_METHOD(bool, isTextureFormatFilterable, (backend::TextureFormat format), (override));
MOCK_METHOD(bool, isRenderTargetFormatSupported, (backend::TextureFormat format), (override));
MOCK_METHOD(bool, isFrameBufferFetchSupported, (), (override));
MOCK_METHOD(bool, isFrameBufferFetchMultiSampleSupported, (), (override));

View File

@@ -1741,9 +1741,10 @@ export interface View$VsmShadowOptions {
msaaSamples?: number;
/**
* Whether to use a 32-bits or 16-bits texture format for VSM shadow maps. 32-bits
* precision is rarely needed, but it does reduces light leaks as well as "fading"
* precision is rarely needed, but it does reduce light leaks as well as "fading"
* of the shadows in some situations. Setting highPrecision to true for a single
* shadow map will double the memory usage of all shadow maps.
* This may not be supported on all mobile devices.
*/
highPrecision?: boolean;
/**