[Fix] (MG_Backend, MG_Impl/GLImpl, MG_Util): make anisotropic filtering actually reachable - advertise GL_EXT/ARB_texture_filter_anisotropic only where the host driver or the samplerAnisotropy device feature supports it, answer GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT from the backend limit, and honor the sampler state on DirectVulkan (feature enable, limit clamp, LINEAR-only gate, resolved value in the sampler cache key)

This commit is contained in:
2026-07-16 22:59:11 -04:00
parent e2f873c95c
commit d4922cb0fb
17 changed files with 207 additions and 29 deletions
+3
View File
@@ -232,6 +232,9 @@ namespace MobileGL {
struct DynamicBackendParameters {
SizeT UniformBufferOffsetAlignment = 256;
// GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT. 1.0 means the backend cannot filter anisotropically,
// which is also why the extension is not advertised in that case.
Float MaxTextureMaxAnisotropy = 1.0f;
Float AliasedLineWidthRangeMin = 1.0f;
Float AliasedLineWidthRangeMax = 1.0f;
Float SmoothLineWidthRangeMin = 1.0f;
@@ -605,9 +605,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
{
.TargetGLVersion = {3, 3, 0}, // Target OpenGL Version
.TargetGLSLVersion = {4, 6, 0}, // Target Shading Language Version
// Baseline advertisement (no timer queries yet); reconciled once
// the ES capabilities exist, see UpdateAdvertisedTimerQueryExtension.
.Extensions = BuildAdvertisedExtensions(false),
// Baseline advertisement (no timer queries / anisotropy yet); reconciled
// once the ES capabilities exist, see UpdateAdvertisedCapabilityExtensions.
.Extensions = BuildAdvertisedExtensions(false, false),
.IsCompatibilityProfile = false // Is Compatibility Profile
},
.StaticBackendCapability = {.AllowVSOnlyPrograms = false} // Backend Capability
@@ -627,8 +627,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// thread can only observe the extension string after the
// advertisement for its context has settled; rebuilding the whole
// list keeps the re-run after a context recreation idempotent.
void UpdateAdvertisedTimerQueryExtension() {
MutableRendererInfo().RendererGLInfo.Extensions = BuildAdvertisedExtensions(AreTimerQueriesSupported());
void UpdateAdvertisedCapabilityExtensions(Bool anisotropicFilteringSupported) {
MutableRendererInfo().RendererGLInfo.Extensions =
BuildAdvertisedExtensions(AreTimerQueriesSupported(), anisotropicFilteringSupported);
}
} // namespace
@@ -672,11 +673,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
return false;
}
DirectGLES::SetGLESCapabilities(m_GLESCapabilities);
// Now that g_GLESCapabilities knows about GL_EXT_disjoint_timer_query,
// reconcile the E_GL_ARB_timer_query advertisement (see the comment on
// UpdateAdvertisedTimerQueryExtension for why it cannot happen when
// the extension list is first built).
UpdateAdvertisedTimerQueryExtension();
// Now that g_GLESCapabilities knows about GL_EXT_disjoint_timer_query and
// GL_EXT_texture_filter_anisotropic, reconcile the advertisement (see the comment on
// UpdateAdvertisedCapabilityExtensions for why it cannot happen when the extension
// list is first built).
UpdateAdvertisedCapabilityExtensions(m_GLESCapabilities.SupportsTextureFilterAnisotropy);
UpdateDynamicBackendParameters();
PopulateFormatCapabilities(m_GLESFunctions, m_GLESCapabilities, MutableFormatCapabilities());
PrintFormatCapabilities(GetFormatCapabilities());
@@ -818,7 +819,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
return MutableRendererInfo();
}
Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported) {
Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported) {
Vector<GLExtension> extensions = {V_OpenGL30, V_OpenGL31, V_OpenGL32,
V_OpenGL33, E_GL_ARB_draw_buffers_blend, E_GL_ARB_compute_shader,
E_GL_ARB_shader_storage_buffer_object, E_GL_ARB_shader_image_load_store,
@@ -836,6 +837,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (timerQueriesSupported && !MG_Config::Features.DisableTimerQuery) {
extensions.push_back(E_GL_ARB_timer_query);
}
// Only advertised when the host ES driver actually filters anisotropically: the sampler
// state is accepted regardless, but forwarding it would be a no-op without the extension,
// and an app that trusts the string (LWJGL builds GLCapabilities from it) would silently
// get plain trilinear.
if (anisotropicFilteringSupported) {
extensions.push_back(E_GL_EXT_texture_filter_anisotropic);
extensions.push_back(E_GL_ARB_texture_filter_anisotropic);
}
return extensions;
}
@@ -940,6 +949,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
void BackendObject_DirectGLES::UpdateDynamicBackendParameters() {
m_dynamicParameters.UniformBufferOffsetAlignment = m_GLESCapabilities.UniformBufferOffsetAlignment;
m_dynamicParameters.MaxTextureMaxAnisotropy = m_GLESCapabilities.MaxTextureMaxAnisotropy;
m_dynamicParameters.AliasedLineWidthRangeMin = m_GLESCapabilities.AliasedLineWidthRangeMin;
m_dynamicParameters.AliasedLineWidthRangeMax = m_GLESCapabilities.AliasedLineWidthRangeMax;
m_dynamicParameters.SmoothLineWidthRangeMin = m_GLESCapabilities.SmoothLineWidthRangeMin;
@@ -66,9 +66,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
const RendererInfo& GetRendererIdentity();
// The full OpenGL extension list Espryt advertises (glGetString(GL_EXTENSIONS))
// for a device whose timer queries are (or are not) usable. The
// MOBILEGL_DISABLE_TIMERQUERY escape hatch is applied inside.
Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported);
// for a device whose timer queries / anisotropic filtering are (or are not) usable.
// The MOBILEGL_DISABLE_TIMERQUERY escape hatch is applied inside.
Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported);
// Format: <OpenGL ES Renderer>, OpenGL ES <Major>.<Minor> — the exact string an
// initialized backend returns from GetBackendAPIVersionString (and that ends up
@@ -488,14 +488,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
.TargetGLSLVersion = {4, 6, 0},
// Baseline advertisement (no shader subgroup, no timer queries); a
// live backend reconciles its copy in UpdateAdvertisedExtensions.
.Extensions = BuildAdvertisedExtensions(false, false),
.Extensions = BuildAdvertisedExtensions(false, false, false),
.IsCompatibilityProfile = false
},
.StaticBackendCapability = {.AllowVSOnlyPrograms = false}};
return rendererInfo;
}
Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported) {
Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported,
Bool anisotropicFilteringSupported) {
Vector<GLExtension> extensions = {V_OpenGL30, V_OpenGL31, V_OpenGL32,
V_OpenGL33, E_GL_ARB_draw_buffers_blend, E_GL_ARB_compute_shader,
E_GL_ARB_shader_storage_buffer_object, E_GL_ARB_shader_image_load_store,
@@ -516,6 +517,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (timerQueriesSupported && !MG_Config::Features.DisableTimerQuery) {
extensions.push_back(E_GL_ARB_timer_query);
}
// Only advertised when the samplerAnisotropy device feature was granted: without it the
// sampler state is accepted but never applied, and an app trusting the string (LWJGL builds
// GLCapabilities from it) would think it enabled anisotropic filtering.
if (anisotropicFilteringSupported) {
extensions.push_back(E_GL_EXT_texture_filter_anisotropic);
extensions.push_back(E_GL_ARB_texture_filter_anisotropic);
}
return extensions;
}
@@ -630,7 +638,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// run without a renderer; no timer query is advertised then. Rebuilding
// the whole list keeps re-runs idempotent.
m_rendererInfo.RendererGLInfo.Extensions = BuildAdvertisedExtensions(
m_vulkanCaps.SupportsShaderSubgroup, pVulkanRenderer && pVulkanRenderer->IsTimerQuerySupported());
m_vulkanCaps.SupportsShaderSubgroup, pVulkanRenderer && pVulkanRenderer->IsTimerQuerySupported(),
pVulkanRenderer && pVulkanRenderer->IsSamplerAnisotropySupported());
}
void BackendObject_DirectVulkan::UpdateDynamicBackendParameters() {
@@ -680,6 +689,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_dynamicParameters.UniformBufferOffsetAlignment = m_vulkanCaps.UniformBufferOffsetAlignment;
m_dynamicParameters.AliasedLineWidthRangeMin = m_vulkanCaps.AliasedLineWidthRangeMin;
m_dynamicParameters.AliasedLineWidthRangeMax = m_vulkanCaps.AliasedLineWidthRangeMax;
// Without the samplerAnisotropy feature the limit is unusable, so report 1.0 (no anisotropy)
// rather than a maximum the sampler manager will never apply.
m_dynamicParameters.MaxTextureMaxAnisotropy =
(pVulkanRenderer && pVulkanRenderer->IsSamplerAnisotropySupported()) ? m_vulkanCaps.MaxSamplerAnisotropy
: 1.0f;
m_dynamicParameters.SmoothLineWidthRangeMin = m_vulkanCaps.SmoothLineWidthRangeMin;
m_dynamicParameters.SmoothLineWidthRangeMax = m_vulkanCaps.SmoothLineWidthRangeMax;
m_dynamicParameters.SmoothLineWidthGranularity = m_vulkanCaps.SmoothLineWidthGranularity;
@@ -73,7 +73,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// a device with the given raw capabilities. The MOBILEGL_DISABLE_SUBGROUP and
// MOBILEGL_DISABLE_TIMERQUERY escape hatches are applied inside, so callers pass
// the detected device support (passing an already-gated value is harmless).
Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported);
Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported,
Bool anisotropicFilteringSupported);
// Format: <GPU Name>, Vulkan <Vulkan Version>, Driver <Driver Version> — the exact
// string an initialized backend returns from GetBackendAPIVersionString (and that
@@ -58,11 +58,24 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_device = initInfo.device;
m_config = initInfo.config;
m_samplerAnisotropySupported = initInfo.samplerAnisotropySupported;
m_maxSamplerAnisotropy = std::max(initInfo.maxSamplerAnisotropy, 1.0f);
MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE && m_config != nullptr,
"VkSamplerManager::Initialize failed: invalid initialization info");
return true;
}
Float VkSamplerManager::ResolveEffectiveMaxAnisotropy(const MG_State::GLState::SamplerObject& sampler) const {
if (!m_samplerAnisotropySupported) return 1.0f;
// VUID-VkSamplerCreateInfo-anisotropyEnable-01071/01072: anisotropy requires both filters to
// be LINEAR and the value to sit within [1, limits.maxSamplerAnisotropy].
if (sampler.GetMinFilter() != SamplerFilterMode::Linear ||
sampler.GetMagFilter() != SamplerFilterMode::Linear) {
return 1.0f;
}
return std::clamp(sampler.GetMaxAnisotropy(), 1.0f, m_maxSamplerAnisotropy);
}
void VkSamplerManager::Shutdown() {
for (auto& [_, sampler] : m_samplers) {
if (m_device != VK_NULL_HANDLE && sampler.handle != VK_NULL_HANDLE) {
@@ -99,9 +112,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
XXHASH_VERIFY(XXH64_update(m_hashState, &maxLod, sizeof(maxLod)));
const auto lodBias = sampler.GetLodBias();
XXHASH_VERIFY(XXH64_update(m_hashState, &lodBias, sizeof(lodBias)));
// Anisotropy is currently an accepted frontend-only state on DirectVulkan.
// Keep it out of the key so changing this no-op does not manufacture duplicate
// VkSamplers while sampler versioning still exposes the new frontend value.
// The RESOLVED value, not the GL request: samplers that only differ in an anisotropy Vulkan
// will not apply (NEAREST filtering, or requests past the device limit) must still share one
// VkSampler, while two samplers that really do differ must not collide onto the first one's.
const auto maxAnisotropy = ResolveEffectiveMaxAnisotropy(sampler);
XXHASH_VERIFY(XXH64_update(m_hashState, &maxAnisotropy, sizeof(maxAnisotropy)));
const auto compareMode = sampler.GetCompareMode();
XXHASH_VERIFY(XXH64_update(m_hashState, &compareMode, sizeof(compareMode)));
const auto compareFunc = ResolveCompareFunc(sampler, texture);
@@ -128,10 +143,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
samplerInfo.addressModeV = ToVkAddressMode(sampler.GetWrapT());
samplerInfo.addressModeW = ToVkAddressMode(sampler.GetWrapR());
samplerInfo.mipLodBias = sampler.GetLodBias();
// DirectVulkan does not yet plumb samplerAnisotropy feature/limit discovery;
// preserve the accepted frontend state without requesting an unsupported feature.
samplerInfo.anisotropyEnable = VK_FALSE;
samplerInfo.maxAnisotropy = 1.0f;
// Must use the same resolver as BuildSamplerKey - a divergence would either collide two
// different samplers or silently create duplicates.
const Float maxAnisotropy = ResolveEffectiveMaxAnisotropy(sampler);
samplerInfo.anisotropyEnable = maxAnisotropy > 1.0f ? VK_TRUE : VK_FALSE;
samplerInfo.maxAnisotropy = maxAnisotropy;
samplerInfo.compareEnable = sampler.GetCompareMode() == SamplerCompareMode::CompareToTexture ? VK_TRUE : VK_FALSE;
samplerInfo.compareOp = ToVkCompareOp(ResolveCompareFunc(sampler, texture));
samplerInfo.maxLod = ResolveEffectiveMaxLod(sampler);
@@ -24,6 +24,10 @@ public:
struct InitInfo {
VkDevice device = VK_NULL_HANDLE;
const VulkanRendererConfig* config = nullptr;
// The samplerAnisotropy device feature was requested and granted at vkCreateDevice.
Bool samplerAnisotropySupported = false;
// VkPhysicalDeviceLimits::maxSamplerAnisotropy.
Float maxSamplerAnisotropy = 1.0f;
};
Bool Initialize(const InitInfo& initInfo);
@@ -49,9 +53,16 @@ private:
const MG_State::GLState::ITextureObject& texture);
static VkBorderColor ResolveVkBorderColor(const MG_State::GLState::SamplerObject& sampler,
const MG_State::GLState::ITextureObject& texture);
// The anisotropy Vulkan will actually apply: 1.0 (i.e. disabled) unless the feature is on and
// the sampler filters linearly both ways, otherwise the GL request clamped to the device limit.
// GL happily carries GL_TEXTURE_MAX_ANISOTROPY on a NEAREST sampler (Blaze3D's blocks do exactly
// that) while Vulkan forbids anisotropyEnable there, so the GL value must never be forwarded raw.
Float ResolveEffectiveMaxAnisotropy(const MG_State::GLState::SamplerObject& sampler) const;
VkDevice m_device = VK_NULL_HANDLE;
const VulkanRendererConfig* m_config = nullptr;
Bool m_samplerAnisotropySupported = false;
Float m_maxSamplerAnisotropy = 1.0f;
UnorderedMap<Uint64, SamplerCacheEntry> m_samplers;
static inline XXH64_state_t* m_hashState = XXH64_createState();
};
@@ -1890,7 +1890,8 @@ void main() {
m_samplerManager = MakeUnique<VkSamplerManager>();
MOBILEGL_ASSERT(m_samplerManager != nullptr, "VkSamplerManager creation failed.");
succeeded = m_samplerManager->Initialize({m_device, &m_config});
succeeded = m_samplerManager->Initialize({m_device, &m_config, m_samplerAnisotropyFeatureEnabled,
m_physicalDevice.properties.limits.maxSamplerAnisotropy});
MOBILEGL_ASSERT(succeeded, "VkSamplerManager initialization failed.");
succeeded = InitializeBlitResources();
MOBILEGL_ASSERT(succeeded, "Blit pipeline resource initialization failed.");
@@ -6508,6 +6509,10 @@ void main() {
deviceFeatures.multiDrawIndirect = supportedDeviceFeatures.multiDrawIndirect;
m_multiDrawIndirectFeatureEnabled = deviceFeatures.multiDrawIndirect == VK_TRUE;
m_logicOpFeatureEnabled = deviceFeatures.logicOp == VK_TRUE;
// Backs GL_TEXTURE_MAX_ANISOTROPY_EXT; optional in Vulkan, so the sampler manager falls back
// to isotropic filtering (and the extension goes unadvertised) when the device lacks it.
deviceFeatures.samplerAnisotropy = supportedDeviceFeatures.samplerAnisotropy;
m_samplerAnisotropyFeatureEnabled = deviceFeatures.samplerAnisotropy == VK_TRUE;
VkDeviceCreateInfo deviceCreateInfo{};
deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
@@ -227,6 +227,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// frontend. Timestamp support (queue timestampValidBits > 0 and a
// non-zero timestampPeriod) is cached at device creation.
Bool IsTimerQuerySupported() const;
// The samplerAnisotropy device feature was granted, so GL_TEXTURE_MAX_ANISOTROPY_EXT is
// honored rather than accepted-and-ignored.
Bool IsSamplerAnisotropySupported() const { return m_samplerAnisotropyFeatureEnabled; }
// Ensures the frame command buffer is recording (same lazy pattern as
// SetupDraw) and writes a bottom-of-pipe timestamp into the current
// frame's pool. Null when unsupported or the pool is exhausted.
@@ -359,6 +362,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Bool m_indexTypeUint8ExtensionEnabled = false;
Bool m_logicOpFeatureEnabled = false;
Bool m_multiDrawIndirectFeatureEnabled = false;
Bool m_samplerAnisotropyFeatureEnabled = false;
Bool m_shaderDrawParametersExtensionEnabled = false;
Bool m_shaderDrawParametersFeatureEnabled = false;
// fillModeNonSolid gates VK_POLYGON_MODE_LINE/_POINT (glPolygonMode); independentBlend gates
@@ -529,6 +529,13 @@ namespace MobileGL::MG_Impl::GLImpl {
params[1] = dynamicParameters.AliasedLineWidthRangeMax;
return;
}
case GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT: {
// EXT_texture_filter_anisotropic queries this as a float; the integer path below widens
// from here, so this case is the authoritative one.
const auto& dynamicParameters = MG_Backend::pActiveBackendObject->GetDynamicParameters();
params[0] = dynamicParameters.MaxTextureMaxAnisotropy;
return;
}
case GL_ALIASED_POINT_SIZE_RANGE:
case GL_POINT_SIZE_RANGE: {
const auto& dynamicParameters = MG_Backend::pActiveBackendObject->GetDynamicParameters();
@@ -1955,6 +1962,10 @@ namespace MobileGL::MG_Impl::GLImpl {
case GL_MAX_SAMPLES:
*params = std::max(dynamicParameters.MaxSamples, kFrontendMaxSamples);
break;
case GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT:
// Float state (see GetFloatv); rounded to nearest for the integer query per GL 3.3 6.1.2.
*params = static_cast<GLint>(std::lround(dynamicParameters.MaxTextureMaxAnisotropy));
break;
default:
MGLOG_E("glGetIntegerv: Invalid enum %s (0x%X)", MG_Util::ConvertGLEnumToString(pname).c_str(), pname);
MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum,
@@ -12,6 +12,10 @@
#include <string>
#include <vector>
#include <algorithm>
#include <MG_Backend/DirectGLES/BackendObject_DirectGLES.h>
#include <MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h>
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
// ProbeIndirectInstanceIdIncludesBaseInstance is driven against a fake GLES driver:
@@ -31,6 +35,11 @@ namespace {
GLenum pendingError = GL_NO_ERROR;
std::vector<std::string> extensions;
// GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT the fake reports, and whether it was ever asked:
// querying it on a driver without the extension would raise GL_INVALID_ENUM.
GLfloat maxTextureMaxAnisotropy = 16.0f;
bool maxTextureMaxAnisotropyQueried = false;
GLuint nextBufferId = 1;
GLuint nextShaderId = 1;
GLuint nextProgramId = 1;
@@ -122,6 +131,10 @@ namespace {
};
funcs.glGetFloatv = [](GLenum pname, GLfloat* data) {
switch (pname) {
case GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT:
g_fake.maxTextureMaxAnisotropyQueried = true;
data[0] = g_fake.maxTextureMaxAnisotropy;
break;
// Two-component range queries.
case GL_ALIASED_LINE_WIDTH_RANGE:
case GL_SMOOTH_LINE_WIDTH_RANGE:
@@ -404,6 +417,51 @@ TEST(IndirectInstanceIdProbe, FillInCapabilitiesWiresProbeResult) {
ExpectProbeReleasedAllObjects();
}
// The extension string is what apps gate on (LWJGL builds GLCapabilities from it), so advertising
// it on a driver that cannot filter anisotropically would leave them silently on trilinear.
TEST(TextureAnisotropyCapabilities, ExtensionIsAdvertisedOnlyWhenTheHostDriverSupportsIt) {
const auto contains = [](const MobileGL::Vector<MobileGL::GLExtension>& extensions,
MobileGL::GLExtension wanted) {
return std::find(extensions.begin(), extensions.end(), wanted) != extensions.end();
};
const auto without = MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false);
EXPECT_FALSE(contains(without, MobileGL::E_GL_EXT_texture_filter_anisotropic));
EXPECT_FALSE(contains(without, MobileGL::E_GL_ARB_texture_filter_anisotropic));
const auto with = MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, true);
EXPECT_TRUE(contains(with, MobileGL::E_GL_EXT_texture_filter_anisotropic));
EXPECT_TRUE(contains(with, MobileGL::E_GL_ARB_texture_filter_anisotropic));
// Same rule on the Vulkan backend, where the gate is the samplerAnisotropy device feature.
const auto vkWithout = MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false);
EXPECT_FALSE(contains(vkWithout, MobileGL::E_GL_EXT_texture_filter_anisotropic));
const auto vkWith = MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, true);
EXPECT_TRUE(contains(vkWith, MobileGL::E_GL_EXT_texture_filter_anisotropic));
EXPECT_TRUE(contains(vkWith, MobileGL::E_GL_ARB_texture_filter_anisotropic));
}
TEST(TextureAnisotropyCapabilities, MaxAnisotropyIsQueriedOnlyWhenTheExtensionIsPresent) {
ResetFakeDriver();
g_fake.maxVertexSsboBlocks = 0;
const auto funcs = MakeFakeGLESFunctions();
MobileGL::MG_External::GLESCapabilities absentCaps;
ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(absentCaps, funcs));
// Never probed (it would be GL_INVALID_ENUM), and reported as "no anisotropy".
EXPECT_FALSE(g_fake.maxTextureMaxAnisotropyQueried);
EXPECT_FLOAT_EQ(absentCaps.MaxTextureMaxAnisotropy, 1.0f);
ResetFakeDriver();
g_fake.maxVertexSsboBlocks = 0;
g_fake.maxTextureMaxAnisotropy = 16.0f;
g_fake.extensions.emplace_back("GL_EXT_texture_filter_anisotropic");
MobileGL::MG_External::GLESCapabilities presentCaps;
ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(presentCaps, funcs));
EXPECT_TRUE(g_fake.maxTextureMaxAnisotropyQueried);
EXPECT_FLOAT_EQ(presentCaps.MaxTextureMaxAnisotropy, 16.0f);
}
TEST(TextureAnisotropyCapabilities, ExtensionPresenceIsDetectedExactly) {
ResetFakeDriver();
g_fake.maxVertexSsboBlocks = 0;
+28
View File
@@ -122,6 +122,10 @@ namespace {
return table;
}
const MobileGL::MG_Backend::DynamicBackendParameters& GetDynamicParameters() const override {
return MutableDynamicParameters();
}
// Lets a test stand in a backend limit (e.g. GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT).
static MobileGL::MG_Backend::DynamicBackendParameters& MutableDynamicParameters() {
static MobileGL::MG_Backend::DynamicBackendParameters params = {};
return params;
}
@@ -162,6 +166,30 @@ TEST_F(TextureTest, CreateTexturesCreatesObjectsWithoutBinding) {
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
}
// GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT is float state that must answer every numeric query: GetFloatv
// is authoritative and GetIntegerv would otherwise fall through to its INVALID_ENUM default.
TEST_F(TextureTest, MaxTextureMaxAnisotropyIsAnsweredFromTheBackendLimit) {
auto backend = MakeUnique<FormatCapabilityBackend>();
FormatCapabilityBackend::MutableDynamicParameters().MaxTextureMaxAnisotropy = 16.0f;
ScopedBackendOverride override(Move(backend));
GLfloat floatValue = 0.0f;
MG_Impl::GLImpl::GetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &floatValue);
EXPECT_FLOAT_EQ(floatValue, 16.0f);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
GLint integerValue = 0;
MG_Impl::GLImpl::GetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &integerValue);
EXPECT_EQ(integerValue, 16);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
// A backend without anisotropy reports the no-anisotropy floor rather than erroring.
FormatCapabilityBackend::MutableDynamicParameters().MaxTextureMaxAnisotropy = 1.0f;
MG_Impl::GLImpl::GetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &floatValue);
EXPECT_FLOAT_EQ(floatValue, 1.0f);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
}
TEST_F(TextureTest, TextureMaxAnisotropyDefaultsToOneAndRoundTripsWithoutRedundantVersionBumps) {
GLuint texture = 0;
MG_Impl::GLImpl::GenTextures(1, &texture);
@@ -911,6 +911,13 @@ namespace MobileGL::MG_Util::BackendLoader {
glesFuncs.glGetIntegerv(GL_MAX_VIEWPORTS, &maxViewports);
glesFuncs.glGetIntegerv(GL_MAX_VIEWPORT_DIMS, maxViewportDims);
glesFuncs.glGetIntegerv(GL_VIEWPORT_SUBPIXEL_BITS, &viewportSubpixelBits);
// Only legal to query once the extension has been seen in the loop above, hence not batched
// with the unconditional probes: on a driver without it this raises GL_INVALID_ENUM.
if (caps.SupportsTextureFilterAnisotropy) {
GLfloat maxTextureMaxAnisotropy = 1.0f;
glesFuncs.glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxTextureMaxAnisotropy);
caps.MaxTextureMaxAnisotropy = std::max(maxTextureMaxAnisotropy, 1.0f);
}
caps.AliasedLineWidthRangeMin = aliasedLineWidthRange[0];
caps.AliasedLineWidthRangeMax = aliasedLineWidthRange[1];
caps.SmoothLineWidthRangeMin = smoothLineWidthRange[0];
@@ -1034,6 +1034,9 @@ namespace MobileGL {
// GL_EXT_texture_filter_anisotropic is present, so sampler/texture
// anisotropy may be forwarded without raising GL_INVALID_ENUM in GLES.
Bool SupportsTextureFilterAnisotropy = false;
// GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT of the host driver; only queried when the
// extension above is present, and left at 1.0 (no anisotropy) otherwise.
Float MaxTextureMaxAnisotropy = 1.0f;
Bool SupportsBaseInstance = false;
// GL_EXT_disjoint_timer_query is present in the extension string.
Bool SupportsDisjointTimerQuery = false;
@@ -124,6 +124,7 @@ namespace MobileGL::MG_Util::BackendLoader {
caps.UniformBufferOffsetAlignment = static_cast<int>(p.limits.minUniformBufferOffsetAlignment);
caps.AliasedLineWidthRangeMin = p.limits.lineWidthRange[0];
caps.AliasedLineWidthRangeMax = p.limits.lineWidthRange[1];
caps.MaxSamplerAnisotropy = p.limits.maxSamplerAnisotropy;
caps.SmoothLineWidthRangeMin = p.limits.lineWidthRange[0];
caps.SmoothLineWidthRangeMax = p.limits.lineWidthRange[1];
caps.SmoothLineWidthGranularity = p.limits.lineWidthGranularity;
@@ -208,6 +209,7 @@ namespace MobileGL::MG_Util::BackendLoader {
caps.UniformBufferOffsetAlignment = static_cast<int>(properties.limits.minUniformBufferOffsetAlignment);
caps.AliasedLineWidthRangeMin = properties.limits.lineWidthRange[0];
caps.AliasedLineWidthRangeMax = properties.limits.lineWidthRange[1];
caps.MaxSamplerAnisotropy = properties.limits.maxSamplerAnisotropy;
caps.SmoothLineWidthRangeMin = properties.limits.lineWidthRange[0];
caps.SmoothLineWidthRangeMax = properties.limits.lineWidthRange[1];
caps.SmoothLineWidthGranularity = properties.limits.lineWidthGranularity;
@@ -18,6 +18,9 @@ namespace MobileGL {
Int UniformBufferOffsetAlignment = 256;
Float AliasedLineWidthRangeMin = 1.0f;
Float AliasedLineWidthRangeMax = 1.0f;
// VkPhysicalDeviceLimits::maxSamplerAnisotropy. Whether it can be used at all depends on
// the samplerAnisotropy feature, which the renderer decides at device creation.
Float MaxSamplerAnisotropy = 1.0f;
Float SmoothLineWidthRangeMin = 1.0f;
Float SmoothLineWidthRangeMax = 1.0f;
Float SmoothLineWidthGranularity = 1.0f;
+5 -3
View File
@@ -548,8 +548,8 @@ namespace MobileGL::MG_Util::SelfTest {
if (summary.capsValid) {
backendApiVersionString = MG_Backend::DirectGLES::FormatBackendAPIVersionString(
summary.caps.GLESRendererString, summary.caps.GLESVersion.Major, summary.caps.GLESVersion.Minor);
advertisedExtensions = JoinAdvertisedExtensions(
MG_Backend::DirectGLES::BuildAdvertisedExtensions(summary.caps.SupportsDisjointTimerQuery));
advertisedExtensions = JoinAdvertisedExtensions(MG_Backend::DirectGLES::BuildAdvertisedExtensions(
summary.caps.SupportsDisjointTimerQuery, summary.caps.SupportsTextureFilterAnisotropy));
}
AppendMobileGLReportedRows(builder, MG_Backend::DirectGLES::GetRendererIdentity(), backendApiVersionString,
advertisedExtensions);
@@ -841,6 +841,7 @@ namespace MobileGL::MG_Util::SelfTest {
String driverVersionString; // raw hex, vendor-encoded (see RunVulkanDriverPost)
Bool shaderSubgroupUsable = false;
Bool timerQueriesSupported = false;
Bool samplerAnisotropySupported = false;
};
} // namespace
@@ -1107,6 +1108,7 @@ namespace MobileGL::MG_Util::SelfTest {
VkPhysicalDeviceFeatures features{};
vkGetPhysicalDeviceFeaturesFn(physicalDevice, &features);
summary.samplerAnisotropySupported = features.samplerAnisotropy == VK_TRUE;
if (features.multiDrawIndirect == VK_TRUE) {
builder.Pass("multiDrawIndirect", "indirect multi-draw batches run as single native commands");
} else {
@@ -1279,7 +1281,7 @@ namespace MobileGL::MG_Util::SelfTest {
backendApiVersionString = MG_Backend::DirectVulkan::FormatBackendAPIVersionString(
summary.deviceName, summary.apiVersionString, summary.driverVersionString);
advertisedExtensions = JoinAdvertisedExtensions(MG_Backend::DirectVulkan::BuildAdvertisedExtensions(
summary.shaderSubgroupUsable, summary.timerQueriesSupported));
summary.shaderSubgroupUsable, summary.timerQueriesSupported, summary.samplerAnisotropySupported));
}
AppendMobileGLReportedRows(builder, MG_Backend::DirectVulkan::GetRendererIdentity(), backendApiVersionString,
advertisedExtensions);