mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +09:00
[Fix, Test] (GLImpl): require only the requested level to exist in glGetTexImage
This commit is contained in:
@@ -4200,8 +4200,14 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For a cube map this is exactly cube completeness: IsComplete() wants all six faces.
|
// GL 4.6 core 8.11.4 names cube completeness as the only completeness a readback requires,
|
||||||
if (!textureObject->IsComplete()) {
|
// and for a cube map that is exactly what IsComplete() answers (all six faces defined at
|
||||||
|
// every level). It must not speak for any other target: on a mip chain it also rejects
|
||||||
|
// "level N defined, the levels below it not", which is a perfectly readable texture at
|
||||||
|
// level N - and the shape glClearTexImage's conformance cases build, since they define
|
||||||
|
// only the level they clear. The requested level's own existence is checked below.
|
||||||
|
if ((target == TextureTarget::TextureCubeMap || target == TextureTarget::TextureCubeMapArray) &&
|
||||||
|
!textureObject->IsComplete()) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidOperation,
|
ErrorCode::InvalidOperation,
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "Texture is incomplete"));
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "Texture is incomplete"));
|
||||||
@@ -4260,11 +4266,27 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
const auto* textureMipmapObject =
|
const auto* textureMipmapObject =
|
||||||
static_cast<const MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
|
static_cast<const MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
|
||||||
const auto& uploadTargets = textureObject->GetUploadTargets();
|
const auto& uploadTargets = textureObject->GetUploadTargets();
|
||||||
if (!uploadTargets.empty() && static_cast<Uint>(level) < textureMipmapObject->GetMipmapLevelCount()) {
|
// The half of the completeness gate above that GL does keep: the REQUESTED level has
|
||||||
|
// to hold an image. A name that was never given one carries no levels at all (which is
|
||||||
|
// also what an Unknown internal format answers), and a chain grown to reach level N
|
||||||
|
// leaves every level below it at {0, 0, 0}.
|
||||||
|
if (uploadTargets.empty() || static_cast<Uint>(level) >= textureMipmapObject->GetMipmapLevelCount()) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "Texture level has no image to read back."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto texelSize = textureMipmapObject->GetMipmapTexelSize(uploadTargets[0], level);
|
||||||
|
if (texelSize.x() <= 0 || texelSize.y() <= 0 || texelSize.z() <= 0) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "Texture level has no image to read back."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Tightly packed, and summed over every face because a cube map query returns all
|
// Tightly packed, and summed over every face because a cube map query returns all
|
||||||
// six. Pack pixel-store state only ever grows this, so a request rejected here
|
// six. Pack pixel-store state only ever grows this, so a request rejected here
|
||||||
// could not have fit under any packing.
|
// could not have fit under any packing.
|
||||||
const auto texelSize = textureMipmapObject->GetMipmapTexelSize(uploadTargets[0], level);
|
|
||||||
const SizeT required = MG_Util::CalculateInputTextureImageSize(textureInputFormat,
|
const SizeT required = MG_Util::CalculateInputTextureImageSize(textureInputFormat,
|
||||||
texturePixelDataType, texelSize) *
|
texturePixelDataType, texelSize) *
|
||||||
uploadTargets.size();
|
uploadTargets.size();
|
||||||
@@ -4288,7 +4310,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1317,6 +1317,56 @@ TEST_F(TextureTest, GetTextureImageReadsNamedObjectWithoutBinding) {
|
|||||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GL 4.6 core 8.11.4 asks a readback for cube completeness and nothing else, so a mip chain whose
|
||||||
|
// levels BELOW the requested one were never defined is still readable at that level - which is
|
||||||
|
// exactly the shape ARB_clear_texture's conformance cases build (they define only the level they
|
||||||
|
// clear). The whole-chain completeness gate used to answer INVALID_OPERATION here.
|
||||||
|
TEST_F(TextureTest, GetTexImageReadsALevelWhoseLowerLevelsWereNeverDefined) {
|
||||||
|
GLuint texture = 0;
|
||||||
|
MG_Impl::GLImpl::GenTextures(1, &texture);
|
||||||
|
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture);
|
||||||
|
|
||||||
|
const Uint8 pixels[] = {
|
||||||
|
61, 62, 63, 64,
|
||||||
|
71, 72, 73, 74,
|
||||||
|
};
|
||||||
|
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 2, GL_RGBA8, 2, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
|
||||||
|
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
|
||||||
|
Uint8 output[sizeof(pixels)] = {};
|
||||||
|
MG_Impl::GLImpl::GetTexImage(GL_TEXTURE_2D, 2, GL_RGBA, GL_UNSIGNED_BYTE, output);
|
||||||
|
|
||||||
|
EXPECT_EQ(std::memcmp(output, pixels, sizeof(pixels)), 0);
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The other half of the same rule: loosening the chain-wide check must not let a level that holds
|
||||||
|
// no image at all through. Level 0 exists as a chain slot once level 2 is defined, but nothing ever
|
||||||
|
// gave it an image, so it stays INVALID_OPERATION - as does a level past the end of the chain and a
|
||||||
|
// texture that was never given any image whatsoever.
|
||||||
|
TEST_F(TextureTest, GetTexImageStillRejectsALevelThatHoldsNoImage) {
|
||||||
|
GLuint texture = 0;
|
||||||
|
MG_Impl::GLImpl::GenTextures(1, &texture);
|
||||||
|
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture);
|
||||||
|
|
||||||
|
Uint8 output[4] = {};
|
||||||
|
|
||||||
|
// No image at all yet: the chain carries no levels.
|
||||||
|
MG_Impl::GLImpl::GetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, output);
|
||||||
|
ExpectSingleGlError(GL_INVALID_OPERATION);
|
||||||
|
|
||||||
|
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 2, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||||
|
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
|
||||||
|
// Inside the chain, but never defined.
|
||||||
|
MG_Impl::GLImpl::GetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, output);
|
||||||
|
ExpectSingleGlError(GL_INVALID_OPERATION);
|
||||||
|
|
||||||
|
// Past the end of the chain.
|
||||||
|
MG_Impl::GLImpl::GetTexImage(GL_TEXTURE_2D, 3, GL_RGBA, GL_UNSIGNED_BYTE, output);
|
||||||
|
ExpectSingleGlError(GL_INVALID_OPERATION);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(TextureTest, GetTextureSubImageReadsFullNamedLevelWithoutBinding) {
|
TEST_F(TextureTest, GetTextureSubImageReadsFullNamedLevelWithoutBinding) {
|
||||||
GLuint texture = 0;
|
GLuint texture = 0;
|
||||||
GLuint boundTexture = 0;
|
GLuint boundTexture = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user