From e83e6ed76e8a947d729c6c381d783ace6f18e47a Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Thu, 2 Jul 2026 22:49:54 +0800 Subject: [PATCH] [Fix] (MG_State/GLState, MG_Impl/GLImpl): track immutable texture storage [skip ci] --- .../MG_Impl/GLImpl/Texture/GL_Texture.cpp | 136 ++++++++++++++++-- .../GLState/TextureState/TextureObject.cpp | 28 ++++ .../GLState/TextureState/TextureObject.h | 7 + 3 files changed, 158 insertions(+), 13 deletions(-) diff --git a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp index 23de86d5..fc4a3ba7 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp +++ b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp @@ -99,6 +99,44 @@ namespace MobileGL::MG_Impl::GLImpl { MG_Util::ConvertGLEnumToTexturePixelDataType(realType)); } + Bool IsSizedTextureStorageInternalFormat(TextureInternalFormat textureInternalFormat) { + switch (textureInternalFormat) { + case TextureInternalFormat::Red: + case TextureInternalFormat::RG: + case TextureInternalFormat::RGB: + case TextureInternalFormat::RGBA: + case TextureInternalFormat::DepthComponent: + case TextureInternalFormat::DepthStencil: + case TextureInternalFormat::Unknown: + return false; + default: + return true; + } + } + + Bool ValidateTextureStorageInternalFormat(TextureInternalFormat textureInternalFormat, const char* caller) { + // TODO: Replace this sized-format filter with the full ARB_texture_storage legal-format table. + if (!IsSizedTextureStorageInternalFormat(textureInternalFormat)) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidEnum, + MakeUnique("MG_Impl/GLImpl", caller, + "TexStorage requires a sized internal format.")); + return false; + } + return TextureImpl::ValidateTextureInternalFormat(textureInternalFormat); + } + + Bool ValidateTextureMutable(const SharedPtr& textureObject, + const char* caller) { + if (!textureObject || !textureObject->IsImmutable()) return true; + + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique("MG_Impl/GLImpl", caller, + "Immutable texture storage cannot be redefined.")); + return false; + } + GLint GetTextureComponentType(TextureInternalFormat textureInternalFormat, GLint size, Bool depthComponent, Bool stencilComponent) { if (size <= 0) return GL_NONE; @@ -529,6 +567,12 @@ namespace MobileGL::MG_Impl::GLImpl { case GL_TEXTURE_MAX_LEVEL: *params = static_cast(textureObject->GetLevelRange().y()); break; + case GL_TEXTURE_IMMUTABLE_FORMAT: + *params = textureObject->IsImmutable() ? GL_TRUE : GL_FALSE; + break; + case GL_TEXTURE_IMMUTABLE_LEVELS: + *params = static_cast(textureObject->GetImmutableLevels()); + break; case GL_TEXTURE_WRAP_S: *params = (GLint)MG_Util::ConvertSamplerWrapModeToGLEnum(textureObject->GetSamplerObject()->GetWrapS()); break; @@ -720,6 +764,12 @@ namespace MobileGL::MG_Impl::GLImpl { MOBILEGL_ASSERT(nullptr != static_cast(textureObject.get()), "Texture object here should always be an object with mipmap"); auto textureMipmapObject = static_cast(textureObject.get()); + if (static_cast(level) >= textureMipmapObject->GetMipmapLevelCount()) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", __func__, "Texture level is out of range.")); + return; + } auto texelSize = textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, level); SizeT inputSize = 0; @@ -1206,6 +1256,7 @@ namespace MobileGL::MG_Impl::GLImpl { // ===================== Error Checking ============================== if (!TextureImpl::ValidateTextureObject(textureObject)) return; + if (!ValidateTextureMutable(textureObject, __func__)) return; // ======================= Processing ================================ if (internalformat == GL_ALPHA || format == GL_ALPHA) { @@ -1319,6 +1370,7 @@ namespace MobileGL::MG_Impl::GLImpl { // ===================== Error Checking ============================== if (!TextureImpl::ValidateTextureObject(textureObject)) return; + if (!ValidateTextureMutable(textureObject, __func__)) return; // ======================= Processing ================================ if (internalformat == GL_ALPHA || format == GL_ALPHA) { @@ -1422,6 +1474,7 @@ namespace MobileGL::MG_Impl::GLImpl { isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget) : bindingSlot.GetBoundObject(); if (!TextureImpl::ValidateTextureObject(textureObject)) return; + if (!ValidateTextureMutable(textureObject, __func__)) return; if (internalFormat == GL_ALPHA || format == GL_ALPHA) { textureObject->SetSwizzleParamRGBA({TextureSwizzleParam::Zero, TextureSwizzleParam::Zero, @@ -1620,6 +1673,16 @@ namespace MobileGL::MG_Impl::GLImpl { *params = static_cast(textureObject->GetLevelRange().y()); } break; + case GL_TEXTURE_IMMUTABLE_FORMAT: + if (params) { + *params = textureObject->IsImmutable() ? GL_TRUE : GL_FALSE; + } + break; + case GL_TEXTURE_IMMUTABLE_LEVELS: + if (params) { + *params = static_cast(textureObject->GetImmutableLevels()); + } + break; case GL_TEXTURE_BORDER_COLOR: if (params) { const auto& borderColor = textureObject->GetBorderColor(); @@ -1748,6 +1811,16 @@ namespace MobileGL::MG_Impl::GLImpl { *params = static_cast(textureObject->GetLevelRange().y()); } break; + case GL_TEXTURE_IMMUTABLE_FORMAT: + if (params) { + *params = textureObject->IsImmutable() ? 1.0f : 0.0f; + } + break; + case GL_TEXTURE_IMMUTABLE_LEVELS: + if (params) { + *params = static_cast(textureObject->GetImmutableLevels()); + } + break; case GL_TEXTURE_BORDER_COLOR: if (params) { const auto& borderColor = textureObject->GetBorderColor(); @@ -2165,9 +2238,14 @@ namespace MobileGL::MG_Impl::GLImpl { // TODO: implement } - void CopyTexImage2D_State(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, + Bool CopyTexImage2D_State(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border) { auto internalFormat = MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat); + const auto textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); + const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); + auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget); + if (!ValidateTextureMutable(textureObject, __func__)) return false; + const auto& currentReadFBO = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(); if (!currentReadFBO) { @@ -2175,7 +2253,7 @@ namespace MobileGL::MG_Impl::GLImpl { ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", "CopyTexImage2D_State", "No framebuffer is currently bound to the GL_READ_FRAMEBUFFER target.")); - return; + return false; } Bool isDepth = MG_Util::IsDepthFormatInternalFormat(internalFormat); @@ -2194,7 +2272,7 @@ namespace MobileGL::MG_Impl::GLImpl { ErrorCode::InvalidOperation, \ MakeUnique("MG_Impl/GLImpl", "CopyTexImage2D_State", \ "The attachment specified by the read buffer is incomplete.")); \ - return; \ + return false; \ } if (isDepth) { GET_SRC_INTERNAL_FORMAT(FramebufferAttachmentType::Depth); @@ -2217,6 +2295,7 @@ namespace MobileGL::MG_Impl::GLImpl { MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelUnpack).GetBoundObject(); TexImage2D_State(target, level, (GLint)realInternalFormat, width, height, border, format, type, nullptr); MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelUnpack).Bind(pixelUnpackBufferObject); + return true; } void CopyTexImage2D_Backend(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, @@ -2252,18 +2331,33 @@ namespace MobileGL::MG_Impl::GLImpl { void CompressedTexImage3D_State(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void* data) { + const auto textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); + const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); + auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget); + if (!ValidateTextureMutable(textureObject, __func__)) return; + // TODO: implement THROW_UNIMPL_EXCEPTION; } void CompressedTexImage2D_State(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void* data) { + const auto textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); + const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); + auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget); + if (!ValidateTextureMutable(textureObject, __func__)) return; + // TODO: implement THROW_UNIMPL_EXCEPTION; } void CompressedTexImage1D_State(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void* data) { + const auto textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); + const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); + auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget); + if (!ValidateTextureMutable(textureObject, __func__)) return; + // TODO: implement THROW_UNIMPL_EXCEPTION; } @@ -2567,15 +2661,14 @@ namespace MobileGL::MG_Impl::GLImpl { if (!TextureImpl::ValidateTextureSizeRange(width, 1, 1)) return; TextureInternalFormat textureInternalFormat = MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat); - textureInternalFormat = MG_Util::ConvertInternalFormatToSized(textureInternalFormat, TextureInputFormat::RGBA, - TexturePixelDataType::UnsignedByte); - if (!TextureImpl::ValidateTextureInternalFormat(textureInternalFormat)) return; + if (!ValidateTextureStorageInternalFormat(textureInternalFormat, __func__)) return; if (textureObject->GetStorageType() != TextureStorageType::Mipmap) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", __func__, "Texture storage is not mipmap-backed.")); return; } + if (!ValidateTextureMutable(textureObject, __func__)) return; const auto textureUploadTarget = GetPrimaryUploadTarget(textureObject); if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return; @@ -2588,6 +2681,7 @@ namespace MobileGL::MG_Impl::GLImpl { textureMipmapObject->AllocateStorage(textureUploadTarget, level, {{levelWidth, 1, 1}, byteSize}); textureMipmapObject->MarkStorageDirty(textureUploadTarget, level, false); } + textureObject->SetImmutableLevels(static_cast(levels)); } void TextureStorage2D(GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height) { @@ -2602,15 +2696,21 @@ namespace MobileGL::MG_Impl::GLImpl { if (!TextureImpl::ValidateTextureSizeRange(width, height, 1)) return; TextureInternalFormat textureInternalFormat = MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat); - textureInternalFormat = MG_Util::ConvertInternalFormatToSized(textureInternalFormat, TextureInputFormat::RGBA, - TexturePixelDataType::UnsignedByte); - if (!TextureImpl::ValidateTextureInternalFormat(textureInternalFormat)) return; + if (!ValidateTextureStorageInternalFormat(textureInternalFormat, __func__)) return; if (textureObject->GetStorageType() != TextureStorageType::Mipmap) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", __func__, "Texture storage is not mipmap-backed.")); return; } + if (!ValidateTextureMutable(textureObject, __func__)) return; + if (textureObject->GetTarget() == TextureTarget::TextureCubeMap && width != height) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", __func__, + "Cube map immutable storage must be square.")); + return; + } auto textureUploadTarget = GetPrimaryUploadTarget(textureObject); if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return; @@ -2633,6 +2733,7 @@ namespace MobileGL::MG_Impl::GLImpl { textureMipmapObject->AllocateStorage(textureUploadTarget, level, {{levelWidth, levelHeight, 1}, byteSize}); textureMipmapObject->MarkStorageDirty(textureUploadTarget, level, false); } + textureObject->SetImmutableLevels(static_cast(levels)); } void TextureStorage3D(GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, @@ -2648,15 +2749,23 @@ namespace MobileGL::MG_Impl::GLImpl { if (!TextureImpl::ValidateTextureSizeRange(width, height, depth)) return; TextureInternalFormat textureInternalFormat = MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat); - textureInternalFormat = MG_Util::ConvertInternalFormatToSized(textureInternalFormat, TextureInputFormat::RGBA, - TexturePixelDataType::UnsignedByte); - if (!TextureImpl::ValidateTextureInternalFormat(textureInternalFormat)) return; + if (!ValidateTextureStorageInternalFormat(textureInternalFormat, __func__)) return; if (textureObject->GetStorageType() != TextureStorageType::Mipmap) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", __func__, "Texture storage is not mipmap-backed.")); return; } + if (!ValidateTextureMutable(textureObject, __func__)) return; + if (textureObject->GetTarget() == TextureTarget::TextureCubeMapArray && + (width != height || depth % 6 != 0)) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeUnique( + "MG_Impl/GLImpl", __func__, + "Cube map array immutable storage must be square with depth multiple of 6.")); + return; + } const auto textureUploadTarget = GetPrimaryUploadTarget(textureObject); if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return; @@ -2673,6 +2782,7 @@ namespace MobileGL::MG_Impl::GLImpl { {{levelWidth, levelHeight, levelDepth}, byteSize}); textureMipmapObject->MarkStorageDirty(textureUploadTarget, level, false); } + textureObject->SetImmutableLevels(static_cast(levels)); } void TextureStorage2DMultisample(GLuint texture, GLsizei samples, GLenum internalformat, GLsizei width, @@ -3415,7 +3525,7 @@ namespace MobileGL::MG_Impl::GLImpl { void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border) { - CopyTexImage2D_State(target, level, internalformat, x, y, width, height, border); + if (!CopyTexImage2D_State(target, level, internalformat, x, y, width, height, border)) return; CopyTexImage2D_Backend(target, level, internalformat, x, y, width, height, border); } diff --git a/MobileGL/MG_State/GLState/TextureState/TextureObject.cpp b/MobileGL/MG_State/GLState/TextureState/TextureObject.cpp index be1c71ed..4aae56a0 100644 --- a/MobileGL/MG_State/GLState/TextureState/TextureObject.cpp +++ b/MobileGL/MG_State/GLState/TextureState/TextureObject.cpp @@ -162,19 +162,47 @@ namespace MobileGL { } void TextureObjectBase::SetBaseLevel(Uint baseLevel) { + if (IsImmutable() && m_immutableLevels > 0) { + baseLevel = std::min(baseLevel, m_immutableLevels - 1); + } if (baseLevel == m_levelRange.x()) return; m_levelRange.x() = baseLevel; + if (IsImmutable() && m_levelRange.y() < m_levelRange.x()) { + m_levelRange.y() = m_levelRange.x(); + } ++m_textureParamsVersion; } 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_levelRange.y() = maxLevel; ++m_textureParamsVersion; } + Bool TextureObjectBase::IsImmutable() const { + return m_immutableLevels > 0; + } + + Uint TextureObjectBase::GetImmutableLevels() const { + return m_immutableLevels; + } + + void TextureObjectBase::SetImmutableLevels(Uint levels) { + 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); + } + ++m_textureParamsVersion; + } + Uint16 TextureObjectBase::GetTextureParamsVersion() const { return m_textureParamsVersion; } diff --git a/MobileGL/MG_State/GLState/TextureState/TextureObject.h b/MobileGL/MG_State/GLState/TextureState/TextureObject.h index 021a09a1..e571d1e2 100644 --- a/MobileGL/MG_State/GLState/TextureState/TextureObject.h +++ b/MobileGL/MG_State/GLState/TextureState/TextureObject.h @@ -43,6 +43,9 @@ namespace MobileGL::MG_State::GLState { virtual const UintVec2& GetLevelRange() const = 0; virtual void SetBaseLevel(Uint baseLevel) = 0; virtual void SetMaxLevel(Uint maxLevel) = 0; + virtual Bool IsImmutable() const = 0; + virtual Uint GetImmutableLevels() const = 0; + virtual void SetImmutableLevels(Uint levels) = 0; virtual Uint16 GetTextureParamsVersion() const = 0; virtual Int GetSamples() const = 0; virtual void SetSamples(Int samples) = 0; @@ -79,6 +82,9 @@ namespace MobileGL::MG_State::GLState { const UintVec2& GetLevelRange() const override; void SetBaseLevel(Uint baseLevel) override; void SetMaxLevel(Uint maxLevel) override; + Bool IsImmutable() const override; + Uint GetImmutableLevels() const override; + void SetImmutableLevels(Uint levels) override; Uint16 GetTextureParamsVersion() const override; Int GetSamples() const override; void SetSamples(Int samples) override; @@ -100,6 +106,7 @@ namespace MobileGL::MG_State::GLState { Vec4 m_swizzleParams = {TextureSwizzleParam::Red, TextureSwizzleParam::Green, TextureSwizzleParam::Blue, TextureSwizzleParam::Alpha}; UintVec2 m_levelRange = {0, 1000}; + Uint m_immutableLevels = 0; Uint16 m_textureParamsVersion = 0; Int m_samples = 0; Bool m_fixedSampleLocations = true;