diff --git a/MobileGL/MG_Test/Texture/TextureTest.cpp b/MobileGL/MG_Test/Texture/TextureTest.cpp index 9a6d58e2..5c9e9baa 100644 --- a/MobileGL/MG_Test/Texture/TextureTest.cpp +++ b/MobileGL/MG_Test/Texture/TextureTest.cpp @@ -1516,6 +1516,47 @@ TEST_F(TextureTest, TexStorage2DTrimsALongerPreExistingMipChain) { EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); } +// glTexImage2D used to reject every GL_COMPRESSED_* internal format with GL_INVALID_ENUM, because +// none of them mapped to a TextureInternalFormat and the "unknown format" gate fired. They now +// resolve to the uncompressed storage that backs them - what GL prescribes for the generic formats, +// and a deliberate deviation for RGTC, which ES cannot compress. The (format, type) pairs below are +// the ones KHR-GL33.packed_pixels uploads with, so this table doubles as a pin for those 480 cases. +TEST_F(TextureTest, CompressedInternalFormatsResolveToTheirUncompressedStorage) { + struct Case { + GLenum internalFormat; + GLenum format; + GLenum type; + TextureInternalFormat expected; + }; + const Case cases[] = { + {GL_COMPRESSED_RED, GL_RED, GL_UNSIGNED_BYTE, TextureInternalFormat::R8}, + {GL_COMPRESSED_RG, GL_RG, GL_UNSIGNED_BYTE, TextureInternalFormat::RG8}, + {GL_COMPRESSED_RGB, GL_RGB, GL_UNSIGNED_BYTE, TextureInternalFormat::RGB8}, + {GL_COMPRESSED_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, TextureInternalFormat::RGBA8}, + {GL_COMPRESSED_SRGB, GL_RGB, GL_UNSIGNED_BYTE, TextureInternalFormat::SRGB8}, + {GL_COMPRESSED_SRGB_ALPHA, GL_RGBA, GL_UNSIGNED_BYTE, TextureInternalFormat::SRGB8Alpha8}, + {GL_COMPRESSED_RED_RGTC1, GL_RED, GL_UNSIGNED_BYTE, TextureInternalFormat::R8}, + {GL_COMPRESSED_RG_RGTC2, GL_RG, GL_UNSIGNED_BYTE, TextureInternalFormat::RG8}, + // The signed RGTC pair is uploaded as GL_BYTE and must land on SNORM storage - resolving + // them to plain R8/RG8 would silently reinterpret negative texels. + {GL_COMPRESSED_SIGNED_RED_RGTC1, GL_RED, GL_BYTE, TextureInternalFormat::R8Snorm}, + {GL_COMPRESSED_SIGNED_RG_RGTC2, GL_RG, GL_BYTE, TextureInternalFormat::RG8Snorm}, + }; + + MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 1); + for (const auto& c : cases) { + GLuint texture = 0; + MG_Impl::GLImpl::GenTextures(1, &texture); + MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture); + MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, c.internalFormat, 4, 4, 0, c.format, c.type, nullptr); + + const auto textureObject = MG_State::pGLContext->GetTextureObject(texture); + ASSERT_NE(textureObject, nullptr) << "internalFormat 0x" << std::hex << c.internalFormat; + EXPECT_EQ(textureObject->GetFormat(), c.expected) << "internalFormat 0x" << std::hex << c.internalFormat; + EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << "internalFormat 0x" << std::hex << c.internalFormat; + } +} + TEST_F(TextureTest, TextureStorage3DAndSubImageModifyNamedObjectOnly) { GLuint texture = 0; MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_3D, 1, &texture); diff --git a/MobileGL/MG_Util/Converters/GLToMG/TextureEnumConverter.cpp b/MobileGL/MG_Util/Converters/GLToMG/TextureEnumConverter.cpp index 6d3e1d68..48ace6bd 100644 --- a/MobileGL/MG_Util/Converters/GLToMG/TextureEnumConverter.cpp +++ b/MobileGL/MG_Util/Converters/GLToMG/TextureEnumConverter.cpp @@ -255,6 +255,37 @@ namespace MobileGL { return TextureInternalFormat::DepthComponent; case GL_DEPTH_STENCIL: return TextureInternalFormat::DepthStencil; + // Compressed internal formats resolve to the uncompressed storage that backs them. + // + // For the six generic formats this is exactly what GL prescribes: the implementation + // picks a specific compressed format, and when none is available it falls back to the + // corresponding base format. Nothing downstream ever sees a compressed enum, so the + // metrics, pixel-store and backend tables keep their "one format, N bytes per texel" + // invariant instead of each needing a compressed-aware arm. + // + // The four RGTC formats are a deliberate deviation: they are specific formats that GL + // 3.3 requires, but ES exposes no RGTC compressor to hand the data to. Storing the + // texels uncompressed keeps them renderable at the cost of the memory saving, which is + // strictly better than the INVALID_ENUM the application used to get. Note the signed + // variants must land on SNORM storage - CTS uploads them as GL_BYTE. + case GL_COMPRESSED_RED: + case GL_COMPRESSED_RED_RGTC1: + return TextureInternalFormat::R8; + case GL_COMPRESSED_SIGNED_RED_RGTC1: + return TextureInternalFormat::R8Snorm; + case GL_COMPRESSED_RG: + case GL_COMPRESSED_RG_RGTC2: + return TextureInternalFormat::RG8; + case GL_COMPRESSED_SIGNED_RG_RGTC2: + return TextureInternalFormat::RG8Snorm; + case GL_COMPRESSED_RGB: + return TextureInternalFormat::RGB8; + case GL_COMPRESSED_RGBA: + return TextureInternalFormat::RGBA8; + case GL_COMPRESSED_SRGB: + return TextureInternalFormat::SRGB8; + case GL_COMPRESSED_SRGB_ALPHA: + return TextureInternalFormat::SRGB8Alpha8; case GL_ALPHA: case GL_RED: return TextureInternalFormat::Red;