[Fix] (GL): keep TEXTURE_BASE_LEVEL/MAX_LEVEL as set on immutable textures and clamp only the effective range

This commit is contained in:
2026-10-12 08:22:16 -04:00
parent 4b5c315ea5
commit 2f312262ea
4 changed files with 81 additions and 23 deletions
@@ -1536,10 +1536,10 @@ namespace MobileGL::MG_Impl::GLImpl {
*params = static_cast<GLint>(textureObject->GetSamplerObject()->GetMaxLod());
break;
case GL_TEXTURE_BASE_LEVEL:
*params = static_cast<GLint>(textureObject->GetLevelRange().x());
*params = static_cast<GLint>(textureObject->GetSpecifiedLevelRange().x());
break;
case GL_TEXTURE_MAX_LEVEL:
*params = static_cast<GLint>(textureObject->GetLevelRange().y());
*params = static_cast<GLint>(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<GLint>(textureObject->GetLevelRange().x());
*params = static_cast<GLint>(textureObject->GetSpecifiedLevelRange().x());
}
break;
case GL_TEXTURE_MAX_LEVEL:
if (params) {
*params = static_cast<GLint>(textureObject->GetLevelRange().y());
*params = static_cast<GLint>(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<GLfloat>(textureObject->GetLevelRange().x());
*params = static_cast<GLfloat>(textureObject->GetSpecifiedLevelRange().x());
}
break;
case GL_TEXTURE_MAX_LEVEL:
if (params) {
*params = static_cast<GLfloat>(textureObject->GetLevelRange().y());
*params = static_cast<GLfloat>(textureObject->GetSpecifiedLevelRange().y());
}
break;
case GL_TEXTURE_IMMUTABLE_FORMAT:
@@ -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
@@ -47,7 +47,12 @@ namespace MobileGL::MG_State::GLState {
virtual void SetSwizzleParam(TextureSwizzleParam param, TextureSwizzleParam value) = 0;
virtual void SetSwizzleParamRGBA(const Vec4<TextureSwizzleParam>& values) = 0;
virtual const Vec4<TextureSwizzleParam>& 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<TextureSwizzleParam>& 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<SamplerObject> m_sampler = nullptr;
Vec4<TextureSwizzleParam> 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;
+40
View File
@@ -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