[Fix, Test] (TextureUtil, GLImpl): accept GL_STENCIL_INDEX as a stencil-only texture internal format

This commit is contained in:
2026-08-20 10:49:57 -04:00
parent a8bebe1a3c
commit 042c61fb75
6 changed files with 60 additions and 6 deletions
@@ -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;
@@ -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)) {
+26
View File
@@ -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.
@@ -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:
@@ -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: {
@@ -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<Uint8>& 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{};