[Fix, Test] (GLImpl): record the specific compressed internalformat in TexImage3D and TexStorage3D

This commit is contained in:
2026-08-20 10:17:55 -04:00
parent 1c0be3e715
commit a9b4c47fea
2 changed files with 103 additions and 0 deletions
@@ -2197,6 +2197,19 @@ namespace MobileGL::MG_Impl::GLImpl {
} else {
DiscardMipmapChainOnBaseRespecification(textureMipmapObject, textureUploadTarget, level);
textureMipmapObject->AllocateStorage(textureUploadTarget, level, {{width, height, depth}, internalBytes});
// The same specific-compressed-format tag glTexImage2D records (see TexImage2D_State):
// GL 4.6 core 8.5 commits the level to that format, so GL_TEXTURE_COMPRESSED and
// GL_TEXTURE_INTERNAL_FORMAT must report it - and, less obviously, glCopyImageSubData
// sizes the level's texel BLOCK from it. Without the tag a GL_COMPRESSED_RG_RGTC2
// array level measured as the RG8 storage it resolved to, 2 bytes instead of 16, and
// the copy-compatibility rule refused a pairing 18.3.2 requires. AllocateStorage above
// clears the tag, so this has to follow it.
const auto compressedInfo = MG_Util::GetCompressedFormatInfo(static_cast<GLenum>(internalformat));
if (compressedInfo.blockWidth != 0) {
textureMipmapObject->SetMipmapCompressedImage(
textureUploadTarget, level, static_cast<GLenum>(internalformat), nullptr,
MG_Util::CalculateCompressedTextureImageSize(compressedInfo, {width, height, depth}));
}
}
if (!originalPixels) {
@@ -4620,6 +4633,10 @@ namespace MobileGL::MG_Impl::GLImpl {
// Array targets keep their layer count constant across levels; only true 3D
// textures halve depth per level (GL 3.3 §3.9 glTexStorage3D).
const Bool depthMips = DepthParticipatesInMipmapping(textureObject->GetTarget());
// The same specific-compressed-format tag glTexStorage2D records, for the array targets a
// compressed glTexStorage3D is legal on (GL_TEXTURE_3D was refused above). Zero width means
// a generic format, which MobileGL answers with uncompressed storage, so it is not tagged.
const auto compressedInfo = MG_Util::GetCompressedFormatInfo(internalformat);
for (GLsizei level = 0; level < levels; ++level) {
const GLsizei levelWidth = std::max<GLsizei>(1, width >> level);
const GLsizei levelHeight = std::max<GLsizei>(1, height >> level);
@@ -4629,6 +4646,13 @@ namespace MobileGL::MG_Impl::GLImpl {
textureMipmapObject->AllocateStorage(textureUploadTarget, level,
{{levelWidth, levelHeight, levelDepth}, byteSize});
textureMipmapObject->MarkStorageDirty(textureUploadTarget, level, false);
if (compressedInfo.blockWidth != 0) {
// After AllocateStorage, which clears the tag.
textureMipmapObject->SetMipmapCompressedImage(
textureUploadTarget, static_cast<Uint>(level), internalformat, nullptr,
MG_Util::CalculateCompressedTextureImageSize(compressedInfo,
{levelWidth, levelHeight, levelDepth}));
}
}
// See TextureStorage1D.
textureMipmapObject->TruncateMipmapLevels(textureUploadTarget, static_cast<Uint>(levels));
+79
View File
@@ -1651,6 +1651,56 @@ TEST_F(TextureTest, AnUncompressedRespecificationClearsTheCompressedTag) {
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
}
// The same rule for the 3D entry points, which never recorded the tag at all. Besides the two
// level queries this decides the level's texel BLOCK SIZE, which glCopyImageSubData compares
// against the other endpoint's - an untagged GL_COMPRESSED_RG_RGTC2 array level measured as the
// RG8 storage it resolves to, 2 bytes instead of 16.
TEST_F(TextureTest, TexImage3DAndTexStorage3DTagASpecificCompressedInternalFormat) {
GLuint texture = 0;
MG_Impl::GLImpl::GenTextures(1, &texture);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D_ARRAY, texture);
MG_Impl::GLImpl::TexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RG_RGTC2, 8, 8, 2, 0, GL_RG,
GL_UNSIGNED_BYTE, nullptr);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
GLint compressed = GL_FALSE;
MG_Impl::GLImpl::GetTexLevelParameteriv(GL_TEXTURE_2D_ARRAY, 0, GL_TEXTURE_COMPRESSED, &compressed);
EXPECT_EQ(compressed, GL_TRUE);
GLint internalFormat = 0;
MG_Impl::GLImpl::GetTexLevelParameteriv(GL_TEXTURE_2D_ARRAY, 0, GL_TEXTURE_INTERNAL_FORMAT, &internalFormat);
EXPECT_EQ(internalFormat, static_cast<GLint>(GL_COMPRESSED_RG_RGTC2));
// 8x8 in 4x4 blocks of 16 bytes each is 64 bytes a layer, and both layers count.
GLint imageSize = 0;
MG_Impl::GLImpl::GetTexLevelParameteriv(GL_TEXTURE_2D_ARRAY, 0, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &imageSize);
EXPECT_EQ(imageSize, 128);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
// The texel shadow behind the tag keeps the uncompressed storage the format resolves to.
const auto textureObject = MG_State::pGLContext->GetTextureObject(texture);
ASSERT_NE(textureObject, nullptr);
EXPECT_EQ(textureObject->GetFormat(), TextureInternalFormat::RG8);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D_ARRAY, 0);
// glTexStorage3D has the same gap and the same fix; immutable storage plus
// glCompressedTexSubImage3D is the modern way to upload a compressed array texture.
GLuint storageTexture = 0;
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_2D_ARRAY, 1, &storageTexture);
MG_Impl::GLImpl::TextureStorage3D(storageTexture, 1, GL_COMPRESSED_RG_RGTC2, 8, 8, 2);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D_ARRAY, storageTexture);
compressed = GL_FALSE;
MG_Impl::GLImpl::GetTexLevelParameteriv(GL_TEXTURE_2D_ARRAY, 0, GL_TEXTURE_COMPRESSED, &compressed);
EXPECT_EQ(compressed, GL_TRUE);
imageSize = 0;
MG_Impl::GLImpl::GetTexLevelParameteriv(GL_TEXTURE_2D_ARRAY, 0, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &imageSize);
EXPECT_EQ(imageSize, 128);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D_ARRAY, 0);
}
namespace {
// A 16x16 RGBA8 texture with exactly `levelCount` levels, defined the way
// KHR-GL43.copy_image.non_existent_mipmap defines its textures - glTexImage2D per
@@ -4511,3 +4561,32 @@ TEST_F(TextureTest, CopyImageSubDataChecksARenderbufferLevelAndStorage) {
EXPECT_FALSE(g_copyImageSubDataCall.Called);
ExpectSingleGlError(GL_INVALID_OPERATION);
}
// A 16-byte RGTC2 block and a 16-byte RGBA32UI texel are in the same size class, so GL 4.6 core
// 18.3.2 requires this copy to succeed. It did not for an ARRAY source: glTexImage3D recorded no
// specific-compressed-format tag, so the level was measured as the 2-byte RG8 storage RGTC2
// resolves to and the compatibility rule saw 2 against 16.
TEST_F(TextureTest, CopyImageSubDataSizesACompressedArrayLevelByItsBlock) {
const ScopedTextureBackendFunctionsOverride backendGuard;
MG_Backend::gBackendFunctionsTable.GL.CopyImageSubData = RecordCopyImageSubData;
g_copyImageSubDataCall = {};
GLuint compressedSource = 0;
MG_Impl::GLImpl::GenTextures(1, &compressedSource);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D_ARRAY, compressedSource);
MG_Impl::GLImpl::TexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RG_RGTC2, 8, 8, 1, 0, GL_RG,
GL_UNSIGNED_BYTE, nullptr);
MG_Impl::GLImpl::TexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BASE_LEVEL, 0);
MG_Impl::GLImpl::TexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, 0);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D_ARRAY, 0);
GLuint uncompressedDestination = 0;
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_2D_ARRAY, 1, &uncompressedDestination);
MG_Impl::GLImpl::TextureStorage3D(uncompressedDestination, 1, GL_RGBA32UI, 8, 8, 1);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
MG_Impl::GLImpl::CopyImageSubData(compressedSource, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, uncompressedDestination,
GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 8, 8, 1);
EXPECT_TRUE(g_copyImageSubDataCall.Called);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
}