[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);
case GL_TEXTURE_DEPTH_TYPE:
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:
MOBILEGL_ASSERT(false, "Invalid texture level component pname: %d", pname);
return 0;
@@ -2214,6 +2219,7 @@ namespace MobileGL::MG_Impl::GLImpl {
if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return;
if (!TextureImpl::ValidateTextureLevelNumber(level)) 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::ValidateTextureInternalFormat(textureInternalFormat)) return;
if (!TextureImpl::ValidateTextureBorderNumber(border)) return;
@@ -3307,6 +3313,7 @@ namespace MobileGL::MG_Impl::GLImpl {
case GL_TEXTURE_ALPHA_SIZE:
case GL_TEXTURE_DEPTH_SIZE:
case GL_TEXTURE_STENCIL_SIZE:
case GL_TEXTURE_SHARED_SIZE:
if (params) {
*params = GetTextureLevelComponentParameter(textureObject->GetFormat(), pname);
}
@@ -3479,6 +3486,7 @@ namespace MobileGL::MG_Impl::GLImpl {
case GL_TEXTURE_ALPHA_SIZE:
case GL_TEXTURE_DEPTH_SIZE:
case GL_TEXTURE_STENCIL_SIZE:
case GL_TEXTURE_SHARED_SIZE:
if (params) {
*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,
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
@@ -4012,7 +4065,22 @@ namespace MobileGL::MG_Impl::GLImpl {
}
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,
@@ -4468,6 +4536,7 @@ namespace MobileGL::MG_Impl::GLImpl {
if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return;
if (!TextureImpl::ValidateTextureLevelNumber(level)) 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::ValidateTextureBorderNumber(border)) return;
if (!TextureImpl::ValidateTextureLevelWithUploadTarget(textureUploadTarget, level)) return;
@@ -5206,17 +5275,11 @@ namespace MobileGL::MG_Impl::GLImpl {
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);
// 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;
auto* textureMipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
@@ -6649,20 +6712,10 @@ namespace MobileGL::MG_Impl::GLImpl {
"cube map array texture."));
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
// rather than a slice within one; every other layered target keeps zoffset as the slice.
TextureUploadTarget uploadTarget = GetPrimaryUploadTarget(textureObject);
GLint sliceOffset = zoffset;
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__);
CopyTextureSubImage3DResolved(textureObject, level, xoffset, yoffset, zoffset, x, y, width, height,
/*allowCubeFaceFromZOffset=*/true, __func__);
}
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;
}
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) {
if (target == TextureUploadTarget::CubeMapPositiveX || target == TextureUploadTarget::CubeMapNegativeX ||
target == TextureUploadTarget::CubeMapPositiveY || target == TextureUploadTarget::CubeMapNegativeY ||
@@ -20,6 +20,13 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl {
Bool ValidateTexturePixelDataType(TexturePixelDataType texturePixelDataType);
Bool ValidateTextureLevelNumber(Int level);
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 ValidateTextureInternalFormat(TextureInternalFormat format);
Bool ValidateTextureBorderNumber(Int border);