From a9b4c47fea1b12796eb2014a82f0d62c112088d5 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Thu, 20 Aug 2026 10:17:55 -0400 Subject: [PATCH] [Fix, Test] (GLImpl): record the specific compressed internalformat in TexImage3D and TexStorage3D --- .../MG_Impl/GLImpl/Texture/GL_Texture.cpp | 24 ++++++ MobileGL/MG_Test/Texture/TextureTest.cpp | 79 +++++++++++++++++++ 2 files changed, 103 insertions(+) diff --git a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp index a9539587..77573173 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp +++ b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp @@ -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(internalformat)); + if (compressedInfo.blockWidth != 0) { + textureMipmapObject->SetMipmapCompressedImage( + textureUploadTarget, level, static_cast(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(1, width >> level); const GLsizei levelHeight = std::max(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(level), internalformat, nullptr, + MG_Util::CalculateCompressedTextureImageSize(compressedInfo, + {levelWidth, levelHeight, levelDepth})); + } } // See TextureStorage1D. textureMipmapObject->TruncateMipmapLevels(textureUploadTarget, static_cast(levels)); diff --git a/MobileGL/MG_Test/Texture/TextureTest.cpp b/MobileGL/MG_Test/Texture/TextureTest.cpp index 8922040e..cbdf57af 100644 --- a/MobileGL/MG_Test/Texture/TextureTest.cpp +++ b/MobileGL/MG_Test/Texture/TextureTest.cpp @@ -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(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); +}