From 2f312262ea5244439dbb2fc0acf483cd749bc2af Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Mon, 12 Oct 2026 08:01:52 -0400 Subject: [PATCH] [Fix] (GL): keep TEXTURE_BASE_LEVEL/MAX_LEVEL as set on immutable textures and clamp only the effective range --- .../MG_Impl/GLImpl/Texture/GL_Texture.cpp | 12 +++--- .../GLState/TextureState/TextureObject.cpp | 41 +++++++++++-------- .../GLState/TextureState/TextureObject.h | 11 +++++ MobileGL/MG_Test/Texture/TextureTest.cpp | 40 ++++++++++++++++++ 4 files changed, 81 insertions(+), 23 deletions(-) diff --git a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp index dd14888b..f50f5393 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp +++ b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp @@ -1536,10 +1536,10 @@ namespace MobileGL::MG_Impl::GLImpl { *params = static_cast(textureObject->GetSamplerObject()->GetMaxLod()); break; case GL_TEXTURE_BASE_LEVEL: - *params = static_cast(textureObject->GetLevelRange().x()); + *params = static_cast(textureObject->GetSpecifiedLevelRange().x()); break; case GL_TEXTURE_MAX_LEVEL: - *params = static_cast(textureObject->GetLevelRange().y()); + *params = static_cast(textureObject->GetSpecifiedLevelRange().y()); break; case GL_TEXTURE_IMMUTABLE_FORMAT: *params = textureObject->IsImmutable() ? GL_TRUE : GL_FALSE; @@ -3211,12 +3211,12 @@ namespace MobileGL::MG_Impl::GLImpl { break; case GL_TEXTURE_BASE_LEVEL: if (params) { - *params = static_cast(textureObject->GetLevelRange().x()); + *params = static_cast(textureObject->GetSpecifiedLevelRange().x()); } break; case GL_TEXTURE_MAX_LEVEL: if (params) { - *params = static_cast(textureObject->GetLevelRange().y()); + *params = static_cast(textureObject->GetSpecifiedLevelRange().y()); } break; case GL_TEXTURE_IMMUTABLE_FORMAT: @@ -3398,12 +3398,12 @@ namespace MobileGL::MG_Impl::GLImpl { break; case GL_TEXTURE_BASE_LEVEL: if (params) { - *params = static_cast(textureObject->GetLevelRange().x()); + *params = static_cast(textureObject->GetSpecifiedLevelRange().x()); } break; case GL_TEXTURE_MAX_LEVEL: if (params) { - *params = static_cast(textureObject->GetLevelRange().y()); + *params = static_cast(textureObject->GetSpecifiedLevelRange().y()); } break; case GL_TEXTURE_IMMUTABLE_FORMAT: diff --git a/MobileGL/MG_State/GLState/TextureState/TextureObject.cpp b/MobileGL/MG_State/GLState/TextureState/TextureObject.cpp index 707f24c2..edd1486e 100644 --- a/MobileGL/MG_State/GLState/TextureState/TextureObject.cpp +++ b/MobileGL/MG_State/GLState/TextureState/TextureObject.cpp @@ -344,16 +344,29 @@ namespace MobileGL { return m_levelRange; } - void TextureObjectBase::SetBaseLevel(Uint baseLevel) { - if (IsImmutable() && m_immutableLevels > 0) { - baseLevel = std::min(baseLevel, m_immutableLevels - 1); - } - if (baseLevel == m_levelRange.x()) return; + const UintVec2& TextureObjectBase::GetSpecifiedLevelRange() const { + return m_specifiedLevelRange; + } - m_levelRange.x() = baseLevel; - if (IsImmutable() && m_levelRange.y() < m_levelRange.x()) { - m_levelRange.y() = m_levelRange.x(); + // GL 4.6 core 8.17: an immutable texture's level_base is clamped to [0, levels - 1] + // and its level_max to [level_base, levels - 1] - when they are USED. The parameters + // themselves keep what was set (glGetTexParameter(GL_TEXTURE_MAX_LEVEL) still reads + // 1000 after a two-level glTexStorage), so the clamp lives in this derived copy. + Bool TextureObjectBase::UpdateEffectiveLevelRange() { + UintVec2 effective = m_specifiedLevelRange; + if (IsImmutable()) { + effective.x() = std::min(effective.x(), m_immutableLevels - 1); + effective.y() = std::min(std::max(effective.y(), effective.x()), m_immutableLevels - 1); } + if (effective == m_levelRange) return false; + m_levelRange = effective; + return true; + } + + void TextureObjectBase::SetBaseLevel(Uint baseLevel) { + m_specifiedLevelRange.x() = baseLevel; + if (!UpdateEffectiveLevelRange()) return; + ++m_textureParamsVersion; MGP_NOTE_AGGREGATE(TextureParams); BumpShapeVersion(); @@ -364,12 +377,9 @@ namespace MobileGL { } void TextureObjectBase::SetMaxLevel(Uint maxLevel) { - if (IsImmutable() && m_immutableLevels > 0) { - maxLevel = std::min(std::max(maxLevel, m_levelRange.x()), m_immutableLevels - 1); - } - if (maxLevel == m_levelRange.y()) return; + m_specifiedLevelRange.y() = maxLevel; + if (!UpdateEffectiveLevelRange()) return; - m_levelRange.y() = maxLevel; ++m_textureParamsVersion; MGP_NOTE_AGGREGATE(TextureParams); BumpShapeVersion(); @@ -388,10 +398,7 @@ namespace MobileGL { if (m_immutableLevels == levels) return; m_immutableLevels = levels; - if (m_immutableLevels > 0) { - m_levelRange.x() = std::min(m_levelRange.x(), m_immutableLevels - 1); - m_levelRange.y() = std::min(std::max(m_levelRange.y(), m_levelRange.x()), m_immutableLevels - 1); - } + UpdateEffectiveLevelRange(); ++m_textureParamsVersion; MGP_NOTE_AGGREGATE(TextureParams); // Immutable is a descriptor fact the backend reads - and NOT a request for an diff --git a/MobileGL/MG_State/GLState/TextureState/TextureObject.h b/MobileGL/MG_State/GLState/TextureState/TextureObject.h index 66c1e454..91fe371f 100644 --- a/MobileGL/MG_State/GLState/TextureState/TextureObject.h +++ b/MobileGL/MG_State/GLState/TextureState/TextureObject.h @@ -47,7 +47,12 @@ namespace MobileGL::MG_State::GLState { virtual void SetSwizzleParam(TextureSwizzleParam param, TextureSwizzleParam value) = 0; virtual void SetSwizzleParamRGBA(const Vec4& values) = 0; virtual const Vec4& GetAllSwizzleParams() const = 0; + // The EFFECTIVE [base, max] level range every consumer of the level range samples, + // allocates and completes against: on an immutable texture both ends are clamped into + // the stored levels (GL 4.6 core 8.17). GetSpecifiedLevelRange() is what the + // application set, which is what glGetTexParameter reports. virtual const UintVec2& GetLevelRange() const = 0; + virtual const UintVec2& GetSpecifiedLevelRange() const = 0; virtual void SetBaseLevel(Uint baseLevel) = 0; virtual void SetMaxLevel(Uint maxLevel) = 0; virtual Bool IsImmutable() const = 0; @@ -146,6 +151,7 @@ namespace MobileGL::MG_State::GLState { void SetSwizzleParam(TextureSwizzleParam param, TextureSwizzleParam value) override; void SetSwizzleParamRGBA(const Vec4& values) override; const UintVec2& GetLevelRange() const override; + const UintVec2& GetSpecifiedLevelRange() const override; void SetBaseLevel(Uint baseLevel) override; void SetMaxLevel(Uint maxLevel) override; Bool IsImmutable() const override; @@ -225,6 +231,8 @@ namespace MobileGL::MG_State::GLState { // this form on the built descriptor, so an over-call costs one 88-byte compare and // never an extra record. void PipePublishDescriptor(); + // Re-derives m_levelRange from the specified range; true when the effective range moved. + Bool UpdateEffectiveLevelRange(); // The PER-LEVEL and the CHAIN-CUT forms of the same call (P4a final review C-1). The // applier keeps a pending-upload set per (uploadTarget, level) and drops the entries // against the storage a respecify REPLACES - and the descriptor cannot tell it which: @@ -252,6 +260,9 @@ namespace MobileGL::MG_State::GLState { SharedPtr m_sampler = nullptr; Vec4 m_swizzleParams = {TextureSwizzleParam::Red, TextureSwizzleParam::Green, TextureSwizzleParam::Blue, TextureSwizzleParam::Alpha}; + // m_specifiedLevelRange is stored as set; m_levelRange is derived from it and the + // immutable level count by UpdateEffectiveLevelRange(). + UintVec2 m_specifiedLevelRange = {0, 1000}; UintVec2 m_levelRange = {0, 1000}; Uint m_immutableLevels = 0; Uint16 m_textureParamsVersion = 0; diff --git a/MobileGL/MG_Test/Texture/TextureTest.cpp b/MobileGL/MG_Test/Texture/TextureTest.cpp index 1055f963..c81762e0 100644 --- a/MobileGL/MG_Test/Texture/TextureTest.cpp +++ b/MobileGL/MG_Test/Texture/TextureTest.cpp @@ -1134,6 +1134,46 @@ TEST_F(TextureTest, ScalarDsaTextureParameterAppliesTheSameTargetRuleAsItsVector DrainPendingGlErrors(); } +// GL 4.6 core 8.17 clamps an immutable texture's base/max level when they are USED; the parameters +// keep what was set. A two-level glTexStorage must still read MAX_LEVEL 1000 back, while the +// effective range the backends consume is [0, 1] - and a later out-of-range BASE_LEVEL is likewise +// reported as set and clamped only in the effective range. +TEST_F(TextureTest, ImmutableStorageClampsOnlyTheEffectiveLevelRange) { + GLuint texture = 0; + MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_2D, 1, &texture); + ASSERT_NE(texture, 0u); + MG_Impl::GLImpl::TextureStorage2D(texture, 2, GL_RGBA8, 4, 4); + ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); + + GLint queried = -1; + MG_Impl::GLImpl::GetTextureParameteriv(texture, GL_TEXTURE_MAX_LEVEL, &queried); + EXPECT_EQ(queried, 1000); + MG_Impl::GLImpl::GetTextureParameteriv(texture, GL_TEXTURE_BASE_LEVEL, &queried); + EXPECT_EQ(queried, 0); + + const auto textureObject = MG_State::pGLContext->GetTextureObject(texture); + ASSERT_NE(textureObject, nullptr); + EXPECT_EQ(textureObject->GetLevelRange(), UintVec2(0, 1)); + + MG_Impl::GLImpl::TextureParameteri(texture, GL_TEXTURE_BASE_LEVEL, 5); + EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); + MG_Impl::GLImpl::GetTextureParameteriv(texture, GL_TEXTURE_BASE_LEVEL, &queried); + EXPECT_EQ(queried, 5); + GLfloat queriedFloat = -1.0f; + MG_Impl::GLImpl::GetTextureParameterfv(texture, GL_TEXTURE_MAX_LEVEL, &queriedFloat); + EXPECT_EQ(queriedFloat, 1000.0f); + EXPECT_EQ(textureObject->GetLevelRange(), UintVec2(1, 1)); + + // Back in range: the effective range follows the specified one again. + MG_Impl::GLImpl::TextureParameteri(texture, GL_TEXTURE_BASE_LEVEL, 0); + MG_Impl::GLImpl::TextureParameteri(texture, GL_TEXTURE_MAX_LEVEL, 0); + EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); + EXPECT_EQ(textureObject->GetLevelRange(), UintVec2(0, 0)); + + MG_Impl::GLImpl::DeleteTextures(1, &texture); + DrainPendingGlErrors(); +} + // GL 4.6 core 8.10 and 8.11 enumerate exactly ten targets and no proxy. The spec's own asymmetry is // the proof: GetTexLevelParameter needs an explicit clause adding the proxies to ITS list, and these // two entry points carry no such clause - so the proxies must be rejected here and still accepted