mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-10 21:28:32 +09:00
[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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user