[Fix, Test] (MG_Impl/GLImpl, MG_Test/Texture, MG_Util/Converters): report proxy texture level sizes

- Record the size a proxy glTexImage* asks for and answer it from
  glGetTexLevelParameter, or zeros when it passes the texture size limits
- Map GL_PROXY_TEXTURE_1D and GL_PROXY_TEXTURE_3D to their upload targets
This commit is contained in:
2026-11-08 19:43:21 -04:00
parent 8497eee197
commit 04c8773d77
6 changed files with 132 additions and 16 deletions
+25 -12
View File
@@ -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<MG_State::GLState::TextureObjectMipmap*>(textureObject.get()),
"Texture object here should always be an object with mipmap");
auto textureMipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(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);
@@ -7,6 +7,7 @@
// End of Source File Header
#include "ProxyTexture.h"
#include <MG_Backend/BackendObjects.h>
#include <MG_State/GLState/TextureState/TextureObject1D.h>
#include <MG_State/GLState/TextureState/TextureObject2D.h>
#include <MG_State/GLState/TextureState/TextureObject2DCube.h>
@@ -87,4 +88,51 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl {
static SharedPtr<MG_State::GLState::ITextureObject> 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<SizeT>(level)) levels.resize(static_cast<SizeT>(level) + 1);
levels[level] = ProxyLevelFits(target, level, size) ? size : IntVec3{0, 0, 0};
}
Optional<IntVec3> 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<SizeT>(level)) {
return std::nullopt;
}
return it->second[level];
}
} // namespace MobileGL::MG_Impl::GLImpl::TextureImpl
@@ -19,8 +19,15 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl {
TextureUploadTarget target);
const SharedPtr<MG_State::GLState::ITextureObject>& 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<IntVec3> GetProxyLevelTexelSize(TextureUploadTarget target, GLint level) const;
private:
UnorderedMap<TextureUploadTarget, SharedPtr<MG_State::GLState::ITextureObject>> m_proxyTexturesMap;
UnorderedMap<TextureUploadTarget, Vector<Optional<IntVec3>>> m_proxyLevelSizes;
};
extern UniquePtr<ProxyTextureManager>& pProxyTextureManager;
+44 -4
View File
@@ -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);
@@ -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:
@@ -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: