[Fix] (Texture): cube-map-array shape rules on glTexImage3D, GL_TEXTURE_SHARED_SIZE, and the unimplemented glCopyTexSubImage1D/3D

This commit is contained in:
2026-08-27 05:37:14 -04:00
parent 0e0882cfc6
commit 02fbb816e9
4 changed files with 258 additions and 24 deletions
+77 -24
View File
@@ -339,6 +339,11 @@ namespace MobileGL::MG_Impl::GLImpl {
return GetTextureComponentType(textureInternalFormat, componentSizes.Alpha, false, false); return GetTextureComponentType(textureInternalFormat, componentSizes.Alpha, false, false);
case GL_TEXTURE_DEPTH_TYPE: case GL_TEXTURE_DEPTH_TYPE:
return GetTextureComponentType(textureInternalFormat, componentSizes.Depth, true, false); return GetTextureComponentType(textureInternalFormat, componentSizes.Depth, true, false);
case GL_TEXTURE_SHARED_SIZE:
// GL 4.6 core table 8.24: the size in bits of the SHARED EXPONENT, which only the
// one shared-exponent format has. Everything else answers zero, and the
// conformance suite compares "at least", not "equal".
return textureInternalFormat == TextureInternalFormat::RGB9E5 ? 5 : 0;
default: default:
MOBILEGL_ASSERT(false, "Invalid texture level component pname: %d", pname); MOBILEGL_ASSERT(false, "Invalid texture level component pname: %d", pname);
return 0; return 0;
@@ -2214,6 +2219,7 @@ namespace MobileGL::MG_Impl::GLImpl {
if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return; if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return;
if (!TextureImpl::ValidateTextureLevelNumber(level)) return; if (!TextureImpl::ValidateTextureLevelNumber(level)) return;
if (!TextureImpl::ValidateTextureSizeWithTextureUploadTarget(textureUploadTarget, width, height)) return; if (!TextureImpl::ValidateTextureSizeWithTextureUploadTarget(textureUploadTarget, width, height)) return;
if (!TextureImpl::ValidateCubeMapArrayShape(textureUploadTarget, width, height, depth, __func__)) return;
if (!TextureImpl::ValidateTextureSizeRange(width, height, depth)) return; if (!TextureImpl::ValidateTextureSizeRange(width, height, depth)) return;
if (!TextureImpl::ValidateTextureInternalFormat(textureInternalFormat)) return; if (!TextureImpl::ValidateTextureInternalFormat(textureInternalFormat)) return;
if (!TextureImpl::ValidateTextureBorderNumber(border)) return; if (!TextureImpl::ValidateTextureBorderNumber(border)) return;
@@ -3307,6 +3313,7 @@ namespace MobileGL::MG_Impl::GLImpl {
case GL_TEXTURE_ALPHA_SIZE: case GL_TEXTURE_ALPHA_SIZE:
case GL_TEXTURE_DEPTH_SIZE: case GL_TEXTURE_DEPTH_SIZE:
case GL_TEXTURE_STENCIL_SIZE: case GL_TEXTURE_STENCIL_SIZE:
case GL_TEXTURE_SHARED_SIZE:
if (params) { if (params) {
*params = GetTextureLevelComponentParameter(textureObject->GetFormat(), pname); *params = GetTextureLevelComponentParameter(textureObject->GetFormat(), pname);
} }
@@ -3479,6 +3486,7 @@ namespace MobileGL::MG_Impl::GLImpl {
case GL_TEXTURE_ALPHA_SIZE: case GL_TEXTURE_ALPHA_SIZE:
case GL_TEXTURE_DEPTH_SIZE: case GL_TEXTURE_DEPTH_SIZE:
case GL_TEXTURE_STENCIL_SIZE: case GL_TEXTURE_STENCIL_SIZE:
case GL_TEXTURE_SHARED_SIZE:
if (params) { if (params) {
*params = static_cast<GLfloat>(GetTextureLevelComponentParameter(textureObject->GetFormat(), pname)); *params = static_cast<GLfloat>(GetTextureLevelComponentParameter(textureObject->GetFormat(), pname));
} }
@@ -3644,9 +3652,54 @@ namespace MobileGL::MG_Impl::GLImpl {
} }
} }
Bool ValidateCopyTextureSubImage(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject, GLint level,
GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height,
GLsizei depth, const char* caller);
// The shared body of glCopyTexSubImage3D and glCopyTextureSubImage3D once the caller has
// resolved the destination texture. `allowCubeFaceFromZOffset` is the ONE difference between
// the two forms: the DSA form takes a cube map and selects the face with zoffset (GL 4.6 core
// 8.6), while the target-taking form cannot even name a cube map here - GL_TEXTURE_CUBE_MAP is
// not in glCopyTexSubImage3D's accepted-target list, its faces go through
// glCopyTexSubImage2D - so for it zoffset is always a layer index.
static void CopyTextureSubImage3DResolved(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x,
GLint y, GLsizei width, GLsizei height, Bool allowCubeFaceFromZOffset,
const char* caller) {
if (!ValidateCopyTextureSubImage(textureObject, level, xoffset, yoffset, zoffset, width, height, 1, caller)) {
return;
}
TextureUploadTarget uploadTarget = GetPrimaryUploadTarget(textureObject);
GLint sliceOffset = zoffset;
if (allowCubeFaceFromZOffset && textureObject->GetTarget() == TextureTarget::TextureCubeMap) {
uploadTarget = static_cast<TextureUploadTarget>(
static_cast<SizeT>(TextureUploadTarget::CubeMapPositiveX) + static_cast<SizeT>(zoffset));
sliceOffset = 0;
}
CopyReadFramebufferIntoMipmapRegion(textureObject, uploadTarget, level, xoffset, yoffset, sliceOffset, x, y,
width, height, caller);
}
void CopyTexSubImage3D_State(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, void CopyTexSubImage3D_State(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x,
GLint y, GLsizei width, GLsizei height) { GLint y, GLsizei width, GLsizei height) {
// TODO: implement // GL 4.6 core 8.6 table: the three-dimensional form of the bound-texture copy accepts
// exactly TEXTURE_3D, TEXTURE_2D_ARRAY and TEXTURE_CUBE_MAP_ARRAY. A cube map's faces are
// two-dimensional targets of their own and go through glCopyTexSubImage2D.
const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
if (textureTarget != TextureTarget::Texture3D && textureTarget != TextureTarget::Texture2DArray &&
textureTarget != TextureTarget::TextureCubeMapArray) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
"glCopyTexSubImage3D requires GL_TEXTURE_3D, GL_TEXTURE_2D_ARRAY or "
"GL_TEXTURE_CUBE_MAP_ARRAY."));
return;
}
const auto textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
if (!textureObject) return;
CopyTextureSubImage3DResolved(textureObject, level, xoffset, yoffset, zoffset, x, y, width, height,
/*allowCubeFaceFromZOffset=*/false, __func__);
} }
// What the three CopyTextureSubImage forms check in common (GL 4.6 core 8.6), once the caller // What the three CopyTextureSubImage forms check in common (GL 4.6 core 8.6), once the caller
@@ -4012,7 +4065,22 @@ namespace MobileGL::MG_Impl::GLImpl {
} }
void CopyTexSubImage1D_State(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width) { void CopyTexSubImage1D_State(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width) {
// TODO: implement // The bound-texture form of glCopyTextureSubImage1D. GL 4.6 core 8.6 accepts only
// GL_TEXTURE_1D here.
const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
if (textureTarget != TextureTarget::Texture1D) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
"glCopyTexSubImage1D requires GL_TEXTURE_1D."));
return;
}
const auto textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
if (!textureObject) return;
if (!ValidateCopyTextureSubImage(textureObject, level, xoffset, 0, 0, width, 1, 1, __func__)) return;
CopyReadFramebufferIntoMipmapRegion(textureObject, GetPrimaryUploadTarget(textureObject), level, xoffset,
/*yoffset=*/0, /*zoffset=*/0, x, y, width, /*height=*/1, __func__);
} }
Bool CopyTexImage2D_State(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, Bool CopyTexImage2D_State(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
@@ -4468,6 +4536,7 @@ namespace MobileGL::MG_Impl::GLImpl {
if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return; if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return;
if (!TextureImpl::ValidateTextureLevelNumber(level)) return; if (!TextureImpl::ValidateTextureLevelNumber(level)) return;
if (!TextureImpl::ValidateTextureSizeWithTextureUploadTarget(textureUploadTarget, width, height)) return; if (!TextureImpl::ValidateTextureSizeWithTextureUploadTarget(textureUploadTarget, width, height)) return;
if (!TextureImpl::ValidateCubeMapArrayShape(textureUploadTarget, width, height, depth, __func__)) return;
if (!TextureImpl::ValidateTextureSizeRange(width, height, depth)) return; if (!TextureImpl::ValidateTextureSizeRange(width, height, depth)) return;
if (!TextureImpl::ValidateTextureBorderNumber(border)) return; if (!TextureImpl::ValidateTextureBorderNumber(border)) return;
if (!TextureImpl::ValidateTextureLevelWithUploadTarget(textureUploadTarget, level)) return; if (!TextureImpl::ValidateTextureLevelWithUploadTarget(textureUploadTarget, level)) return;
@@ -5206,17 +5275,11 @@ namespace MobileGL::MG_Impl::GLImpl {
return; return;
} }
if (!ValidateTextureMutable(textureObject, __func__)) return; if (!ValidateTextureMutable(textureObject, __func__)) return;
if (textureObject->GetTarget() == TextureTarget::TextureCubeMapArray &&
(width != height || depth % 6 != 0)) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>(
"MG_Impl/GLImpl", __func__,
"Cube map array immutable storage must be square with depth multiple of 6."));
return;
}
const auto textureUploadTarget = GetPrimaryUploadTarget(textureObject); const auto textureUploadTarget = GetPrimaryUploadTarget(textureObject);
// The cube-array shape rules, shared with glTexImage3D / glCompressedTexImage3D so the
// three cannot drift (they had: this check used to exist here and nowhere else).
if (!TextureImpl::ValidateCubeMapArrayShape(textureUploadTarget, width, height, depth, __func__)) return;
if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return; if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return;
auto* textureMipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get()); auto* textureMipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
@@ -6649,20 +6712,10 @@ namespace MobileGL::MG_Impl::GLImpl {
"cube map array texture.")); "cube map array texture."));
return; return;
} }
if (!ValidateCopyTextureSubImage(textureObject, level, xoffset, yoffset, zoffset, width, height, 1, __func__)) {
return;
}
// A cube map addresses its faces as separate upload targets, so zoffset selects the target // A cube map addresses its faces as separate upload targets, so zoffset selects the target
// rather than a slice within one; every other layered target keeps zoffset as the slice. // rather than a slice within one; every other layered target keeps zoffset as the slice.
TextureUploadTarget uploadTarget = GetPrimaryUploadTarget(textureObject); CopyTextureSubImage3DResolved(textureObject, level, xoffset, yoffset, zoffset, x, y, width, height,
GLint sliceOffset = zoffset; /*allowCubeFaceFromZOffset=*/true, __func__);
if (target == TextureTarget::TextureCubeMap) {
uploadTarget = static_cast<TextureUploadTarget>(
static_cast<SizeT>(TextureUploadTarget::CubeMapPositiveX) + static_cast<SizeT>(zoffset));
sliceOffset = 0;
}
CopyReadFramebufferIntoMipmapRegion(textureObject, uploadTarget, level, xoffset, yoffset, sliceOffset, x, y,
width, height, __func__);
} }
void CopyTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width) { void CopyTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width) {
@@ -103,6 +103,28 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl {
return true; return true;
} }
Bool ValidateCubeMapArrayShape(TextureUploadTarget target, GLsizei width, GLsizei height, GLsizei depth,
const char* caller) {
if (target != TextureUploadTarget::CubeMapArray && target != TextureUploadTarget::ProxyCubeMapArray) {
return true;
}
if (width != height) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
"Cube map array levels must be square (width == height)"));
return false;
}
if (depth % 6 != 0) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
"Cube map array depth must be a multiple of six"));
return false;
}
return true;
}
Bool ValidateTextureSizeWithTextureUploadTarget(TextureUploadTarget target, GLsizei width, GLsizei height) { Bool ValidateTextureSizeWithTextureUploadTarget(TextureUploadTarget target, GLsizei width, GLsizei height) {
if (target == TextureUploadTarget::CubeMapPositiveX || target == TextureUploadTarget::CubeMapNegativeX || if (target == TextureUploadTarget::CubeMapPositiveX || target == TextureUploadTarget::CubeMapNegativeX ||
target == TextureUploadTarget::CubeMapPositiveY || target == TextureUploadTarget::CubeMapNegativeY || target == TextureUploadTarget::CubeMapPositiveY || target == TextureUploadTarget::CubeMapNegativeY ||
@@ -20,6 +20,13 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl {
Bool ValidateTexturePixelDataType(TexturePixelDataType texturePixelDataType); Bool ValidateTexturePixelDataType(TexturePixelDataType texturePixelDataType);
Bool ValidateTextureLevelNumber(Int level); Bool ValidateTextureLevelNumber(Int level);
Bool ValidateTextureSizeWithTextureUploadTarget(TextureUploadTarget target, GLsizei width, GLsizei height); Bool ValidateTextureSizeWithTextureUploadTarget(TextureUploadTarget target, GLsizei width, GLsizei height);
// The two shape rules a cube-map-array level owes (GL 4.6 core 8.5): its faces are square, and
// its depth counts whole cubes. Both are GL_INVALID_VALUE. This used to be spelled inline in
// glTexStorage3D only, which is why glTexImage3D let both violations through - every entry
// point that DEFINES a cube-array level calls this now, so the two cannot drift again. A
// non-cube-array upload target answers true untouched.
Bool ValidateCubeMapArrayShape(TextureUploadTarget target, GLsizei width, GLsizei height, GLsizei depth,
const char* caller);
Bool ValidateTextureSizeRange(Int width, Int height, Int depth); Bool ValidateTextureSizeRange(Int width, Int height, Int depth);
Bool ValidateTextureInternalFormat(TextureInternalFormat format); Bool ValidateTextureInternalFormat(TextureInternalFormat format);
Bool ValidateTextureBorderNumber(Int border); Bool ValidateTextureBorderNumber(Int border);
+152
View File
@@ -5545,3 +5545,155 @@ TEST_F(TextureTest, ImageWidenedUploadSplitsAPacked2101010RevShadowIntoFourChann
EXPECT_TRUE(empty.empty()); EXPECT_TRUE(empty.empty());
} }
} }
// ---------------------------------------------------------------------------------------------
// GL_TEXTURE_CUBE_MAP_ARRAY: the shape rules, the shared-exponent level query, and the
// three-dimensional bound-texture copy. All three were front-end gaps rather than backend ones -
// the DirectVulkan baseline failed the identical conformance bodies.
// ---------------------------------------------------------------------------------------------
// glTexStorage3D carried the two cube-array shape rules inline and glTexImage3D carried neither,
// which is exactly why esextcTextureCubeMapArrayTex3DValidation failed on its two glTexImage3D
// assertions and passed both glTexStorage3D ones. The predicate now lives in one validator that
// every level-defining entry point calls.
TEST_F(TextureTest, TexImage3DAppliesTheCubeMapArrayShapeRules) {
GLuint texture = 0;
MG_Impl::GLImpl::GenTextures(1, &texture);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, texture);
DrainPendingGlErrors();
// Non-square faces are GL_INVALID_VALUE.
MG_Impl::GLImpl::TexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, GL_RGBA8, 4, 8, 6, 0, GL_RGBA, GL_UNSIGNED_BYTE,
nullptr);
ExpectSingleGlError(GL_INVALID_VALUE);
// A depth that is not a whole number of cubes is GL_INVALID_VALUE.
MG_Impl::GLImpl::TexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, GL_RGBA8, 4, 4, 5, 0, GL_RGBA, GL_UNSIGNED_BYTE,
nullptr);
ExpectSingleGlError(GL_INVALID_VALUE);
// The legal shape still goes through untouched.
MG_Impl::GLImpl::TexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, GL_RGBA8, 4, 4, 12, 0, GL_RGBA, GL_UNSIGNED_BYTE,
nullptr);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
// And the rules are not applied to targets they do not belong to: a 2D array may be any
// rectangle with any layer count.
GLuint arrayTexture = 0;
MG_Impl::GLImpl::GenTextures(1, &arrayTexture);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D_ARRAY, arrayTexture);
MG_Impl::GLImpl::TexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 4, 8, 5, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D_ARRAY, 0);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, 0);
}
TEST_F(TextureTest, TexStorage3DKeepsTheCubeMapArrayShapeRulesAfterTheyMovedIntoTheSharedValidator) {
GLuint texture = 0;
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_CUBE_MAP_ARRAY, 1, &texture);
DrainPendingGlErrors();
MG_Impl::GLImpl::TextureStorage3D(texture, 1, GL_RGBA8, 4, 8, 6);
ExpectSingleGlError(GL_INVALID_VALUE);
GLuint second = 0;
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_CUBE_MAP_ARRAY, 1, &second);
MG_Impl::GLImpl::TextureStorage3D(second, 1, GL_RGBA8, 4, 4, 5);
ExpectSingleGlError(GL_INVALID_VALUE);
GLuint third = 0;
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_CUBE_MAP_ARRAY, 1, &third);
MG_Impl::GLImpl::TextureStorage3D(third, 1, GL_RGBA8, 4, 4, 6);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
}
// GL_TEXTURE_SHARED_SIZE (0x8C3F) had no case in either glGetTexLevelParameter switch, so it fell
// into the terminal default arm and raised GL_INVALID_ENUM. esextcTextureCubeMapArrayGetterCalls
// walks a fixed pname list and TCU_FAILs on the first error, so the whole body died there even
// though every other pname it asks for was already implemented.
TEST_F(TextureTest, GetTexLevelParameterAnswersSharedSize) {
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_RGBA8, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
DrainPendingGlErrors();
GLint sharedSize = -1;
MG_Impl::GLImpl::GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_SHARED_SIZE, &sharedSize);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
EXPECT_EQ(sharedSize, 0) << "only a shared-exponent format has a shared exponent";
GLfloat sharedSizeF = -1.0f;
MG_Impl::GLImpl::GetTexLevelParameterfv(GL_TEXTURE_2D, 0, GL_TEXTURE_SHARED_SIZE, &sharedSizeF);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
EXPECT_FLOAT_EQ(sharedSizeF, 0.0f) << "the fv switch is a copy of the iv one and must not drift";
// RGB9_E5 is the one format that HAS one, and it is five bits wide.
GLuint sharedTexture = 0;
MG_Impl::GLImpl::GenTextures(1, &sharedTexture);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, sharedTexture);
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGB9_E5, 4, 4, 0, GL_RGB, GL_FLOAT, nullptr);
DrainPendingGlErrors();
sharedSize = -1;
MG_Impl::GLImpl::GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_SHARED_SIZE, &sharedSize);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
EXPECT_EQ(sharedSize, 5);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, 0);
}
// glCopyTexSubImage3D was `{ // TODO: implement }` - no validation, no error, no copy - while its
// DSA sibling was fully implemented right next door. The two now share one body, so the target
// rules are the only thing that separates them.
TEST_F(TextureTest, CopyTexSubImage3DRejectsTargetsTheThreeDimensionalFormDoesNotTake) {
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_RGBA8, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
DrainPendingGlErrors();
// GL 4.6 core 8.6: the 3D form takes only TEXTURE_3D / TEXTURE_2D_ARRAY /
// TEXTURE_CUBE_MAP_ARRAY. A cube map's faces are two-dimensional targets and go through
// glCopyTexSubImage2D. This used to be accepted silently, which is how the defect hid.
MG_Impl::GLImpl::CopyTexSubImage3D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 0, 2, 2);
ExpectSingleGlError(GL_INVALID_ENUM);
MG_Impl::GLImpl::CopyTexSubImage3D(GL_TEXTURE_CUBE_MAP, 0, 0, 0, 0, 0, 0, 2, 2);
ExpectSingleGlError(GL_INVALID_ENUM);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, 0);
}
TEST_F(TextureTest, CopyTexSubImage3DValidatesTheDestinationRegionOnACubeMapArray) {
GLuint texture = 0;
MG_Impl::GLImpl::GenTextures(1, &texture);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, texture);
MG_Impl::GLImpl::TexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, GL_RGBA8, 4, 4, 6, 0, GL_RGBA, GL_UNSIGNED_BYTE,
nullptr);
DrainPendingGlErrors();
// A negative level is GL_INVALID_VALUE, and reaching it at all proves the entry point now
// validates instead of returning silently.
MG_Impl::GLImpl::CopyTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, -1, 0, 0, 0, 0, 0, 2, 2);
ExpectSingleGlError(GL_INVALID_VALUE);
// So is a negative extent.
MG_Impl::GLImpl::CopyTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, 0, 0, 0, 0, 0, -2, 2);
ExpectSingleGlError(GL_INVALID_VALUE);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, 0);
}
TEST_F(TextureTest, CopyTexSubImage1DRejectsAnythingButTexture1D) {
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_RGBA8, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
DrainPendingGlErrors();
MG_Impl::GLImpl::CopyTexSubImage1D(GL_TEXTURE_2D, 0, 0, 0, 0, 2);
ExpectSingleGlError(GL_INVALID_ENUM);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, 0);
}