diff --git a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp index 4a034df1..4268e635 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp +++ b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp @@ -2197,7 +2197,7 @@ namespace MobileGL::MG_Impl::GLImpl { // Allocate in TextureObject if (isProxy) { - MGLOG_D("%s: isProxy = true, not allocating", __func__); + TextureImpl::pProxyTextureManager->RecordProxyLevel(textureUploadTarget, level, {width, height, depth}); } else { DiscardMipmapChainOnBaseRespecification(textureMipmapObject, textureUploadTarget, level); textureMipmapObject->AllocateStorage(textureUploadTarget, level, {{width, height, depth}, internalBytes}); @@ -2345,7 +2345,7 @@ namespace MobileGL::MG_Impl::GLImpl { // Allocate in TextureObject if (isProxy) { - MGLOG_D("%s: isProxy = true, not allocating", __func__); + TextureImpl::pProxyTextureManager->RecordProxyLevel(textureUploadTarget, level, {width, height, 1}); } else { MGLOG_D("%s: Allocating %d bytes at mip %d", __func__, internalBytes, level); DiscardMipmapChainOnBaseRespecification(textureMipmapObject, textureUploadTarget, level); @@ -2460,7 +2460,9 @@ 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 (!isProxy) { + if (isProxy) { + TextureImpl::pProxyTextureManager->RecordProxyLevel(textureUploadTarget, level, {width, 1, 1}); + } else { DiscardMipmapChainOnBaseRespecification(textureMipmapObject, textureUploadTarget, level); textureMipmapObject->AllocateStorage(textureUploadTarget, level, {{width, 1, 1}, internalBytes}); // After AllocateStorage, which clears the tag. No block-compressed format has a 1D @@ -2905,7 +2907,12 @@ namespace MobileGL::MG_Impl::GLImpl { case GL_TEXTURE_HEIGHT: case GL_TEXTURE_DEPTH: { const SizeT axis = pname == GL_TEXTURE_WIDTH ? 0 : (pname == GL_TEXTURE_HEIGHT ? 1 : 2); - if (storageType == TextureStorageType::Mipmap && mipmapObject != nullptr) { + // A proxy level never took storage; its size lives with the proxy manager. + const auto proxySize = + isProxy ? TextureImpl::pProxyTextureManager->GetProxyLevelTexelSize(uploadTarget, level) : std::nullopt; + if (proxySize) { + value = (*proxySize)[axis]; + } else if (storageType == TextureStorageType::Mipmap && mipmapObject != nullptr) { value = mipmapObject->GetMipmapTexelSize(uploadTarget, level)[axis]; } else if (storageType == TextureStorageType::Buffer) { // A buffer texture is one-dimensional. @@ -3988,10 +3995,13 @@ namespace MobileGL::MG_Impl::GLImpl { MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat); textureObject->SetInternalFormat(textureInternalFormat); - // A proxy records the format and nothing else - it must never take storage, and it must never - // be tagged compressed, or GL_TEXTURE_COMPRESSED_IMAGE_SIZE on a proxy would stop being - // INVALID_OPERATION. - if (isProxy) return; + // A proxy records the format and the level size and nothing else - it must never take + // storage, and it must never be tagged compressed, or GL_TEXTURE_COMPRESSED_IMAGE_SIZE on a + // proxy would stop being INVALID_OPERATION. + if (isProxy) { + TextureImpl::pProxyTextureManager->RecordProxyLevel(textureUploadTarget, level, {width, height, depth}); + return; + } const SizeT internalBpp = MG_Util::GetInternalBytesPerPixel(textureInternalFormat, TexturePixelDataType::UnsignedByte); @@ -4072,10 +4082,13 @@ namespace MobileGL::MG_Impl::GLImpl { MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat); textureObject->SetInternalFormat(textureInternalFormat); - // A proxy records the format and nothing else - it must never take storage, and it must never - // be tagged compressed, or GL_TEXTURE_COMPRESSED_IMAGE_SIZE on a proxy would stop being - // INVALID_OPERATION. - if (isProxy) return; + // A proxy records the format and the level size and nothing else - it must never take + // storage, and it must never be tagged compressed, or GL_TEXTURE_COMPRESSED_IMAGE_SIZE on a + // proxy would stop being INVALID_OPERATION. + if (isProxy) { + TextureImpl::pProxyTextureManager->RecordProxyLevel(textureUploadTarget, level, {width, height, 1}); + return; + } const SizeT internalBpp = MG_Util::GetInternalBytesPerPixel(textureInternalFormat, TexturePixelDataType::UnsignedByte); diff --git a/MobileGL/MG_Impl/GLImpl/Texture/ProxyTexture.cpp b/MobileGL/MG_Impl/GLImpl/Texture/ProxyTexture.cpp index 156f7d76..8b7e9b31 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/ProxyTexture.cpp +++ b/MobileGL/MG_Impl/GLImpl/Texture/ProxyTexture.cpp @@ -7,6 +7,7 @@ // End of Source File Header #include "ProxyTexture.h" +#include #include #include #include @@ -87,4 +88,51 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl { static SharedPtr nullTextureObject = nullptr; return nullTextureObject; } + + namespace { + // Whether the implementation can hold a level of this size on this proxy target. Every + // dimension of a mipmapped image shrinks with the level; array layers do not. + Bool ProxyLevelFits(TextureUploadTarget target, GLint level, IntVec3 size) { + MG_Backend::DynamicBackendParameters limits{}; + if (MG_Backend::pActiveBackendObject) limits = MG_Backend::pActiveBackendObject->GetDynamicParameters(); + const auto levelLimit = [level](Int limit) { return level < 31 ? std::max(limit >> level, 1) : 1; }; + const Int planar = levelLimit(limits.MaxTextureSize); + const Int cube = levelLimit(limits.MaxCubeMapTextureSize); + const Int volume = levelLimit(limits.Max3DTextureSize); + const Int layers = limits.MaxArrayTextureLayers; + switch (target) { + case TextureUploadTarget::ProxyTexture1D: + return size.x() <= planar; + case TextureUploadTarget::ProxyTexture1DArray: + return size.x() <= planar && size.y() <= layers; + case TextureUploadTarget::ProxyTexture2DArray: + return size.x() <= planar && size.y() <= planar && size.z() <= layers; + case TextureUploadTarget::ProxyTexture3D: + return size.x() <= volume && size.y() <= volume && size.z() <= volume; + case TextureUploadTarget::ProxyCubeMap: + return size.x() <= cube && size.y() <= cube; + case TextureUploadTarget::ProxyCubeMapArray: + return size.x() <= cube && size.y() <= cube && size.z() <= layers; + case TextureUploadTarget::ProxyTextureRectangle: + return level == 0 && size.x() <= limits.MaxTextureSize && size.y() <= limits.MaxTextureSize; + default: + return size.x() <= planar && size.y() <= planar; + } + } + } // namespace + + void ProxyTextureManager::RecordProxyLevel(TextureUploadTarget target, GLint level, IntVec3 size) { + if (level < 0) return; + auto& levels = m_proxyLevelSizes[target]; + if (levels.size() <= static_cast(level)) levels.resize(static_cast(level) + 1); + levels[level] = ProxyLevelFits(target, level, size) ? size : IntVec3{0, 0, 0}; + } + + Optional ProxyTextureManager::GetProxyLevelTexelSize(TextureUploadTarget target, GLint level) const { + auto it = m_proxyLevelSizes.find(target); + if (it == m_proxyLevelSizes.end() || level < 0 || it->second.size() <= static_cast(level)) { + return std::nullopt; + } + return it->second[level]; + } } // namespace MobileGL::MG_Impl::GLImpl::TextureImpl diff --git a/MobileGL/MG_Impl/GLImpl/Texture/ProxyTexture.h b/MobileGL/MG_Impl/GLImpl/Texture/ProxyTexture.h index f83f3faf..60611ffb 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/ProxyTexture.h +++ b/MobileGL/MG_Impl/GLImpl/Texture/ProxyTexture.h @@ -19,8 +19,15 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl { TextureUploadTarget target); const SharedPtr& GetProxyTextureObject(TextureUploadTarget target); + // The image a proxy glTexImage* or glCompressedTexImage* describes, kept here because a proxy + // never takes storage. A request past the implementation limits records all zeros (GL 4.6 + // core 8.22), which is how glGetTexLevelParameter tells the application it cannot be had. + void RecordProxyLevel(TextureUploadTarget target, GLint level, IntVec3 size); + Optional GetProxyLevelTexelSize(TextureUploadTarget target, GLint level) const; + private: UnorderedMap> m_proxyTexturesMap; + UnorderedMap>> m_proxyLevelSizes; }; extern UniquePtr& pProxyTextureManager; diff --git a/MobileGL/MG_Test/Texture/TextureTest.cpp b/MobileGL/MG_Test/Texture/TextureTest.cpp index 92fe5e87..a78c8b13 100644 --- a/MobileGL/MG_Test/Texture/TextureTest.cpp +++ b/MobileGL/MG_Test/Texture/TextureTest.cpp @@ -1204,18 +1204,58 @@ TEST_F(TextureTest, TextureParameterRejectsProxyTargetsThatGetTexLevelParameterS ExpectSingleGlError(GL_INVALID_ENUM); // The level query keeps its proxy support - that is the entire point of a proxy texture, and the - // predicate deliberately does not gate it. Only the ERROR is asserted, not the width: MobileGL - // does not currently report a proxy level's dimensions back (it answers 0), which is a separate - // pre-existing gap in GetTexLevelParameter and not something this predicate decides. What - // matters here is that the query is not turned into GL_INVALID_ENUM alongside the setters. + // predicate deliberately does not gate it: the query answers the size the proxy was given + // instead of turning into GL_INVALID_ENUM alongside the setters. GLint proxyWidth = -1; MG_Impl::GLImpl::GetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &proxyWidth); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << "the proxy target must still be accepted by GetTexLevelParameter"; + EXPECT_EQ(proxyWidth, 4) << "a proxy level reports the width it was given"; DrainPendingGlErrors(); } +// GL 4.6 core 8.22: a proxy level reports the size it was given while the implementation can hold +// it, and all zeros - with no error - once a dimension passes the limit at that level. Applications +// probe GL_MAX_TEXTURE_SIZE this way, halving from the largest size until the width comes back. +TEST_F(TextureTest, ProxyTextureLevelReportsRequestedSizeOrZerosPastTheLimit) { + GLint maxTextureSize = 0; + MG_Impl::GLImpl::GetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); + ASSERT_GT(maxTextureSize, 1); + const auto levelSize = [](GLenum target, GLint level, GLenum pname) { + GLint value = -1; + MG_Impl::GLImpl::GetTexLevelParameteriv(target, level, pname, &value); + return value; + }; + + MG_Impl::GLImpl::TexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA8, maxTextureSize, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, + nullptr); + EXPECT_EQ(levelSize(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH), maxTextureSize); + EXPECT_EQ(levelSize(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT), 16); + + MG_Impl::GLImpl::TexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA8, maxTextureSize * 2, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, + nullptr); + EXPECT_EQ(levelSize(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH), 0) << "past GL_MAX_TEXTURE_SIZE"; + EXPECT_EQ(levelSize(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT), 0) << "every dimension is cleared"; + + // The limit halves with each level. + MG_Impl::GLImpl::TexImage2D(GL_PROXY_TEXTURE_2D, 1, GL_RGBA8, maxTextureSize, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, + nullptr); + EXPECT_EQ(levelSize(GL_PROXY_TEXTURE_2D, 1, GL_TEXTURE_WIDTH), 0); + MG_Impl::GLImpl::TexImage2D(GL_PROXY_TEXTURE_2D, 1, GL_RGBA8, maxTextureSize / 2, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, + nullptr); + EXPECT_EQ(levelSize(GL_PROXY_TEXTURE_2D, 1, GL_TEXTURE_WIDTH), maxTextureSize / 2); + + MG_Impl::GLImpl::TexImage1D(GL_PROXY_TEXTURE_1D, 0, GL_RGBA8, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + EXPECT_EQ(levelSize(GL_PROXY_TEXTURE_1D, 0, GL_TEXTURE_WIDTH), 64); + MG_Impl::GLImpl::TexImage3D(GL_PROXY_TEXTURE_3D, 0, GL_RGBA8, 8, 4, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + EXPECT_EQ(levelSize(GL_PROXY_TEXTURE_3D, 0, GL_TEXTURE_WIDTH), 8); + EXPECT_EQ(levelSize(GL_PROXY_TEXTURE_3D, 0, GL_TEXTURE_HEIGHT), 4); + EXPECT_EQ(levelSize(GL_PROXY_TEXTURE_3D, 0, GL_TEXTURE_DEPTH), 2); + + EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << "an unsupported proxy size is not an error"; +} + TEST_F(TextureTest, GenThenBindCreatesObjectForUnsizedPackedBgraSubImageUpload) { GLuint texture = 0; MG_Impl::GLImpl::GenTextures(1, &texture); diff --git a/MobileGL/MG_Util/Converters/GLToMG/TextureEnumConverter.cpp b/MobileGL/MG_Util/Converters/GLToMG/TextureEnumConverter.cpp index 8e63347a..d974396a 100644 --- a/MobileGL/MG_Util/Converters/GLToMG/TextureEnumConverter.cpp +++ b/MobileGL/MG_Util/Converters/GLToMG/TextureEnumConverter.cpp @@ -410,8 +410,12 @@ namespace MobileGL { return TextureUploadTarget::Texture2D; case GL_TEXTURE_3D: return TextureUploadTarget::Texture3D; + case GL_PROXY_TEXTURE_1D: + return TextureUploadTarget::ProxyTexture1D; case GL_PROXY_TEXTURE_2D: return TextureUploadTarget::ProxyTexture2D; + case GL_PROXY_TEXTURE_3D: + return TextureUploadTarget::ProxyTexture3D; case GL_TEXTURE_1D_ARRAY: return TextureUploadTarget::Texture1DArray; case GL_PROXY_TEXTURE_1D_ARRAY: diff --git a/MobileGL/MG_Util/Converters/MGToGL/TextureEnumConverter.cpp b/MobileGL/MG_Util/Converters/MGToGL/TextureEnumConverter.cpp index 88fbb456..d03eef4d 100644 --- a/MobileGL/MG_Util/Converters/MGToGL/TextureEnumConverter.cpp +++ b/MobileGL/MG_Util/Converters/MGToGL/TextureEnumConverter.cpp @@ -317,8 +317,12 @@ namespace MobileGL { return GL_TEXTURE_1D; case TextureUploadTarget::Texture3D: return GL_TEXTURE_3D; + case TextureUploadTarget::ProxyTexture1D: + return GL_PROXY_TEXTURE_1D; case TextureUploadTarget::ProxyTexture2D: return GL_PROXY_TEXTURE_2D; + case TextureUploadTarget::ProxyTexture3D: + return GL_PROXY_TEXTURE_3D; case TextureUploadTarget::Texture1DArray: return GL_TEXTURE_1D_ARRAY; case TextureUploadTarget::ProxyTexture1DArray: