[Merge] (GLImpl, DirectGLES, DirectVulkan): complete direct state access and the compressed-texture family

This commit is contained in:
2026-08-22 22:47:08 -04:00
6 changed files with 505 additions and 13 deletions
@@ -1047,9 +1047,9 @@ DECLARE_GL_FUNCTION_HEAD(void, TextureStorage3DMultisample, GLuint texture, GLsi
DECLARE_GL_FUNCTION_HEAD(void, TextureSubImage1D, GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void* pixels) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureSubImage1D, texture, level, xoffset, width, format, type, pixels) DECLARE_GL_FUNCTION_HEAD(void, TextureSubImage1D, GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void* pixels) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureSubImage1D, texture, level, xoffset, width, 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, 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_HEAD(void, CompressedTextureSubImage1D, GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CompressedTextureSubImage1D, texture, level, xoffset, width, 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_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_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_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)
DECLARE_GL_FUNCTION_HEAD(void, CopyTextureSubImage3D, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CopyTextureSubImage3D, texture, level, xoffset, yoffset, zoffset, x, y, width, height) DECLARE_GL_FUNCTION_HEAD(void, CopyTextureSubImage3D, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CopyTextureSubImage3D, texture, level, xoffset, yoffset, zoffset, x, y, width, height)
@@ -1835,9 +1835,9 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, GetBooleanIndexedvEXT, GLenum target, GLuint
DECLARE_GL_FUNCTION_STUB_HEAD(void, CompressedTextureImage3DEXT, GLuint texture, 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, CompressedTextureImage3DEXT, texture, target, level, internalformat, width, height, depth, border, imageSize, bits) DECLARE_GL_FUNCTION_STUB_HEAD(void, CompressedTextureImage3DEXT, GLuint texture, 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, CompressedTextureImage3DEXT, texture, target, level, internalformat, width, height, depth, 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, 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_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_END_NO_RETURN(void, CompressedTextureSubImage3D, texture, level, xoffset, yoffset, zoffset, width, height, depth, 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_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_HEAD(void, CompressedTextureSubImage1DEXT, GLuint texture, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void* bits) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CompressedTextureSubImage1D, texture, 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)
DECLARE_GL_FUNCTION_STUB_HEAD(void, CompressedMultiTexImage2DEXT, GLenum texunit, 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, CompressedMultiTexImage2DEXT, texunit, target, level, internalformat, width, height, border, imageSize, bits) DECLARE_GL_FUNCTION_STUB_HEAD(void, CompressedMultiTexImage2DEXT, GLenum texunit, 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, CompressedMultiTexImage2DEXT, texunit, target, level, internalformat, width, height, border, imageSize, bits)
+251 -5
View File
@@ -4099,11 +4099,171 @@ namespace MobileGL::MG_Impl::GLImpl {
"1D textures are not supported by this implementation")); "1D textures are not supported by this implementation"));
} }
// The three-dimensional twin of CompressedTexSubImage2D_State: a block-aligned box of the
// compressed image the level shadows is replaced, slice by slice. Same deviation as the 2D form
// - the uncompressed texel shadow beside it is NOT touched, so what changes is the image
// glGetCompressedTexImage hands back, not what the level samples as.
void CompressedTexSubImage3D_State(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, void CompressedTexSubImage3D_State(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset,
GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize,
const void* data) { 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 || depth < 0) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
"width, height and depth 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));
const Int levelDepth = std::max(levelSize.z(), 1);
// Subtractions rather than sums for the reason CompressedTexSubImage2D_State spells out:
// offset + extent are both application-supplied GLints and a signed overflow is undefined.
if (xoffset < 0 || yoffset < 0 || zoffset < 0 || width > levelSize.x() - xoffset ||
height > levelSize.y() - yoffset || depth > levelDepth - zoffset) {
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. Every
// format that reaches here is 4x4x1, so the depth axis carries no block alignment rule -
// each slice is its own block grid.
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, depth});
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 ================================
if (!ValidateCompressedUnpackBufferSource(data, expectedImageSize, __func__)) return;
const void* compressedBytes = CompressedUnpackSource(data);
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;
}
static std::atomic<Bool> announcedNoCodec3D{false};
if (!announcedNoCodec3D.exchange(true)) {
MGLOG_W("%s: the compressed blocks are stored verbatim and returned by "
"glGetCompressedTexImage, but there is no BC/ETC decoder here, so they do not "
"reach the texels this level SAMPLES as. Upload through glTexSubImage3D for "
"that.",
__func__);
}
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 levelBlocksY = (static_cast<SizeT>(levelSize.y()) + compressedInfo.blockHeight - 1) /
compressedInfo.blockHeight;
const SizeT levelRowBytes = levelBlocksX * blockByteSize;
const SizeT levelSliceBytes = levelRowBytes * levelBlocksY;
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 SizeT regionSliceBytes = regionRowBytes * regionBlocksY;
const auto* source = static_cast<const Uint8*>(compressedBytes);
for (SizeT slice = 0; slice < static_cast<SizeT>(depth); ++slice) {
const SizeT destSliceBase = (static_cast<SizeT>(zoffset) + slice) * levelSliceBytes;
for (SizeT row = 0; row < regionBlocksY; ++row) {
const SizeT destOffset =
destSliceBase + (firstBlockY + row) * levelRowBytes + firstBlockX * blockByteSize;
if (destOffset + regionRowBytes > blobSize) break; // a level whose blob predates its size
Memcpy(blob.data() + destOffset, source + slice * regionSliceBytes + row * regionRowBytes,
regionRowBytes);
}
}
textureMipmapObject->SetMipmapCompressedImage(textureUploadTarget, static_cast<Uint>(level), format,
blob.data(), blobSize);
} }
// Replaces a block-aligned rectangle of the compressed image glCompressedTexImage2D (or a // Replaces a block-aligned rectangle of the compressed image glCompressedTexImage2D (or a
@@ -4278,15 +4438,83 @@ namespace MobileGL::MG_Impl::GLImpl {
RecordUnsupportedCompressedFormat(__func__); RecordUnsupportedCompressedFormat(__func__);
} }
// The three-dimensional twin of CompressedTexImage2D_State, and the same deviation applies: the
// blocks are shadowed verbatim for glGetCompressedTexImage while the texels this level SAMPLES
// as stay zero, because there is no BC/ETC decoder here. A 3D compressed image is a stack of
// `depth` two-dimensional block grids - every format that reaches here has a 4x4x1 block - so
// the blob layout is slice-major and CalculateCompressedTextureImageSize already multiplies by
// depth.
void CompressedTexImage3D_State(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, void CompressedTexImage3D_State(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height,
GLsizei depth, GLint border, GLsizei imageSize, const void* data) { GLsizei depth, GLint border, GLsizei imageSize, const void* data) {
// ======================= Converting ================================
const auto textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); const auto textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget); // Zero block width doubles as "internalformat is not a specific compressed format", which is
// the INVALID_ENUM case - one lookup answers both questions.
const auto compressedInfo = MG_Util::GetCompressedFormatInfo(internalformat);
// ===================== Error Checking ==============================
if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return;
if (!TextureImpl::ValidateTextureLevelNumber(level)) return;
if (!TextureImpl::ValidateTextureSizeWithTextureUploadTarget(textureUploadTarget, width, height)) return;
if (!TextureImpl::ValidateTextureSizeRange(width, height, depth)) return;
if (!TextureImpl::ValidateTextureBorderNumber(border)) return;
if (!TextureImpl::ValidateTextureLevelWithUploadTarget(textureUploadTarget, level)) return;
if (compressedInfo.blockWidth == 0) {
RecordUnsupportedCompressedFormat(__func__);
return;
}
// GL 4.6 core 8.7: imageSize must be exactly the size the format and dimensions imply,
// otherwise INVALID_VALUE. This is also the guard that keeps the copy below in bounds.
const SizeT expectedImageSize =
MG_Util::CalculateCompressedTextureImageSize(compressedInfo, {width, height, depth});
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;
}
// Object resolution copied from TexImage3D_State rather than routed through
// GetTextureObjectByTarget, for the reason CompressedTexImage2D_State gives: a proxy target
// is legal here and only CreateOrReplaceProxyTextureObject gives it an object to answer the
// level queries from.
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
const Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget);
auto& textureObject =
isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget)
: bindingSlot.GetBoundObject();
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
if (!ValidateTextureMutable(textureObject, __func__)) return; if (!ValidateTextureMutable(textureObject, __func__)) return;
// TODO: implement compressed upload - see CompressedTexImage2D_State. // ======================= Processing ================================
RecordUnsupportedCompressedFormat(__func__); const TextureInternalFormat textureInternalFormat =
MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat);
textureObject->SetInternalFormat(textureInternalFormat);
// A proxy records the format and nothing else - it must never take storage, and it must never
// be tagged compressed, or GL_TEXTURE_COMPRESSED_IMAGE_SIZE on a proxy would stop being
// INVALID_OPERATION.
if (isProxy) return;
const SizeT internalBpp =
MG_Util::GetInternalBytesPerPixel(textureInternalFormat, TexturePixelDataType::UnsignedByte);
const SizeT internalBytes =
static_cast<SizeT>(width) * static_cast<SizeT>(height) * static_cast<SizeT>(depth) * internalBpp;
auto* textureMipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
DiscardMipmapChainOnBaseRespecification(textureMipmapObject, textureUploadTarget, level);
// AllocateStorage clears any compressed image the level used to hold, so this must run before
// SetMipmapCompressedImage re-arms it.
textureMipmapObject->AllocateStorage(textureUploadTarget, level, {{width, height, depth}, internalBytes});
if (!ValidateCompressedUnpackBufferSource(data, expectedImageSize, __func__)) return;
const void* compressedBytes = CompressedUnpackSource(data);
textureMipmapObject->SetMipmapCompressedImage(textureUploadTarget, level, internalformat, compressedBytes,
expectedImageSize);
textureMipmapObject->MarkStorageDirty(textureUploadTarget, level, true);
} }
void CompressedTexImage2D_State(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, void CompressedTexImage2D_State(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height,
@@ -5521,6 +5749,14 @@ namespace MobileGL::MG_Impl::GLImpl {
free(processedPixels); free(processedPixels);
} }
void CompressedTextureSubImage1D(GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format,
GLsizei imageSize, const void* data) {
auto textureObject = GetTextureObjectByName(texture, __func__);
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) {
CompressedTexSubImage1D_State(target, level, xoffset, width, format, imageSize, data);
});
}
void CompressedTextureSubImage2D(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, void CompressedTextureSubImage2D(GLuint texture, 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) {
auto textureObject = GetTextureObjectByName(texture, __func__); auto textureObject = GetTextureObjectByName(texture, __func__);
@@ -5529,6 +5765,16 @@ namespace MobileGL::MG_Impl::GLImpl {
}); });
} }
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) {
auto textureObject = GetTextureObjectByName(texture, __func__);
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) {
CompressedTexSubImage3D_State(target, level, xoffset, yoffset, zoffset, width, height, depth, 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,8 +37,13 @@ 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 CompressedTextureSubImage1D(GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format,
GLsizei imageSize, const void* data);
void CompressedTextureSubImage2D(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, void CompressedTextureSubImage2D(GLuint texture, 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);
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);
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);
+56
View File
@@ -711,6 +711,62 @@ TEST_F(BufferTest, ClearNamedBufferSubDataRepeatsPattern) {
EXPECT_EQ(actual, (Vector<Uint32>{0, pattern, pattern, pattern, 0})); EXPECT_EQ(actual, (Vector<Uint32>{0, pattern, pattern, pattern, 0}));
EXPECT_EQ(MobileGL::MG_Impl::GLImpl::GetError(), GL_NO_ERROR); EXPECT_EQ(MobileGL::MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
} }
// GL 4.6 core table 8.2 pairs GL_INT with the non-integer base formats as a signed-normalized
// source, so a GL_R8 clear whose pattern arrives as (GL_RED, GL_INT) is legal. The pair used to be
// rejected with INVALID_VALUE, which is the first call
// KHR-GL45.direct_state_access.buffers_functional makes.
TEST_F(BufferTest, ClearNamedBufferSubDataAcceptsSignedNormalizedIntPattern) {
GLuint buffer = 0;
MobileGL::MG_Impl::GLImpl::CreateBuffers(1, &buffer);
const Vector<Uint8> initial(24, 0x7F);
MobileGL::MG_Impl::GLImpl::NamedBufferStorage(
buffer, initial.size(), initial.data(),
GL_MAP_READ_BIT | GL_MAP_WRITE_BIT | GL_DYNAMIC_STORAGE_BIT | GL_MAP_PERSISTENT_BIT);
ASSERT_EQ(MobileGL::MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
const GLint zero = 0;
MobileGL::MG_Impl::GLImpl::ClearNamedBufferSubData(buffer, GL_R8, 0, sizeof(GLint), GL_RED, GL_INT, &zero);
EXPECT_EQ(MobileGL::MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
Vector<Uint8> actual(initial.size());
auto bufferObject = MobileGL::MG_State::pGLContext->GetBufferObject(buffer);
ASSERT_NE(bufferObject, nullptr);
Memcpy(actual.data(), bufferObject->AcquireMemory(false, true, false), actual.size());
Vector<Uint8> expected(initial);
for (SizeT i = 0; i < sizeof(GLint); ++i) expected[i] = 0;
EXPECT_EQ(actual, expected);
MobileGL::MG_Impl::GLImpl::DeleteBuffers(1, &buffer);
DrainPendingGlErrors();
}
// The same pair on the bound-target entry point: the DSA and the bound call share
// ClearBufferRange_State, and a regression in either direction has to show up here too.
TEST_F(BufferTest, ClearBufferSubDataAcceptsSignedNormalizedIntPattern) {
GLuint buffer = 0;
MobileGL::MG_Impl::GLImpl::GenBuffers(1, &buffer);
MobileGL::MG_Impl::GLImpl::BindBuffer(GL_ARRAY_BUFFER, buffer);
const Vector<Uint8> initial(8, 0x7F);
MobileGL::MG_Impl::GLImpl::BufferData(GL_ARRAY_BUFFER, initial.size(), initial.data(), GL_STATIC_DRAW);
// GL_INT is signed-normalized against 2^31-1, so the maximum maps to a saturated GL_R8 texel.
const GLint one = 2147483647;
MobileGL::MG_Impl::GLImpl::ClearBufferSubData(GL_ARRAY_BUFFER, GL_R8, 0, 4, GL_RED, GL_INT, &one);
EXPECT_EQ(MobileGL::MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
Vector<Uint8> actual(initial.size());
auto bufferObject = MobileGL::MG_State::pGLContext->GetBufferObject(buffer);
ASSERT_NE(bufferObject, nullptr);
Memcpy(actual.data(), bufferObject->AcquireMemory(false, true, false), actual.size());
EXPECT_EQ(actual, (Vector<Uint8>{0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x7F, 0x7F, 0x7F}));
MobileGL::MG_Impl::GLImpl::BindBuffer(GL_ARRAY_BUFFER, 0);
MobileGL::MG_Impl::GLImpl::DeleteBuffers(1, &buffer);
DrainPendingGlErrors();
}
TEST_F(BufferTest, ClearBufferSubDataInitializesIrisStaticSsboRange) { TEST_F(BufferTest, ClearBufferSubDataInitializesIrisStaticSsboRange) {
GLuint buffer = 0; GLuint buffer = 0;
MobileGL::MG_Impl::GLImpl::GenBuffers(1, &buffer); MobileGL::MG_Impl::GLImpl::GenBuffers(1, &buffer);
+169
View File
@@ -2227,6 +2227,175 @@ TEST_F(TextureTest, CompressedTextureSubImage2DModifiesTheNamedTextureOnly) {
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
} }
namespace {
// 8x8x8 RGTC1: 2x2 blocks of 8 bytes per slice, so a slice is 32 bytes and the stack is 256.
constexpr GLsizei kRgtc1Size8x8x8 = 256;
constexpr GLsizei kRgtc1Slice8x8 = 32;
GLuint MakeCompressedRgtc1Texture3D() {
GLuint texture = 0;
MG_Impl::GLImpl::GenTextures(1, &texture);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_3D, texture);
MG_Impl::GLImpl::CompressedTexImage3D(GL_TEXTURE_3D, 0, GL_COMPRESSED_RED_RGTC1, 8, 8, 8, 0, kRgtc1Size8x8x8,
nullptr);
return texture;
}
} // namespace
// glCompressedTexImage3D used to answer GL_INVALID_ENUM to every call, which is what threw
// KHR-GL45.direct_state_access.textures_compressed_subimage out with an InternalError: the CTS
// asserts no error on it. A 3D compressed image is a stack of per-slice block grids, and the whole
// stack has to come back byte for byte.
TEST_F(TextureTest, CompressedTexImage3DShadowsTheWholeStackForReadback) {
Uint8 whole[kRgtc1Size8x8x8];
for (Int i = 0; i < kRgtc1Size8x8x8; ++i) whole[i] = static_cast<Uint8>(i);
GLuint texture = 0;
MG_Impl::GLImpl::GenTextures(1, &texture);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_3D, texture);
MG_Impl::GLImpl::CompressedTexImage3D(GL_TEXTURE_3D, 0, GL_COMPRESSED_RED_RGTC1, 8, 8, 8, 0, kRgtc1Size8x8x8,
whole);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
Uint8 stored[kRgtc1Size8x8x8] = {};
MG_Impl::GLImpl::GetCompressedTexImage(GL_TEXTURE_3D, 0, stored);
EXPECT_EQ(std::memcmp(stored, whole, sizeof(whole)), 0);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
// An imageSize that is not the one the format and the three dimensions imply - the depth axis
// is the term a 2D-shaped size calculation would drop.
MG_Impl::GLImpl::CompressedTexImage3D(GL_TEXTURE_3D, 0, GL_COMPRESSED_RED_RGTC1, 8, 8, 8, 0, kRgtc1Slice8x8,
whole);
ExpectSingleGlError(GL_INVALID_VALUE);
}
// Where the incoming blocks land. The box below is one block wide, one block high and two slices
// deep, starting at block (1,1) of slice 3: an implementation that dropped the slice stride, the
// block-row term or the block-column term puts them somewhere else, and a full-image write would
// hide all three.
TEST_F(TextureTest, CompressedTexSubImage3DPlacesBlocksSliceBySlice) {
const GLuint texture = MakeCompressedRgtc1Texture3D();
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
Uint8 zeros[kRgtc1Size8x8x8] = {};
MG_Impl::GLImpl::CompressedTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 8, 8, 8, GL_COMPRESSED_RED_RGTC1,
kRgtc1Size8x8x8, zeros);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
const Uint8 box[16] = {0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7};
MG_Impl::GLImpl::CompressedTexSubImage3D(GL_TEXTURE_3D, 0, 4, 4, 3, 4, 4, 2, GL_COMPRESSED_RED_RGTC1,
static_cast<GLsizei>(sizeof(box)), box);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
Uint8 expected[kRgtc1Size8x8x8] = {};
// slice 3, block row 1, block column 1 -> 3*32 + 1*16 + 1*8, and the same place one slice on.
std::memcpy(expected + 3 * kRgtc1Slice8x8 + 16 + 8, box, 8);
std::memcpy(expected + 4 * kRgtc1Slice8x8 + 16 + 8, box + 8, 8);
Uint8 stored[kRgtc1Size8x8x8] = {};
MG_Impl::GLImpl::GetCompressedTexImage(GL_TEXTURE_3D, 0, stored);
EXPECT_EQ(std::memcmp(stored, expected, sizeof(expected)), 0);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
}
// glCompressedTextureSubImage3D was an exported no-op that raised no error at all. It must reach the
// NAMED texture and leave the binding it borrowed exactly as it found it.
TEST_F(TextureTest, CompressedTextureSubImage3DModifiesTheNamedTextureOnly) {
const GLuint bound = MakeCompressedRgtc1Texture3D();
Uint8 boundImage[kRgtc1Size8x8x8];
std::memset(boundImage, 0x11, sizeof(boundImage));
MG_Impl::GLImpl::CompressedTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 8, 8, 8, GL_COMPRESSED_RED_RGTC1,
kRgtc1Size8x8x8, boundImage);
const GLuint named = MakeCompressedRgtc1Texture3D();
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_3D, bound); // `named` is NOT the bound texture
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
Uint8 namedImage[kRgtc1Size8x8x8];
std::memset(namedImage, 0x22, sizeof(namedImage));
MG_Impl::GLImpl::CompressedTextureSubImage3D(named, 0, 0, 0, 0, 8, 8, 8, GL_COMPRESSED_RED_RGTC1,
kRgtc1Size8x8x8, namedImage);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
Uint8 stored[kRgtc1Size8x8x8] = {};
MG_Impl::GLImpl::GetCompressedTexImage(GL_TEXTURE_3D, 0, stored);
EXPECT_EQ(std::memcmp(stored, boundImage, sizeof(stored)), 0);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_3D, named);
std::memset(stored, 0, sizeof(stored));
MG_Impl::GLImpl::GetCompressedTexImage(GL_TEXTURE_3D, 0, stored);
EXPECT_EQ(std::memcmp(stored, namedImage, sizeof(stored)), 0);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
}
TEST_F(TextureTest, CompressedTexSubImage3DRejectsTheRegionsGLForbids) {
const GLuint texture = MakeCompressedRgtc1Texture3D();
Uint8 blocks[kRgtc1Size8x8x8] = {};
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
// A format that is not the one the image is stored in.
MG_Impl::GLImpl::CompressedTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 8, 8, 8, GL_COMPRESSED_RG_RGTC2, 512, blocks);
ExpectSingleGlError(GL_INVALID_OPERATION);
// A start that is not on a block boundary.
MG_Impl::GLImpl::CompressedTexSubImage3D(GL_TEXTURE_3D, 0, 2, 0, 0, 4, 8, 8, GL_COMPRESSED_RED_RGTC1, 128, blocks);
ExpectSingleGlError(GL_INVALID_OPERATION);
// A box that runs past the last slice - the depth bound a 2D-shaped range check never applies.
MG_Impl::GLImpl::CompressedTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 6, 8, 8, 4, GL_COMPRESSED_RED_RGTC1, 128, blocks);
ExpectSingleGlError(GL_INVALID_VALUE);
(void)texture;
}
// The DSA name rule the CTS's textures_creation pair does not reach for these two entry points: a
// name handed out by glGenTextures has no object until it is first bound, so a by-name call on it is
// INVALID_OPERATION - and, unlike the stub these replaced, it has to SAY so rather than return
// quietly. A glCreateTextures name is a created object and gets past the name check.
TEST_F(TextureTest, CompressedTextureSubImage3DRejectsAGeneratedButNeverBoundName) {
GLuint generated = 0;
MG_Impl::GLImpl::GenTextures(1, &generated);
ASSERT_NE(generated, 0u);
DrainPendingGlErrors();
Uint8 blocks[kRgtc1Size8x8x8] = {};
MG_Impl::GLImpl::CompressedTextureSubImage3D(generated, 0, 0, 0, 0, 8, 8, 8, GL_COMPRESSED_RED_RGTC1,
kRgtc1Size8x8x8, blocks);
ExpectSingleGlError(GL_INVALID_OPERATION);
// A created name is past the name check, so whatever it answers is about the IMAGE (this one
// holds none yet), never about the name.
GLuint created = 0;
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_3D, 1, &created);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
MG_Impl::GLImpl::CompressedTextureSubImage3D(created, 0, 0, 0, 0, 8, 8, 8, GL_COMPRESSED_RED_RGTC1,
kRgtc1Size8x8x8, blocks);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_OPERATION)
<< "a created 3D texture with no compressed image is an image error, not a name error";
DrainPendingGlErrors();
}
// Core GL defines no compressed format for a 1D target, so both the bound and the by-name entry
// point have to REFUSE the call. The by-name one used to be an exported no-op that raised nothing,
// which is the one answer an application cannot act on.
TEST_F(TextureTest, CompressedTextureSubImage1DRefusesLikeTheBoundCall) {
GLuint texture = 0;
MG_Impl::GLImpl::GenTextures(1, &texture);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_1D, texture);
MG_Impl::GLImpl::TexImage1D(GL_TEXTURE_1D, 0, GL_R8, 8, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
DrainPendingGlErrors();
Uint8 blocks[16] = {};
MG_Impl::GLImpl::CompressedTexSubImage1D(GL_TEXTURE_1D, 0, 0, 8, GL_COMPRESSED_RED_RGTC1,
static_cast<GLsizei>(sizeof(blocks)), blocks);
ExpectSingleGlError(GL_INVALID_ENUM);
MG_Impl::GLImpl::CompressedTextureSubImage1D(texture, 0, 0, 8, GL_COMPRESSED_RED_RGTC1,
static_cast<GLsizei>(sizeof(blocks)), blocks);
ExpectSingleGlError(GL_INVALID_ENUM);
}
TEST_F(TextureTest, CompressedTexSubImage2DRejectsTheRegionsGLForbids) { TEST_F(TextureTest, CompressedTexSubImage2DRejectsTheRegionsGLForbids) {
const GLuint texture = MakeCompressedRgtc1Texture8x8(); const GLuint texture = MakeCompressedRgtc1Texture8x8();
Uint8 blocks[kRgtc1Size8x8] = {}; Uint8 blocks[kRgtc1Size8x8] = {};
@@ -442,14 +442,30 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
return packed.fieldCount == mapping.channelCount; return packed.fieldCount == mapping.channelCount;
} }
// GL 4.6 core table 8.2: every unpacked component type pairs with every base format,
// with only two exclusions - an integer format takes integer types only, and the two
// floating types need a non-integer format. This used to be derived from
// GetDirectShadowComponentForType, which answers a different question (is the client
// layout byte-identical to some shadow layout) and has no SNorm32 to hand back for
// (non-integer format, GL_INT). That legal pair was therefore rejected outright, even
// though ConvertUnpackRow decodes it through DecodeComponentToFloat like every other
// normalized type - which is what glClearBufferData(GL_R8, GL_RED, GL_INT) needs.
switch (type) { switch (type) {
case TexturePixelDataType::UnsignedInt5999Rev: case TexturePixelDataType::UnsignedInt5999Rev:
case TexturePixelDataType::UnsignedInt101111Rev: case TexturePixelDataType::UnsignedInt101111Rev:
return !mapping.isInteger && mapping.channelCount == 3; return !mapping.isInteger && mapping.channelCount == 3;
default: { case TexturePixelDataType::UnsignedByte:
ShadowComponent component{}; case TexturePixelDataType::Byte:
return GetDirectShadowComponentForType(type, mapping.isInteger, component); case TexturePixelDataType::UnsignedShort:
} case TexturePixelDataType::Short:
case TexturePixelDataType::UnsignedInt:
case TexturePixelDataType::Int:
return true;
case TexturePixelDataType::HalfFloat:
case TexturePixelDataType::Float:
return !mapping.isInteger;
default:
return false;
} }
} }