diff --git a/MobileGL/MG_Backend/BackendObject.h b/MobileGL/MG_Backend/BackendObject.h index 5032c5dc..8432f312 100644 --- a/MobileGL/MG_Backend/BackendObject.h +++ b/MobileGL/MG_Backend/BackendObject.h @@ -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; diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index 0c5b62be..1ebbfdf3 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -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 BuildAdvertisedExtensions(Bool timerQueriesSupported) { + Vector BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported) { Vector 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; diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.h b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.h index b13f2344..00bddb9b 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.h +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.h @@ -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 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 BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported); // Format: , OpenGL ES . — the exact string an // initialized backend returns from GetBackendAPIVersionString (and that ends up diff --git a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp index 4f9f4788..2a4b416f 100644 --- a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp @@ -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 BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported) { + Vector BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported, + Bool anisotropicFilteringSupported) { Vector 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; diff --git a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h index f3a19816..8f81f6bc 100644 --- a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h +++ b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h @@ -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 BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported); + Vector BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported, + Bool anisotropicFilteringSupported); // Format: , Vulkan , Driver — the exact // string an initialized backend returns from GetBackendAPIVersionString (and that diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.cpp index 2d837a92..9b80a961 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.cpp @@ -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); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.h index edd62f3c..001b11a5 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.h @@ -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 m_samplers; static inline XXH64_state_t* m_hashState = XXH64_createState(); }; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 84af9d4d..851ac679 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -1890,7 +1890,8 @@ void main() { m_samplerManager = MakeUnique(); 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; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index b64de3b2..b5c7a57e 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -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 diff --git a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp index 816f6135..1bf4089f 100644 --- a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp +++ b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp @@ -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(); @@ -1959,6 +1966,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(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, diff --git a/MobileGL/MG_Test/BackendLoader/BackendLoaderTest.cpp b/MobileGL/MG_Test/BackendLoader/BackendLoaderTest.cpp index 8ee37032..87354376 100644 --- a/MobileGL/MG_Test/BackendLoader/BackendLoaderTest.cpp +++ b/MobileGL/MG_Test/BackendLoader/BackendLoaderTest.cpp @@ -12,6 +12,10 @@ #include #include +#include + +#include +#include #include // ProbeIndirectInstanceIdIncludesBaseInstance is driven against a fake GLES driver: @@ -31,6 +35,11 @@ namespace { GLenum pendingError = GL_NO_ERROR; std::vector 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& 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; diff --git a/MobileGL/MG_Test/Program/ProgramUtilTest.cpp b/MobileGL/MG_Test/Program/ProgramUtilTest.cpp index 1b272dd3..afbf2586 100644 --- a/MobileGL/MG_Test/Program/ProgramUtilTest.cpp +++ b/MobileGL/MG_Test/Program/ProgramUtilTest.cpp @@ -556,6 +556,81 @@ TEST_F(ProgramUtilTest, CompileSimpleVertexShader) { } } +// Legacy desktop sources are normalized to "#version 330 core", which is stricter than the 460 they +// used to be forced to. A shader declaring 330 while using 420-era syntax without the matching +// #extension line is accepted by real drivers, so CompileShader retries it at 460 instead of failing. +TEST_F(ProgramUtilTest, CompileShaderRetriesAt460WhenLegacyVersionRejects420Syntax) { + using namespace MG_Util::ShaderTranspiler; + String source = R"(#version 330 +layout(binding = 0) uniform sampler2D InSampler; +in vec2 texCoord; +out vec4 fragColor; +void main() { + fragColor = texture(InSampler, texCoord); +})"; + PreprocessShaderSource(ShaderStage::Fragment, source); + // The normal path still emits 330 - the retry must not become the default. + ASSERT_EQ(source.find("#version 330 core"), 0u); + + ShaderAttrib attrib{.shaderType = GL_FRAGMENT_SHADER, .sourceStr = source}; + auto res = ShaderCompiler::CompileShader(attrib); + if (!res) { + FAIL() << "errc: " << res.error().errc << "\nlog: " << res.error().log; + } + + // Same source compiled for the OpenGL environment must take the retry too. + ShaderAttrib glAttrib{ + .shaderType = GL_FRAGMENT_SHADER, .sourceStr = source, .flags = ShaderCompileBits::CompileForOpenGL}; + auto glRes = ShaderCompiler::CompileShader(glAttrib); + if (!glRes) { + FAIL() << "errc: " << glRes.error().errc << "\nlog: " << glRes.error().log; + } +} + +TEST_F(ProgramUtilTest, CompileShaderStillFailsWithOriginalDiagnosticsWhenRetryCannotHelp) { + using namespace MG_Util::ShaderTranspiler; + String source = R"(#version 330 +in vec2 texCoord; +out vec4 fragColor; +void main() { + fragColor = thisFunctionDoesNotExist(texCoord); +})"; + PreprocessShaderSource(ShaderStage::Fragment, source); + + ShaderAttrib attrib{.shaderType = GL_FRAGMENT_SHADER, .sourceStr = source}; + auto res = ShaderCompiler::CompileShader(attrib); + ASSERT_FALSE(res); + EXPECT_EQ(res.error().errc, -2); + EXPECT_NE(res.error().log.find("thisFunctionDoesNotExist"), String::npos) << res.error().log; +} + +TEST_F(ProgramUtilTest, RetargetLegacyVersionDirectiveOnlyTouchesNormalizedDesktopCore) { + using namespace MG_Util::ShaderTranspiler; + + String normalized = "#version 330 core\nvoid main() {}\n"; + EXPECT_TRUE(RetargetLegacyVersionDirectiveTo460(normalized)); + EXPECT_EQ(normalized.find("#version 460 core"), 0u); + + // Already modern: nothing to retarget. + String modern = "#version 460 core\nvoid main() {}\n"; + EXPECT_FALSE(RetargetLegacyVersionDirectiveTo460(modern)); + EXPECT_EQ(modern.find("#version 460 core"), 0u); + + // ES and compatibility sources keep what they declared. + String es = "#version 300 es\nvoid main() {}\n"; + EXPECT_FALSE(RetargetLegacyVersionDirectiveTo460(es)); + EXPECT_EQ(es.find("#version 300 es"), 0u); + + String compat = "#version 330 compatibility\nvoid main() {}\n"; + EXPECT_FALSE(RetargetLegacyVersionDirectiveTo460(compat)); + EXPECT_EQ(compat.find("#version 330 compatibility"), 0u); + + // A commented-out directive is not the real one. + String commented = "// #version 330 core\nvoid main() {}\n"; + EXPECT_FALSE(RetargetLegacyVersionDirectiveTo460(commented)); + EXPECT_EQ(commented.find("#version 460"), String::npos); +} + const char* fs = R"(#version 150 uniform sampler2D InSampler; diff --git a/MobileGL/MG_Test/Texture/TextureTest.cpp b/MobileGL/MG_Test/Texture/TextureTest.cpp index d19d098b..388a7fef 100644 --- a/MobileGL/MG_Test/Texture/TextureTest.cpp +++ b/MobileGL/MG_Test/Texture/TextureTest.cpp @@ -125,6 +125,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; } @@ -165,6 +169,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::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); diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp index 9b9aa97c..87caa900 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp @@ -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]; diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h index f07f4b00..c87066e3 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h @@ -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; diff --git a/MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.cpp b/MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.cpp index 1323087c..3105abb6 100644 --- a/MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.cpp +++ b/MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.cpp @@ -124,6 +124,7 @@ namespace MobileGL::MG_Util::BackendLoader { caps.UniformBufferOffsetAlignment = static_cast(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(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; diff --git a/MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.h b/MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.h index 9819ea33..887d8541 100644 --- a/MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.h +++ b/MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.h @@ -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; diff --git a/MobileGL/MG_Util/SelfTest/DriverPost.cpp b/MobileGL/MG_Util/SelfTest/DriverPost.cpp index 82d93ee3..ddba5082 100644 --- a/MobileGL/MG_Util/SelfTest/DriverPost.cpp +++ b/MobileGL/MG_Util/SelfTest/DriverPost.cpp @@ -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); diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp index 4833d1d0..bd60797b 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp @@ -18,6 +18,7 @@ #include "spirv-tools/libspirv.h" #include "spirv-tools/optimizer.hpp" +#include "ShaderSourceProcessor.h" #include #include @@ -133,27 +134,23 @@ namespace MobileGL { return Resources; } - Result> ShaderCompiler::CompileShader(const ShaderAttrib& attrib) { - auto shaderType = attrib.shaderType; - auto& sourceStr = attrib.sourceStr; - - auto lang = MG_Util::ConvertGLEnumToEShLanguage(shaderType); - if (lang == EShLanguage::EShLangCount) { - ResultInfo r; - r.log += "Error: [Preprocess] Unsupported shader type: " + ConvertGLEnumToString(shaderType); - r.errc = -1; - return std::unexpected(r); - } - + // One parse attempt. A glslang::TShader cannot be re-parsed, so a retry has to build a + // fresh one with byte-identical setup - hence a single factored body rather than two + // copies that could drift apart. + static Result> ParseShaderSource(EShLanguage lang, GLenum shaderType, + const String& source, + Flags flags) { SharedPtr res; auto& tshader = res; tshader = MakeShared(lang); - const char* src[] = {sourceStr.data()}; + // setStrings gets no length array, so it relies on NUL termination: source must be an + // owning buffer that outlives parse(), never a StringView's substring. + const char* src[] = {source.c_str()}; tshader->setStrings(src, 1); tshader->setNanMinMaxClamp(true); tshader->setInvertY(true); tshader->setPreamble("#undef VULKAN\n"); - if (attrib.flags & ShaderCompileBits::CompileForOpenGL) { + if (flags & ShaderCompileBits::CompileForOpenGL) { tshader->setEnvInput(glslang::EShSourceGlsl, lang, glslang::EShClientVulkan, 450); tshader->setEnvClient(glslang::EShClientOpenGL, glslang::EShTargetOpenGL_450); tshader->setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_3); @@ -182,6 +179,39 @@ namespace MobileGL { return res; } + Result> ShaderCompiler::CompileShader(const ShaderAttrib& attrib) { + auto shaderType = attrib.shaderType; + + auto lang = MG_Util::ConvertGLEnumToEShLanguage(shaderType); + if (lang == EShLanguage::EShLangCount) { + ResultInfo r; + r.log += "Error: [Preprocess] Unsupported shader type: " + ConvertGLEnumToString(shaderType); + r.errc = -1; + return std::unexpected(r); + } + + const String source(attrib.sourceStr); + auto result = ParseShaderSource(lang, shaderType, source, attrib.flags); + if (result) return result; + + // Legacy desktop sources are normalized to "#version 330 core", which parses under + // stricter rules than the 460 they used to be forced to: a shader declaring 330 while + // using e.g. layout(binding=...) without the matching #extension line compiles on real + // drivers but is rejected here. Retry once at 460 before reporting failure; a genuinely + // broken shader fails both attempts and keeps its original diagnostics. + String retrySource = source; + if (!MG_Util::ShaderTranspiler::RetargetLegacyVersionDirectiveTo460(retrySource)) { + return result; + } + + auto retryResult = ParseShaderSource(lang, shaderType, retrySource, attrib.flags); + if (!retryResult) return result; + + MGLOG_D("CompileShader: %s only parsed after retargeting its legacy #version to 460", + ConvertGLEnumToString(shaderType).c_str()); + return retryResult; + } + Result> ShaderCompiler::LinkProgram(const ProgramAttrib& attrib) { SharedPtr program = MakeShared(); for (auto& s : attrib.shaders) { diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp b/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp index 5466f24e..87a82d69 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp @@ -633,6 +633,21 @@ namespace MobileGL { InjectDepthRangeBuiltinShim(stage, source); } + Bool RetargetLegacyVersionDirectiveTo460(String& source) { + // Re-inspect rather than searching for the literal directive: it is not necessarily at + // offset 0 (a BOM or comments may precede it) and a commented-out "#version" elsewhere + // must not be mistaken for the real one. + const ShaderLanguageInfo info = InspectShaderLanguage(source); + if (!info.HasVersionDirective()) return false; + // Only the set NormalizeVersionDirective downgraded: desktop core below 400. ES and + // compatibility shaders keep whatever they declared. + if (info.profile != ShaderProfile::Core || info.version >= 400) return false; + + source.replace(info.versionDirectiveStart, info.versionDirectiveEnd - info.versionDirectiveStart, + "#version 460 core\n"); + return true; + } + } // namespace ShaderTranspiler } // namespace MG_Util } // namespace MobileGL diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.h b/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.h index f9d1c510..b86cf153 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.h +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.h @@ -20,6 +20,14 @@ namespace MobileGL { namespace MG_Util { namespace ShaderTranspiler { void PreprocessShaderSource(ShaderStage stage, String& source); + + // Rewrites a "#version 330 core" directive that PreprocessShaderSource normalized down + // from a legacy desktop version back up to "#version 460 core". Returns false (leaving + // the source untouched) for anything else: ES, compatibility, or an already-modern + // declaration. Exists so a shader that only parses under the laxer 460 rules - e.g. it + // uses 420-era syntax without the matching #extension line, which real drivers tend to + // accept - can be retried instead of failing to compile. + Bool RetargetLegacyVersionDirectiveTo460(String& source); } // namespace ShaderTranspiler } // namespace MG_Util } // namespace MobileGL \ No newline at end of file