mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Fix] (GL): implement glGetCompressedTextureSubImage over the compressed block shadow
This commit is contained in:
@@ -1118,7 +1118,7 @@ DECLARE_GL_FUNCTION_HEAD(void, GetQueryBufferObjectiv, GLuint id, GLuint buffer,
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetQueryBufferObjectui64v, GLuint id, GLuint buffer, GLenum pname, GLintptr offset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetQueryBufferObjectui64v, id, buffer, pname, offset)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetQueryBufferObjectuiv, GLuint id, GLuint buffer, GLenum pname, GLintptr offset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetQueryBufferObjectuiv, id, buffer, pname, offset)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetTextureSubImage, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, GLsizei bufSize, void* pixels) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTextureSubImage, texture, level, xoffset, yoffset, zoffset, width, height, depth, format, type, bufSize, pixels)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetCompressedTextureSubImage, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei bufSize, void* pixels) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetCompressedTextureSubImage, texture, level, xoffset, yoffset, zoffset, width, height, depth, bufSize, pixels)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetCompressedTextureSubImage, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei bufSize, void* pixels) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetCompressedTextureSubImage, texture, level, xoffset, yoffset, zoffset, width, height, depth, bufSize, pixels)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnCompressedTexImage, GLenum target, GLint lod, GLsizei bufSize, void* pixels) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnCompressedTexImage, target, lod, bufSize, pixels)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnTexImage, GLenum target, GLint level, GLenum format, GLenum type, GLsizei bufSize, void* pixels) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnTexImage, target, level, format, type, bufSize, pixels)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetnUniformdv, GLuint program, GLint location, GLsizei bufSize, GLdouble* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetnUniformdv, program, location, bufSize, params)
|
||||
|
||||
@@ -3897,28 +3897,12 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
}
|
||||
|
||||
// The half glGetCompressedTexImage and glGetCompressedTextureImage share, factored out for the
|
||||
// same reason ValidateTextureImageQuery was: the by-name entry point must not drift away from
|
||||
// the by-target one's rules. bufSize < 0 means "no destination-size argument" - the by-target
|
||||
// form has none (GL 4.6 core 8.11 has the caller size it from GL_TEXTURE_COMPRESSED_IMAGE_SIZE),
|
||||
// so only the DSA form passes a real bound.
|
||||
void CopyCompressedTextureImageToClientOrPBO(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
|
||||
TextureUploadTarget uploadTarget, GLint level, GLsizei bufSize,
|
||||
void* pixels, const char* caller) {
|
||||
if (GetCompressedLevelFormat(textureObject, uploadTarget, level) == GL_NONE) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
||||
"Texture level is not stored in a compressed format."));
|
||||
return;
|
||||
}
|
||||
|
||||
const auto* textureMipmapObject = MG_State::GLState::AsMipmapTexture(textureObject.get());
|
||||
const SizeT imageSize =
|
||||
textureMipmapObject->GetMipmapCompressedByteSize(uploadTarget, static_cast<Uint>(level));
|
||||
const void* src = textureMipmapObject->MapMipmapCompressedImage(uploadTarget, static_cast<Uint>(level));
|
||||
if (!src || imageSize == 0) return;
|
||||
|
||||
// Where every compressed readback lands its bytes: the destination-size check, then either the
|
||||
// bound pixel pack buffer (pixels is an offset into it) or client memory. No pixel-store packing
|
||||
// here on purpose: GL 4.6 core 8.11 says the pixel storage modes are ignored for a compressed
|
||||
// image, which is also the only way the round trip stays byte-exact.
|
||||
static void WriteCompressedImageToClientOrPBO(const void* src, SizeT imageSize, GLsizei bufSize, void* pixels,
|
||||
const char* caller) {
|
||||
if (bufSize >= 0 && static_cast<SizeT>(bufSize) < imageSize) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
@@ -3948,11 +3932,34 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
return;
|
||||
}
|
||||
|
||||
// No pixel-store packing here on purpose: GL 4.6 core 8.11 says the pixel storage modes are
|
||||
// ignored for a compressed image, which is also the only way the round trip stays byte-exact.
|
||||
if (pixels) Memcpy(pixels, src, imageSize);
|
||||
}
|
||||
|
||||
// The half glGetCompressedTexImage and glGetCompressedTextureImage share, factored out for the
|
||||
// same reason ValidateTextureImageQuery was: the by-name entry point must not drift away from
|
||||
// the by-target one's rules. bufSize < 0 means "no destination-size argument" - the by-target
|
||||
// form has none (GL 4.6 core 8.11 has the caller size it from GL_TEXTURE_COMPRESSED_IMAGE_SIZE),
|
||||
// so only the DSA form passes a real bound.
|
||||
void CopyCompressedTextureImageToClientOrPBO(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
|
||||
TextureUploadTarget uploadTarget, GLint level, GLsizei bufSize,
|
||||
void* pixels, const char* caller) {
|
||||
if (GetCompressedLevelFormat(textureObject, uploadTarget, level) == GL_NONE) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
||||
"Texture level is not stored in a compressed format."));
|
||||
return;
|
||||
}
|
||||
|
||||
const auto* textureMipmapObject = MG_State::GLState::AsMipmapTexture(textureObject.get());
|
||||
const SizeT imageSize =
|
||||
textureMipmapObject->GetMipmapCompressedByteSize(uploadTarget, static_cast<Uint>(level));
|
||||
const void* src = textureMipmapObject->MapMipmapCompressedImage(uploadTarget, static_cast<Uint>(level));
|
||||
if (!src || imageSize == 0) return;
|
||||
|
||||
WriteCompressedImageToClientOrPBO(src, imageSize, bufSize, pixels, caller);
|
||||
}
|
||||
|
||||
void GetCompressedTexImage_State(GLenum target, GLint level, void* img) {
|
||||
const auto textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target);
|
||||
const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
|
||||
@@ -6746,6 +6753,83 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
__func__);
|
||||
}
|
||||
|
||||
// GL 4.6 core 8.11.4. Like glGetCompressedTextureImage this answers out of the CPU shadow of the
|
||||
// compressed blocks (no backend was handed them), cropped to whole blocks: the region has to
|
||||
// start on a block boundary and cover whole blocks except where it reaches the level's edge.
|
||||
void GetCompressedTextureSubImage(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset,
|
||||
GLsizei width, GLsizei height, GLsizei depth, GLsizei bufSize, void* pixels) {
|
||||
auto textureObject = GetTextureObjectByName(texture, __func__);
|
||||
if (!textureObject) return;
|
||||
IntVec3 levelExtent;
|
||||
if (!ValidateTextureSubImageRegion(textureObject, level, xoffset, yoffset, zoffset, width, height, depth,
|
||||
levelExtent, __func__)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// A cube map's faces are separate images; every other target keeps its layers in one.
|
||||
const Bool isCubeMap = textureObject->GetTarget() == TextureTarget::TextureCubeMap;
|
||||
const auto imageTarget = [&](GLint z) {
|
||||
return isCubeMap
|
||||
? static_cast<TextureUploadTarget>(static_cast<Int>(TextureUploadTarget::CubeMapPositiveX) + z)
|
||||
: GetPrimaryUploadTarget(textureObject);
|
||||
};
|
||||
const GLenum compressedFormat = GetCompressedLevelFormat(textureObject, imageTarget(zoffset), level);
|
||||
if (compressedFormat == GL_NONE) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"Texture level is not stored in a compressed format."));
|
||||
return;
|
||||
}
|
||||
const auto blockInfo = MG_Util::GetCompressedFormatInfo(compressedFormat);
|
||||
const Int blockWidth = static_cast<Int>(blockInfo.blockWidth);
|
||||
const Int blockHeight = static_cast<Int>(blockInfo.blockHeight);
|
||||
if (blockWidth == 0 || blockHeight == 0) return;
|
||||
if (xoffset % blockWidth != 0 || yoffset % blockHeight != 0 ||
|
||||
(width % blockWidth != 0 && xoffset + width != levelExtent.x()) ||
|
||||
(height % blockHeight != 0 && yoffset + height != levelExtent.y())) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"The region is not aligned to the compressed block size."));
|
||||
return;
|
||||
}
|
||||
|
||||
const auto blocksFor = [](Int extent, Int block) { return static_cast<SizeT>((extent + block - 1) / block); };
|
||||
const SizeT levelRowBlocks = blocksFor(levelExtent.x(), blockWidth);
|
||||
const SizeT levelImageBlocks = levelRowBlocks * blocksFor(levelExtent.y(), blockHeight);
|
||||
const SizeT rowBlocks = blocksFor(width, blockWidth);
|
||||
const SizeT rows = blocksFor(height, blockHeight);
|
||||
const SizeT rowBytes = rowBlocks * blockInfo.blockByteSize;
|
||||
Vector<Uint8> region(rowBytes * rows * static_cast<SizeT>(depth));
|
||||
|
||||
const auto* textureMipmapObject = MG_State::GLState::AsMipmapTexture(textureObject.get());
|
||||
for (GLsizei z = 0; z < depth && rows > 0 && rowBytes > 0; ++z) {
|
||||
const auto target = imageTarget(zoffset + z);
|
||||
const auto* image = static_cast<const Uint8*>(
|
||||
textureMipmapObject->MapMipmapCompressedImage(target, static_cast<Uint>(level)));
|
||||
const SizeT imageSize = textureMipmapObject->GetMipmapCompressedByteSize(target, static_cast<Uint>(level));
|
||||
// Layers of one image are stored one after another, each a full level's worth of blocks.
|
||||
const SizeT layer = isCubeMap ? 0 : static_cast<SizeT>(zoffset + z);
|
||||
const SizeT firstBlock = layer * levelImageBlocks +
|
||||
static_cast<SizeT>(yoffset / blockHeight) * levelRowBlocks +
|
||||
static_cast<SizeT>(xoffset / blockWidth);
|
||||
const SizeT endByte = (firstBlock + (rows - 1) * levelRowBlocks) * blockInfo.blockByteSize + rowBytes;
|
||||
if (!image || endByte > imageSize) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"Texture image is not stored in a compressed format."));
|
||||
return;
|
||||
}
|
||||
for (SizeT row = 0; row < rows; ++row) {
|
||||
Memcpy(region.data() + (static_cast<SizeT>(z) * rows + row) * rowBytes,
|
||||
image + (firstBlock + row * levelRowBlocks) * blockInfo.blockByteSize, rowBytes);
|
||||
}
|
||||
}
|
||||
WriteCompressedImageToClientOrPBO(region.data(), region.size(), bufSize, pixels, __func__);
|
||||
}
|
||||
|
||||
// A buffer texture carries none of the sampler or level state these queries report. Reached by
|
||||
// name there is no target token to blame, so the wrong object is INVALID_OPERATION rather than
|
||||
// the INVALID_ENUM the target forms report for an unaccepted target (GL 4.6 core 8.11).
|
||||
|
||||
@@ -74,6 +74,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
void TextureBufferRange(GLuint texture, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size);
|
||||
void GetTextureSubImage(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width,
|
||||
GLsizei height, GLsizei depth, GLenum format, GLenum type, GLsizei bufSize, void* pixels);
|
||||
void GetCompressedTextureSubImage(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset,
|
||||
GLsizei width, GLsizei height, GLsizei depth, GLsizei bufSize, void* pixels);
|
||||
void GetTextureParameterfv(GLuint texture, GLenum pname, GLfloat* params);
|
||||
void GetTextureParameterIiv(GLuint texture, GLenum pname, GLint* params);
|
||||
void GetTextureParameterIuiv(GLuint texture, GLenum pname, GLuint* params);
|
||||
|
||||
@@ -2759,6 +2759,48 @@ namespace {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// glGetCompressedTextureSubImage was a stub. It returns whole blocks of the stored image (GL 4.6 core
|
||||
// 8.11.4): the right-hand block column of an 8x8 RGTC1 image is the blocks at bytes 8 and 24, and the
|
||||
// region errors match glGetTextureSubImage's.
|
||||
TEST_F(TextureTest, GetCompressedTextureSubImageCropsWholeBlocks) {
|
||||
const GLuint texture = MakeCompressedRgtc1Texture8x8();
|
||||
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 column[16] = {};
|
||||
MG_Impl::GLImpl::GetCompressedTextureSubImage(texture, 0, 4, 0, 0, 4, 8, 1, sizeof(column), column);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
EXPECT_EQ(std::memcmp(column, whole + 8, 8), 0);
|
||||
EXPECT_EQ(std::memcmp(column + 8, whole + 24, 8), 0);
|
||||
|
||||
Uint8 all[kRgtc1Size8x8] = {};
|
||||
MG_Impl::GLImpl::GetCompressedTextureSubImage(texture, 0, 0, 0, 0, 8, 8, 1, sizeof(all), all);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
EXPECT_EQ(std::memcmp(all, whole, sizeof(all)), 0);
|
||||
|
||||
MG_Impl::GLImpl::GetCompressedTextureSubImage(texture, 0, 2, 0, 0, 4, 4, 1, sizeof(all), all);
|
||||
ExpectSingleGlError(GL_INVALID_VALUE); // not on a block boundary
|
||||
MG_Impl::GLImpl::GetCompressedTextureSubImage(texture, 0, 4, 4, 0, 8, 8, 1, sizeof(all), all);
|
||||
ExpectSingleGlError(GL_INVALID_VALUE); // past the level
|
||||
MG_Impl::GLImpl::GetCompressedTextureSubImage(texture, 0, 0, 0, 1, 8, 8, 2, sizeof(all), all);
|
||||
ExpectSingleGlError(GL_INVALID_VALUE); // a 2D texture has one layer
|
||||
MG_Impl::GLImpl::GetCompressedTextureSubImage(texture, 0, 0, 0, 0, 8, 8, 1, 1, all);
|
||||
ExpectSingleGlError(GL_INVALID_OPERATION); // bufSize too small
|
||||
|
||||
GLuint plain = 0;
|
||||
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_2D, 1, &plain);
|
||||
MG_Impl::GLImpl::TextureStorage2D(plain, 1, GL_RGBA8, 4, 4);
|
||||
MG_Impl::GLImpl::GetCompressedTextureSubImage(plain, 0, 0, 0, 0, 4, 4, 1, sizeof(all), all);
|
||||
ExpectSingleGlError(GL_INVALID_OPERATION); // not compressed
|
||||
|
||||
MG_Impl::GLImpl::DeleteTextures(1, &plain);
|
||||
MG_Impl::GLImpl::DeleteTextures(1, &texture);
|
||||
DrainPendingGlErrors();
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
Reference in New Issue
Block a user