[Fix, Feat, Test] (MG_Impl, MG_Test): a specific compressed internalformat now tags the level it defines, and the compressed sub-image entry points stopped being stubs

This commit is contained in:
2026-08-12 08:52:01 -04:00
parent 0e31c1481b
commit 9b0ed5b3af
4 changed files with 430 additions and 10 deletions
@@ -1061,7 +1061,7 @@ DECLARE_GL_FUNCTION_HEAD(void, TextureSubImage1D, GLuint texture, GLint level, G
DECLARE_GL_FUNCTION_HEAD(void, TextureSubImage2D, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void* pixels) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureSubImage2D, texture, level, xoffset, yoffset, width, height, format, type, pixels) DECLARE_GL_FUNCTION_HEAD(void, TextureSubImage2D, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void* pixels) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureSubImage2D, texture, level, xoffset, yoffset, width, height, format, type, pixels)
DECLARE_GL_FUNCTION_HEAD(void, TextureSubImage3D, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* pixels) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureSubImage3D, texture, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels) DECLARE_GL_FUNCTION_HEAD(void, TextureSubImage3D, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* pixels) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureSubImage3D, texture, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels)
DECLARE_GL_FUNCTION_STUB_HEAD(void, CompressedTextureSubImage1D, GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CompressedTextureSubImage1D, texture, level, xoffset, width, format, imageSize, data) DECLARE_GL_FUNCTION_STUB_HEAD(void, CompressedTextureSubImage1D, GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CompressedTextureSubImage1D, texture, level, xoffset, width, format, imageSize, data)
DECLARE_GL_FUNCTION_STUB_HEAD(void, CompressedTextureSubImage2D, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CompressedTextureSubImage2D, texture, level, xoffset, yoffset, width, height, format, imageSize, data) DECLARE_GL_FUNCTION_HEAD(void, CompressedTextureSubImage2D, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CompressedTextureSubImage2D, texture, level, xoffset, yoffset, width, height, format, imageSize, data)
DECLARE_GL_FUNCTION_STUB_HEAD(void, CompressedTextureSubImage3D, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CompressedTextureSubImage3D, texture, level, xoffset, yoffset, zoffset, width, height, depth, format, imageSize, data) DECLARE_GL_FUNCTION_STUB_HEAD(void, CompressedTextureSubImage3D, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CompressedTextureSubImage3D, texture, level, xoffset, yoffset, zoffset, width, height, depth, format, imageSize, data)
DECLARE_GL_FUNCTION_HEAD(void, CopyTextureSubImage1D, GLuint texture, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CopyTextureSubImage1D, texture, level, xoffset, x, y, width) DECLARE_GL_FUNCTION_HEAD(void, CopyTextureSubImage1D, GLuint texture, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CopyTextureSubImage1D, texture, level, xoffset, x, y, width)
DECLARE_GL_FUNCTION_HEAD(void, CopyTextureSubImage2D, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CopyTextureSubImage2D, texture, level, xoffset, yoffset, x, y, width, height) DECLARE_GL_FUNCTION_HEAD(void, CopyTextureSubImage2D, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CopyTextureSubImage2D, texture, level, xoffset, yoffset, x, y, width, height)
@@ -1849,7 +1849,7 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, CompressedTextureImage3DEXT, GLuint texture,
DECLARE_GL_FUNCTION_STUB_HEAD(void, CompressedTextureImage2DEXT, GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void* bits) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CompressedTextureImage2DEXT, texture, target, level, internalformat, width, height, border, imageSize, bits) DECLARE_GL_FUNCTION_STUB_HEAD(void, CompressedTextureImage2DEXT, GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void* bits) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CompressedTextureImage2DEXT, texture, target, level, internalformat, width, height, border, imageSize, bits)
DECLARE_GL_FUNCTION_STUB_HEAD(void, CompressedTextureImage1DEXT, GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void* bits) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CompressedTextureImage1DEXT, texture, target, level, internalformat, width, border, imageSize, bits) DECLARE_GL_FUNCTION_STUB_HEAD(void, CompressedTextureImage1DEXT, GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void* bits) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CompressedTextureImage1DEXT, texture, target, level, internalformat, width, border, imageSize, bits)
DECLARE_GL_FUNCTION_STUB_HEAD(void, CompressedTextureSubImage3DEXT, GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void* bits) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CompressedTextureSubImage3DEXT, texture, target, level, xoffset, yoffset, zoffset, width, height, depth, format, imageSize, bits) DECLARE_GL_FUNCTION_STUB_HEAD(void, CompressedTextureSubImage3DEXT, GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void* bits) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CompressedTextureSubImage3DEXT, texture, target, level, xoffset, yoffset, zoffset, width, height, depth, format, imageSize, bits)
DECLARE_GL_FUNCTION_STUB_HEAD(void, CompressedTextureSubImage2DEXT, GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void* bits) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CompressedTextureSubImage2DEXT, texture, target, level, xoffset, yoffset, width, height, format, imageSize, bits) DECLARE_GL_FUNCTION_HEAD(void, CompressedTextureSubImage2DEXT, GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void* bits) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CompressedTextureSubImage2D, texture, level, xoffset, yoffset, width, height, format, imageSize, bits)
DECLARE_GL_FUNCTION_STUB_HEAD(void, CompressedTextureSubImage1DEXT, GLuint texture, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void* bits) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CompressedTextureSubImage1DEXT, texture, target, level, xoffset, width, format, imageSize, bits) DECLARE_GL_FUNCTION_STUB_HEAD(void, CompressedTextureSubImage1DEXT, GLuint texture, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void* bits) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CompressedTextureSubImage1DEXT, texture, target, level, xoffset, width, format, imageSize, bits)
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetCompressedTextureImageEXT, GLuint texture, GLenum target, GLint lod, void* img) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetCompressedTextureImageEXT, texture, target, lod, img) DECLARE_GL_FUNCTION_STUB_HEAD(void, GetCompressedTextureImageEXT, GLuint texture, GLenum target, GLint lod, void* img) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetCompressedTextureImageEXT, texture, target, lod, img)
DECLARE_GL_FUNCTION_STUB_HEAD(void, CompressedMultiTexImage3DEXT, GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void* bits) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CompressedMultiTexImage3DEXT, texunit, target, level, internalformat, width, height, depth, border, imageSize, bits) DECLARE_GL_FUNCTION_STUB_HEAD(void, CompressedMultiTexImage3DEXT, GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void* bits) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CompressedMultiTexImage3DEXT, texunit, target, level, internalformat, width, height, depth, border, imageSize, bits)
+193 -8
View File
@@ -2238,6 +2238,23 @@ namespace MobileGL::MG_Impl::GLImpl {
DiscardMipmapChainOnBaseRespecification(textureMipmapObject, textureUploadTarget, level); DiscardMipmapChainOnBaseRespecification(textureMipmapObject, textureUploadTarget, level);
textureMipmapObject->AllocateStorage(textureUploadTarget, level, textureMipmapObject->AllocateStorage(textureUploadTarget, level,
{{width, height, 1}, internalBytes}); {{width, height, 1}, internalBytes});
// GL 4.6 core 8.5: a SPECIFIC compressed internalformat (unlike a generic
// GL_COMPRESSED_* one, where the implementation is free to choose) commits the
// level to that format - GL_TEXTURE_COMPRESSED must then answer true for it and
// GL_TEXTURE_INTERNAL_FORMAT must report it, which is how an application asks for
// the size to hand glCompressedTexSubImage2D afterwards. Only the tag and the size
// are recorded: there is no BC/ETC codec here, so the texel shadow keeps the
// uncompressed storage this format resolved to (which is also what lets the level
// sample as the application's texels), and the compressed image the tag describes
// is zero-filled - the one reproducible answer glGetCompressedTexImage can give for
// an image nothing ever compressed. AllocateStorage above clears the tag, so this
// has to follow it.
const auto compressedInfo = MG_Util::GetCompressedFormatInfo(static_cast<GLenum>(internalformat));
if (compressedInfo.blockWidth != 0) {
textureMipmapObject->SetMipmapCompressedImage(
textureUploadTarget, level, static_cast<GLenum>(internalformat), nullptr,
MG_Util::CalculateCompressedTextureImageSize(compressedInfo, {width, height, 1}));
}
} }
if (!originalPixels) { if (!originalPixels) {
@@ -2965,9 +2982,9 @@ namespace MobileGL::MG_Impl::GLImpl {
case GL_TEXTURE_INTERNAL_FORMAT: case GL_TEXTURE_INTERNAL_FORMAT:
if (params) { if (params) {
// A level stored compressed must report the token it was given, not the // A level stored compressed must report the token it was given, not the
// uncompressed format backing it (GL 4.6 core 8.11). Only glCompressedTexImage* sets // uncompressed format backing it (GL 4.6 core 8.11). glCompressedTexImage2D sets
// that tag, so every level created by glTexImage*D - including one given a compressed // that tag, and so does a glTexImage2D given a SPECIFIC compressed internalformat;
// internalformat - still answers with its resolved storage format. // every other level answers with its resolved storage format.
const GLenum compressedFormat = GetCompressedLevelFormat(textureObject, textureUploadTarget, level); const GLenum compressedFormat = GetCompressedLevelFormat(textureObject, textureUploadTarget, level);
*params = (compressedFormat != GL_NONE) *params = (compressedFormat != GL_NONE)
? (GLint)compressedFormat ? (GLint)compressedFormat
@@ -3103,9 +3120,9 @@ namespace MobileGL::MG_Impl::GLImpl {
case GL_TEXTURE_INTERNAL_FORMAT: case GL_TEXTURE_INTERNAL_FORMAT:
if (params) { if (params) {
// A level stored compressed must report the token it was given, not the // A level stored compressed must report the token it was given, not the
// uncompressed format backing it (GL 4.6 core 8.11). Only glCompressedTexImage* sets // uncompressed format backing it (GL 4.6 core 8.11). glCompressedTexImage2D sets
// that tag, so every level created by glTexImage*D - including one given a compressed // that tag, and so does a glTexImage2D given a SPECIFIC compressed internalformat;
// internalformat - still answers with its resolved storage format. // every other level answers with its resolved storage format.
const GLenum compressedFormat = GetCompressedLevelFormat(textureObject, textureUploadTarget, level); const GLenum compressedFormat = GetCompressedLevelFormat(textureObject, textureUploadTarget, level);
*params = (GLfloat)((compressedFormat != GL_NONE) *params = (GLfloat)((compressedFormat != GL_NONE)
? compressedFormat ? compressedFormat
@@ -3468,10 +3485,170 @@ namespace MobileGL::MG_Impl::GLImpl {
RecordUnsupportedCompressedFormat(__func__); RecordUnsupportedCompressedFormat(__func__);
} }
// Replaces a block-aligned rectangle of the compressed image glCompressedTexImage2D (or a
// compressed glTexImage2D) shadowed for this level. Same deviation as the image call it
// patches: the uncompressed texel shadow beside it is NOT touched, because there is no
// BC/ETC codec here to decode the incoming blocks with - so what changes is the image
// glGetCompressedTexImage hands back, not what the level samples as. Marking the texels
// dirty would therefore only re-upload bytes that did not change.
void CompressedTexSubImage2D_State(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, void CompressedTexSubImage2D_State(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width,
GLsizei height, GLenum format, GLsizei imageSize, const void* data) { GLsizei height, GLenum format, GLsizei imageSize, const void* data) {
// TODO: implement compressed upload - see CompressedTexImage2D_State. // ======================= Converting ================================
RecordUnsupportedCompressedFormat(__func__); const auto textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
// Zero block width doubles as "format is not a specific compressed format", the
// INVALID_ENUM case - one lookup answers both questions.
const auto compressedInfo = MG_Util::GetCompressedFormatInfo(format);
// ===================== Error Checking ==============================
if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return;
// A proxy holds no image to modify; only the glTexImage*/glCompressedTexImage* pair
// accepts one.
if (TextureImpl::IsProxyTextureTarget(textureUploadTarget)) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
"A proxy target has no texture image to modify."));
return;
}
if (!TextureImpl::ValidateTextureLevelNumber(level)) return;
if (!TextureImpl::ValidateTextureLevelWithUploadTarget(textureUploadTarget, level)) return;
if (width < 0 || height < 0) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "width and height must be non-negative."));
return;
}
if (compressedInfo.blockWidth == 0) {
RecordUnsupportedCompressedFormat(__func__);
return;
}
auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
auto* textureMipmapObject = MG_State::GLState::AsMipmapTexture(textureObject.get());
if (textureMipmapObject == nullptr) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Texture storage is not mipmap-backed."));
return;
}
// GL 4.6 core 8.7: INVALID_OPERATION unless the image being modified is stored in
// exactly this compressed format. That is also what makes the block arithmetic below
// sound - the level's grid is measured with THIS format's block size.
const GLenum levelFormat =
textureMipmapObject->GetMipmapCompressedFormat(textureUploadTarget, static_cast<Uint>(level));
if (levelFormat != format) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
"format does not match the internal format of the texture image."));
return;
}
const IntVec3 levelSize = textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, static_cast<Uint>(level));
if (xoffset < 0 || yoffset < 0 || xoffset + width > levelSize.x() || yoffset + height > levelSize.y()) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
"The replaced region does not lie within the texture image."));
return;
}
// GL 4.6 core 8.7 for block-based formats: the region must start on a block boundary
// and must either be a whole number of blocks wide/high or run to the image's edge.
const Int blockWidth = static_cast<Int>(compressedInfo.blockWidth);
const Int blockHeight = static_cast<Int>(compressedInfo.blockHeight);
const Bool alignedX = (xoffset % blockWidth == 0) &&
(width % blockWidth == 0 || xoffset + width == levelSize.x());
const Bool alignedY = (yoffset % blockHeight == 0) &&
(height % blockHeight == 0 || yoffset + height == levelSize.y());
if (!alignedX || !alignedY) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
"The replaced region is not aligned to the format's compressed blocks."));
return;
}
// Exactly the size the format and dimensions imply, which is also what keeps the copy
// below in bounds.
const SizeT expectedImageSize =
MG_Util::CalculateCompressedTextureImageSize(compressedInfo, {width, height, 1});
if (imageSize < 0 || static_cast<SizeT>(imageSize) != expectedImageSize) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
"imageSize does not match the compressed image size."));
return;
}
// ======================= Processing ================================
const void* compressedBytes = data;
const auto& pixelUnpackBufferObject =
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelUnpack).GetBoundObject();
if (pixelUnpackBufferObject) {
if (pixelUnpackBufferObject->IsMapped()) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
"Pixel unpack buffer is currently mapped."));
return;
}
const SizeT offset = reinterpret_cast<SizeT>(data);
const SizeT bufferSize = pixelUnpackBufferObject->GetSize();
if (offset > bufferSize || expectedImageSize > bufferSize - offset) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
"Unpacking would read past the end of the pixel unpack buffer."));
return;
}
compressedBytes = reinterpret_cast<const char*>(pixelUnpackBufferObject->MappedData()) + offset;
}
if (expectedImageSize == 0) return; // a zero-sized region is a legal no-op
if (compressedBytes == nullptr) {
// No unpack buffer and a null client pointer: there is nothing to read. GL leaves
// this undefined rather than erroring, and dereferencing it is the one answer that
// is never acceptable.
MGLOG_D("%s: null data with no pixel unpack buffer bound, nothing to replace", __func__);
return;
}
// The level's compressed image is stored as one blob, so the rectangle is patched into
// a copy of it and the whole thing handed back. Compressed sub-image uploads are not a
// hot path, and this keeps the storage layer's compressed API to the two calls it has.
const SizeT blobSize =
textureMipmapObject->GetMipmapCompressedByteSize(textureUploadTarget, static_cast<Uint>(level));
const void* existing =
textureMipmapObject->MapMipmapCompressedImage(textureUploadTarget, static_cast<Uint>(level));
if (blobSize == 0 || existing == nullptr) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
"The texture level holds no compressed image to modify."));
return;
}
Vector<Uint8> blob(blobSize);
Memcpy(blob.data(), existing, blobSize);
const SizeT blockByteSize = compressedInfo.blockByteSize;
const SizeT levelBlocksX = (static_cast<SizeT>(levelSize.x()) + compressedInfo.blockWidth - 1) /
compressedInfo.blockWidth;
const SizeT levelRowBytes = levelBlocksX * blockByteSize;
const SizeT regionBlocksX = (static_cast<SizeT>(width) + compressedInfo.blockWidth - 1) /
compressedInfo.blockWidth;
const SizeT regionBlocksY = (static_cast<SizeT>(height) + compressedInfo.blockHeight - 1) /
compressedInfo.blockHeight;
const SizeT firstBlockX = static_cast<SizeT>(xoffset) / compressedInfo.blockWidth;
const SizeT firstBlockY = static_cast<SizeT>(yoffset) / compressedInfo.blockHeight;
const SizeT regionRowBytes = regionBlocksX * blockByteSize;
const auto* source = static_cast<const Uint8*>(compressedBytes);
for (SizeT row = 0; row < regionBlocksY; ++row) {
const SizeT destOffset = (firstBlockY + row) * levelRowBytes + firstBlockX * blockByteSize;
if (destOffset + regionRowBytes > blobSize) break; // a level whose blob predates its size
Memcpy(blob.data() + destOffset, source + row * regionRowBytes, regionRowBytes);
}
textureMipmapObject->SetMipmapCompressedImage(textureUploadTarget, static_cast<Uint>(level), format,
blob.data(), blobSize);
} }
void CompressedTexSubImage1D_State(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, void CompressedTexSubImage1D_State(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format,
@@ -4472,6 +4649,14 @@ namespace MobileGL::MG_Impl::GLImpl {
free(processedPixels); free(processedPixels);
} }
void CompressedTextureSubImage2D(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width,
GLsizei height, GLenum format, GLsizei imageSize, const void* data) {
auto textureObject = GetTextureObjectByName(texture, __func__);
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) {
CompressedTexSubImage2D_State(target, level, xoffset, yoffset, width, height, format, imageSize, data);
});
}
void TextureSubImage3D(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, void TextureSubImage3D(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width,
GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* pixels) { GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* pixels) {
auto textureObject = GetTextureObjectByName(texture, __func__); auto textureObject = GetTextureObjectByName(texture, __func__);
@@ -37,6 +37,8 @@ namespace MobileGL::MG_Impl::GLImpl {
GLenum format, GLenum type, const void* pixels); GLenum format, GLenum type, const void* pixels);
void TextureSubImage3D(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, void TextureSubImage3D(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width,
GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* pixels); GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* pixels);
void CompressedTextureSubImage2D(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width,
GLsizei height, GLenum format, GLsizei imageSize, const void* data);
void TextureParameterf(GLuint texture, GLenum pname, GLfloat param); void TextureParameterf(GLuint texture, GLenum pname, GLfloat param);
void TextureParameterfv(GLuint texture, GLenum pname, const GLfloat* params); void TextureParameterfv(GLuint texture, GLenum pname, const GLfloat* params);
void TextureParameteri(GLuint texture, GLenum pname, GLint param); void TextureParameteri(GLuint texture, GLenum pname, GLint param);
+233
View File
@@ -16,6 +16,7 @@
#include <MG_Backend/BackendObjects.h> #include <MG_Backend/BackendObjects.h>
#include <MG_Backend/DirectGLES/Managers.h> #include <MG_Backend/DirectGLES/Managers.h>
#include <MG_Backend/DirectGLES/Utils.h> #include <MG_Backend/DirectGLES/Utils.h>
#include <MG_Impl/GLImpl/Buffer/GL_Buffer.h>
#include <MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h> #include <MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h>
#include <MG_Impl/GLImpl/Getter/GL_Getter.h> #include <MG_Impl/GLImpl/Getter/GL_Getter.h>
#include <MG_Impl/GLImpl/RenderState/GL_RenderState.h> #include <MG_Impl/GLImpl/RenderState/GL_RenderState.h>
@@ -1572,6 +1573,238 @@ TEST_F(TextureTest, CompressedInternalFormatsResolveToTheirUncompressedStorage)
} }
} }
// Resolving to uncompressed storage is a storage decision, not a licence to answer the level
// queries as if the application had asked for an uncompressed format. GL 4.6 core 8.5 lets the
// implementation choose for the GENERIC formats (GL_COMPRESSED_RED and friends), but a SPECIFIC
// one commits the level: GL_TEXTURE_COMPRESSED is true, GL_TEXTURE_INTERNAL_FORMAT is the token
// that was passed, and GL_TEXTURE_COMPRESSED_IMAGE_SIZE answers instead of erroring - which is
// exactly the three-query sequence KHR-GL44.buffer_storage.map_persistent_texture opens with to
// size the image it then uploads through glCompressedTexSubImage2D.
TEST_F(TextureTest, ASpecificCompressedInternalFormatTagsTheLevelCompressed) {
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_COMPRESSED_RED_RGTC1, 8, 8, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
GLint compressed = GL_FALSE;
MG_Impl::GLImpl::GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED, &compressed);
EXPECT_EQ(compressed, GL_TRUE);
GLint internalFormat = 0;
MG_Impl::GLImpl::GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &internalFormat);
EXPECT_EQ(internalFormat, static_cast<GLint>(GL_COMPRESSED_RED_RGTC1));
// 8x8 in 4x4 blocks of 8 bytes each.
GLint imageSize = 0;
MG_Impl::GLImpl::GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &imageSize);
EXPECT_EQ(imageSize, 32);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
// The texel shadow behind the tag still carries the uncompressed storage the format resolves
// to - which is what lets the level sample, and what every size computation downstream
// divides by.
const auto textureObject = MG_State::pGLContext->GetTextureObject(texture);
ASSERT_NE(textureObject, nullptr);
EXPECT_EQ(textureObject->GetFormat(), TextureInternalFormat::R8);
}
// The negative control for the case above, and the reason it cannot simply tag every
// GL_COMPRESSED_* token: for a generic format the implementation's choice IS the answer, and
// MobileGL chooses uncompressed - so the level is not compressed and the size query is the
// INVALID_OPERATION GL 4.6 core 8.11 prescribes for an uncompressed image.
TEST_F(TextureTest, AGenericCompressedInternalFormatLeavesTheLevelUncompressed) {
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_COMPRESSED_RED, 8, 8, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
GLint compressed = GL_TRUE;
MG_Impl::GLImpl::GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED, &compressed);
EXPECT_EQ(compressed, GL_FALSE);
GLint internalFormat = 0;
MG_Impl::GLImpl::GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &internalFormat);
EXPECT_EQ(internalFormat, static_cast<GLint>(GL_R8));
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
GLint imageSize = 0;
MG_Impl::GLImpl::GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &imageSize);
ExpectSingleGlError(GL_INVALID_OPERATION);
}
// A plain glTexImage2D over a level that was tagged compressed has to un-tag it, the same way it
// does for a level a glCompressedTexImage2D shadowed - otherwise the size query would keep
// answering for an image that no longer exists.
TEST_F(TextureTest, AnUncompressedRespecificationClearsTheCompressedTag) {
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_COMPRESSED_RED_RGTC1, 8, 8, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_R8, 8, 8, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
GLint compressed = GL_TRUE;
MG_Impl::GLImpl::GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED, &compressed);
EXPECT_EQ(compressed, GL_FALSE);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
}
namespace {
// 8x8 RGTC1: 2x2 blocks of 8 bytes, so the stored image is 32 bytes and one block row is 16.
constexpr GLsizei kRgtc1Size8x8 = 32;
GLuint MakeCompressedRgtc1Texture8x8() {
GLuint texture = 0;
MG_Impl::GLImpl::GenTextures(1, &texture);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture);
MG_Impl::GLImpl::CompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RED_RGTC1, 8, 8, 0, kRgtc1Size8x8,
nullptr);
return texture;
}
} // namespace
// glCompressedTexSubImage2D was a stub that answered GL_INVALID_ENUM to every call. It replaces a
// block-aligned rectangle of the stored image, and the arithmetic that places the incoming blocks
// is what the partial write below pins: a full-width write would pass with the rows concatenated
// in either order.
TEST_F(TextureTest, CompressedTexSubImage2DReplacesTheStoredBlocks) {
const GLuint texture = MakeCompressedRgtc1Texture8x8();
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
Uint8 whole[kRgtc1Size8x8];
for (Int i = 0; i < kRgtc1Size8x8; ++i) whole[i] = static_cast<Uint8>(i + 1);
MG_Impl::GLImpl::CompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 8, GL_COMPRESSED_RED_RGTC1, kRgtc1Size8x8,
whole);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
Uint8 stored[kRgtc1Size8x8] = {};
MG_Impl::GLImpl::GetCompressedTexImage(GL_TEXTURE_2D, 0, stored);
EXPECT_EQ(std::memcmp(stored, whole, sizeof(whole)), 0);
// The right-hand block column only: one block wide, two block rows high. Its two blocks land
// at byte 8 and byte 24, not at bytes 0 and 8.
const Uint8 column[16] = {0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7};
MG_Impl::GLImpl::CompressedTexSubImage2D(GL_TEXTURE_2D, 0, 4, 0, 4, 8, GL_COMPRESSED_RED_RGTC1,
static_cast<GLsizei>(sizeof(column)), column);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
Uint8 expected[kRgtc1Size8x8];
std::memcpy(expected, whole, sizeof(expected));
std::memcpy(expected + 8, column, 8);
std::memcpy(expected + 24, column + 8, 8);
std::memset(stored, 0, sizeof(stored));
MG_Impl::GLImpl::GetCompressedTexImage(GL_TEXTURE_2D, 0, stored);
EXPECT_EQ(std::memcmp(stored, expected, sizeof(expected)), 0);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
}
// The same call sourcing its blocks from a buffer bound to GL_PIXEL_UNPACK_BUFFER, where `data` is
// an offset into that buffer rather than a client pointer - which is the form
// KHR-GL44.buffer_storage.map_persistent_texture uses for every one of its operations.
TEST_F(TextureTest, CompressedTexSubImage2DUnpacksFromAPixelUnpackBuffer) {
Uint8 source[256];
for (Int i = 0; i < 256; ++i) source[i] = static_cast<Uint8>(i);
GLuint buffer = 0;
MG_Impl::GLImpl::GenBuffers(1, &buffer);
MG_Impl::GLImpl::BindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer);
MG_Impl::GLImpl::BufferData(GL_PIXEL_UNPACK_BUFFER, sizeof(source), source, GL_STATIC_DRAW);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
const GLuint texture = MakeCompressedRgtc1Texture8x8();
MG_Impl::GLImpl::CompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 8, GL_COMPRESSED_RED_RGTC1, kRgtc1Size8x8,
reinterpret_cast<const void*>(static_cast<SizeT>(64)));
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
Uint8 stored[kRgtc1Size8x8] = {};
MG_Impl::GLImpl::GetCompressedTexImage(GL_TEXTURE_2D, 0, stored);
EXPECT_EQ(std::memcmp(stored, source + 64, sizeof(stored)), 0);
// Reading past the end of the buffer is the unpack-buffer error, not a read out of bounds.
MG_Impl::GLImpl::CompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 8, GL_COMPRESSED_RED_RGTC1, kRgtc1Size8x8,
reinterpret_cast<const void*>(static_cast<SizeT>(sizeof(source) - 8)));
ExpectSingleGlError(GL_INVALID_OPERATION);
MG_Impl::GLImpl::BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
(void)texture;
}
// glCompressedTextureSubImage2D was an exported no-op that raised no error at all, so an
// application could not tell the write had not happened. It must reach the NAMED texture and leave
// the binding it borrowed exactly as it found it.
TEST_F(TextureTest, CompressedTextureSubImage2DModifiesTheNamedTextureOnly) {
const GLuint bound = MakeCompressedRgtc1Texture8x8();
Uint8 boundImage[kRgtc1Size8x8];
for (Int i = 0; i < kRgtc1Size8x8; ++i) boundImage[i] = 0x11;
MG_Impl::GLImpl::CompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 8, GL_COMPRESSED_RED_RGTC1, kRgtc1Size8x8,
boundImage);
const GLuint named = MakeCompressedRgtc1Texture8x8();
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, bound); // `named` is NOT the bound texture
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
Uint8 namedImage[kRgtc1Size8x8];
for (Int i = 0; i < kRgtc1Size8x8; ++i) namedImage[i] = 0x22;
MG_Impl::GLImpl::CompressedTextureSubImage2D(named, 0, 0, 0, 8, 8, GL_COMPRESSED_RED_RGTC1, kRgtc1Size8x8,
namedImage);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
// The borrowed binding is back, and it kept its own image.
Uint8 stored[kRgtc1Size8x8] = {};
MG_Impl::GLImpl::GetCompressedTexImage(GL_TEXTURE_2D, 0, stored);
EXPECT_EQ(std::memcmp(stored, boundImage, sizeof(stored)), 0);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, named);
std::memset(stored, 0, sizeof(stored));
MG_Impl::GLImpl::GetCompressedTexImage(GL_TEXTURE_2D, 0, stored);
EXPECT_EQ(std::memcmp(stored, namedImage, sizeof(stored)), 0);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
}
TEST_F(TextureTest, CompressedTexSubImage2DRejectsTheRegionsGLForbids) {
const GLuint texture = MakeCompressedRgtc1Texture8x8();
Uint8 blocks[kRgtc1Size8x8] = {};
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
// A format that is not the one the image is stored in.
MG_Impl::GLImpl::CompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 8, GL_COMPRESSED_RG_RGTC2, 64, blocks);
ExpectSingleGlError(GL_INVALID_OPERATION);
// A start that is not on a block boundary.
MG_Impl::GLImpl::CompressedTexSubImage2D(GL_TEXTURE_2D, 0, 2, 0, 4, 8, GL_COMPRESSED_RED_RGTC1, 16, blocks);
ExpectSingleGlError(GL_INVALID_OPERATION);
// A width that is neither a whole number of blocks nor a run to the image's edge.
MG_Impl::GLImpl::CompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 8, GL_COMPRESSED_RED_RGTC1, 16, blocks);
ExpectSingleGlError(GL_INVALID_OPERATION);
// A region that runs off the image.
MG_Impl::GLImpl::CompressedTexSubImage2D(GL_TEXTURE_2D, 0, 4, 0, 8, 8, GL_COMPRESSED_RED_RGTC1, 32, blocks);
ExpectSingleGlError(GL_INVALID_VALUE);
// An imageSize that does not match the region.
MG_Impl::GLImpl::CompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 8, GL_COMPRESSED_RED_RGTC1, 16, blocks);
ExpectSingleGlError(GL_INVALID_VALUE);
// A format with no defined block layout here.
MG_Impl::GLImpl::CompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 8, GL_RGBA8, 32, blocks);
ExpectSingleGlError(GL_INVALID_ENUM);
// An uncompressed image has nothing for it to replace.
GLuint plain = 0;
MG_Impl::GLImpl::GenTextures(1, &plain);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, plain);
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_R8, 8, 8, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
MG_Impl::GLImpl::CompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 8, GL_COMPRESSED_RED_RGTC1, kRgtc1Size8x8,
blocks);
ExpectSingleGlError(GL_INVALID_OPERATION);
(void)texture;
}
// RGTC compresses 4x4 blocks of a 2D image and has no 3D form, so glTexImage3D must reject it even // RGTC compresses 4x4 blocks of a 2D image and has no 3D form, so glTexImage3D must reject it even
// though the same enum is accepted on a 2D target. The generic compressed formats carry no such // though the same enum is accepted on a 2D target. The generic compressed formats carry no such
// restriction and stay legal in 3D. // restriction and stay legal in 3D.