diff --git a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp index c690d986..9009930b 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp +++ b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp @@ -4303,8 +4303,8 @@ namespace MobileGL::MG_Impl::GLImpl { } // Shared format/type/internal-format matrix (packed-type pairing, depth-vs-color mismatch, - // integer-ness). Also rejects STENCIL_INDEX readback, which needs GL_ARB_texture_stencil8 - // (not advertised by MobileGL). + // integer-ness). Also rejects a STENCIL_INDEX readback of anything but stencil-only + // storage, which is the only pairing GL 4.4 / ARB_texture_stencil8 ever made legal. if (!TextureImpl::ValidateTextureInternalFormatCompatibleWithInput( textureInputFormat, textureObject->GetFormat(), texturePixelDataType)) { return false; diff --git a/MobileGL/MG_Impl/GLImpl/Texture/Validators.cpp b/MobileGL/MG_Impl/GLImpl/Texture/Validators.cpp index 1b1dd6f2..ec423efb 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/Validators.cpp +++ b/MobileGL/MG_Impl/GLImpl/Texture/Validators.cpp @@ -313,9 +313,13 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl { return false; } - // TexImage in core 3.3 has no stencil-only upload path (that arrived with GL 4.4). - if (format == TextureInputFormat::StencilIndex) { - return recordInvalidOperation("STENCIL_INDEX is not a valid texture upload format"); + // The stencil-only transfer path arrived with GL 4.4 / ARB_texture_stencil8, and only ever + // pairs with stencil-only storage: against a depth, depth-stencil or colour internal format + // STENCIL_INDEX keeps the pre-4.4 answer (GL CTS packed_pixels feeds exactly that pairing + // and expects INVALID_OPERATION). + if (format == TextureInputFormat::StencilIndex && + internalFormat != TextureInternalFormat::StencilIndex8) { + return recordInvalidOperation("STENCIL_INDEX requires a stencil-only internal format"); } if (IsDepthLikeInputFormat(format) != IsDepthLikeInternalFormat(internalFormat)) { diff --git a/MobileGL/MG_Test/Texture/TextureTest.cpp b/MobileGL/MG_Test/Texture/TextureTest.cpp index b663b3f7..953a8929 100644 --- a/MobileGL/MG_Test/Texture/TextureTest.cpp +++ b/MobileGL/MG_Test/Texture/TextureTest.cpp @@ -1057,6 +1057,32 @@ TEST_F(TextureTest, TexImage2DAcceptsSpecCompliantFormatCombinations) { EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); } +// GL_STENCIL_INDEX is the unsized base format for stencil-only storage, and refusing it as an +// internal format killed the ARB_clear_texture stencil case in its own setup - before it could +// reach the calls it actually tests. The stencil-only transfer format stays paired with +// stencil-only storage in both directions, which is what keeps those clears erroring. +TEST_F(TextureTest, StencilIndexIsATextureInternalFormatPairedOnlyWithStencilStorage) { + 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, GL_STENCIL_INDEX, 4, 4, 0, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, + nullptr); + EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); + + const auto textureObject = MG_State::pGLContext->GetTextureObject(texture); + ASSERT_NE(textureObject, nullptr); + EXPECT_EQ(textureObject->GetFormat(), TextureInternalFormat::StencilIndex8); + + // A colour transfer format against stencil storage is still INVALID_OPERATION, so the clear + // the conformance case makes next fails the way it is supposed to. + MG_Impl::GLImpl::ClearTexImage(texture, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + ExpectSingleGlError(GL_INVALID_OPERATION); + + // ...and the other direction: GL_STENCIL_INDEX against colour storage stays illegal. + MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 4, 4, 0, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, nullptr); + ExpectSingleGlError(GL_INVALID_OPERATION); +} + // Desktop GL table 3.3 lists GREEN and BLUE as TexImage client formats (GL CTS packed_pixels // rgba8_format_green/blue upload with them and verify the readback): the single input component // feeds the named channel, the other color channels default to 0 and alpha to 1. diff --git a/MobileGL/MG_Util/Converters/GLToMG/TextureEnumConverter.cpp b/MobileGL/MG_Util/Converters/GLToMG/TextureEnumConverter.cpp index 332a3cd4..8d6fb042 100644 --- a/MobileGL/MG_Util/Converters/GLToMG/TextureEnumConverter.cpp +++ b/MobileGL/MG_Util/Converters/GLToMG/TextureEnumConverter.cpp @@ -253,6 +253,12 @@ namespace MobileGL { return TextureInternalFormat::Depth32FStencil8; case GL_STENCIL_INDEX8: return TextureInternalFormat::StencilIndex8; + // The unsized stencil base format resolves to the only stencil storage there is, the + // same way the unsized colour and depth base formats below resolve to theirs. Returning + // Unknown made glTexImage2D(GL_STENCIL_INDEX) an error, which killed the negative + // clear-texture cases in their own setup before they could reach the call they test. + case GL_STENCIL_INDEX: + return TextureInternalFormat::StencilIndex8; case GL_DEPTH_COMPONENT: return TextureInternalFormat::DepthComponent; case GL_DEPTH_STENCIL: diff --git a/MobileGL/MG_Util/Converters/MGToMG/TextureEnumConverter.cpp b/MobileGL/MG_Util/Converters/MGToMG/TextureEnumConverter.cpp index 4564d155..f75bfd22 100644 --- a/MobileGL/MG_Util/Converters/MGToMG/TextureEnumConverter.cpp +++ b/MobileGL/MG_Util/Converters/MGToMG/TextureEnumConverter.cpp @@ -124,6 +124,9 @@ namespace MobileGL { case TextureInternalFormat::DepthComponent32F: case TextureInternalFormat::Depth24Stencil8: case TextureInternalFormat::Depth32FStencil8: + // Already sized: both GL_STENCIL_INDEX8 and the unsized GL_STENCIL_INDEX resolve here, + // and there is only one stencil storage to infer. + case TextureInternalFormat::StencilIndex8: return internalformat; // probably we should assume unorm here? case TextureInternalFormat::RGBA: { diff --git a/MobileGL/MG_Util/Texture/PixelStoreProcessor.cpp b/MobileGL/MG_Util/Texture/PixelStoreProcessor.cpp index 099f49b3..62c633ba 100644 --- a/MobileGL/MG_Util/Texture/PixelStoreProcessor.cpp +++ b/MobileGL/MG_Util/Texture/PixelStoreProcessor.cpp @@ -138,6 +138,11 @@ namespace MobileGL::MG_Util::PixelStoreProcessor { case TextureInternalFormat::DepthComponent32F: out = {1, ShadowComponent::Float32, false}; return true; + // Stencil is the one single-channel INTEGER shadow that is not a colour format: eight + // bits, held as an unsigned index rather than a normalized value. + case TextureInternalFormat::StencilIndex8: + out = {1, ShadowComponent::UInt8, true}; + return true; case TextureInternalFormat::R8: case TextureInternalFormat::Red: out = {1, ShadowComponent::UNorm8, false}; return true; @@ -336,8 +341,13 @@ namespace MobileGL::MG_Util::PixelStoreProcessor { case TextureInputFormat::BGRAInteger: out = {{2, 1, 0, 3}, 4, true}; return true; // A depth value converts like a single normalized/float channel. case TextureInputFormat::DepthComponent: out = {{0, -1, -1, -1}, 1, false}; return true; + // A stencil index is a single INTEGER channel (GL 4.6 core 8.4.4.3). Without this the + // upload fell to the raw-memcpy branch, which copies the client element width into the + // one-byte STENCIL_INDEX8 shadow verbatim - right for GL_UNSIGNED_BYTE and wrong for + // every wider type. The state layer keeps this paired with stencil-only storage. + case TextureInputFormat::StencilIndex: out = {{0, -1, -1, -1}, 1, true}; return true; default: - return false; // stencil / packed depth-stencil / unknown + return false; // packed depth-stencil / unknown } } @@ -998,6 +1008,11 @@ namespace MobileGL::MG_Util::PixelStoreProcessor { const void* inputPixel, Vector& outputPixel) { outputPixel.clear(); + // A stencil index became a transferable format when STENCIL_INDEX8 texture storage did (see + // GetUnpackChannelMapping), but this helper serves glClearBufferData, whose internal formats + // are all colour (GL 4.6 core table 8.20): a stencil pattern would otherwise pass the size + // check and land silently in an equally-sized colour store. + if (textureInputFormat == TextureInputFormat::StencilIndex) return false; if (inputPixel == nullptr || !IsValidUnpackPixelPair(textureInputFormat, inputDataType)) return false; PixelStoreParameters params{};