mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-09 04:38:30 +09:00
[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:
@@ -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, 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, 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_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)
|
||||
@@ -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, 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, 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, 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)
|
||||
|
||||
@@ -2238,6 +2238,23 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
DiscardMipmapChainOnBaseRespecification(textureMipmapObject, textureUploadTarget, level);
|
||||
textureMipmapObject->AllocateStorage(textureUploadTarget, level,
|
||||
{{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) {
|
||||
@@ -2965,9 +2982,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
case GL_TEXTURE_INTERNAL_FORMAT:
|
||||
if (params) {
|
||||
// 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
|
||||
// that tag, so every level created by glTexImage*D - including one given a compressed
|
||||
// internalformat - still answers with its resolved storage format.
|
||||
// uncompressed format backing it (GL 4.6 core 8.11). glCompressedTexImage2D sets
|
||||
// that tag, and so does a glTexImage2D given a SPECIFIC compressed internalformat;
|
||||
// every other level answers with its resolved storage format.
|
||||
const GLenum compressedFormat = GetCompressedLevelFormat(textureObject, textureUploadTarget, level);
|
||||
*params = (compressedFormat != GL_NONE)
|
||||
? (GLint)compressedFormat
|
||||
@@ -3103,9 +3120,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
case GL_TEXTURE_INTERNAL_FORMAT:
|
||||
if (params) {
|
||||
// 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
|
||||
// that tag, so every level created by glTexImage*D - including one given a compressed
|
||||
// internalformat - still answers with its resolved storage format.
|
||||
// uncompressed format backing it (GL 4.6 core 8.11). glCompressedTexImage2D sets
|
||||
// that tag, and so does a glTexImage2D given a SPECIFIC compressed internalformat;
|
||||
// every other level answers with its resolved storage format.
|
||||
const GLenum compressedFormat = GetCompressedLevelFormat(textureObject, textureUploadTarget, level);
|
||||
*params = (GLfloat)((compressedFormat != GL_NONE)
|
||||
? compressedFormat
|
||||
@@ -3468,10 +3485,170 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
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,
|
||||
GLsizei height, GLenum format, GLsizei imageSize, const void* data) {
|
||||
// TODO: implement compressed upload - see CompressedTexImage2D_State.
|
||||
RecordUnsupportedCompressedFormat(__func__);
|
||||
// ======================= Converting ================================
|
||||
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,
|
||||
@@ -4472,6 +4649,14 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
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,
|
||||
GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* pixels) {
|
||||
auto textureObject = GetTextureObjectByName(texture, __func__);
|
||||
|
||||
@@ -37,6 +37,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
GLenum format, GLenum type, const void* pixels);
|
||||
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);
|
||||
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 TextureParameterfv(GLuint texture, GLenum pname, const GLfloat* params);
|
||||
void TextureParameteri(GLuint texture, GLenum pname, GLint param);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <MG_Backend/BackendObjects.h>
|
||||
#include <MG_Backend/DirectGLES/Managers.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/Getter/GL_Getter.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
|
||||
// though the same enum is accepted on a 2D target. The generic compressed formats carry no such
|
||||
// restriction and stay legal in 3D.
|
||||
|
||||
Reference in New Issue
Block a user