[Fix] (MG_Impl/GLImpl): TexImage3D - apply ConvertInternalFormatToSized like 2D/1D so unsized-internal 3D uploads get channel/type conversion, skip proxy shadow allocation, auto-generate mipmaps; TexSubImage3D - bound level and region against the target mip

This commit is contained in:
2026-07-16 10:54:48 -04:00
parent 1cefb9780b
commit f896c7396f
2 changed files with 183 additions and 1 deletions
+22 -1
View File
@@ -779,6 +779,12 @@ namespace MobileGL::MG_Impl::GLImpl {
MOBILEGL_ASSERT(nullptr != static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get()),
"Texture object here should always be an object with mipmap");
auto textureMipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
if (static_cast<Uint>(level) >= textureMipmapObject->GetMipmapLevelCount()) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Texture level is out of range."));
return;
}
const void* originalPixels = pixels;
const auto& pixelUnpackBufferObject =
@@ -811,6 +817,14 @@ namespace MobileGL::MG_Impl::GLImpl {
const SizeT destRowSize = static_cast<SizeT>(texelSize.x()) * internalBpp;
const SizeT destSliceSize = static_cast<SizeT>(texelSize.y()) * destRowSize;
if (xoffset + width > static_cast<GLsizei>(texelSize.x()) ||
yoffset + height > static_cast<GLsizei>(texelSize.y()) ||
zoffset + depth > static_cast<GLsizei>(texelSize.z())) {
MGLOG_E("TexSubImage3D_State: Specified region exceeds texture level dimensions");
free(processedPixels);
return;
}
const auto* srcData = static_cast<const Uint8*>(processedPixels);
Uint8* destData = static_cast<Uint8*>(textureMipmapObject->MapMipmapData(textureUploadTarget, level));
if (destData) {
@@ -1378,6 +1392,8 @@ namespace MobileGL::MG_Impl::GLImpl {
// target and data is not evenly divisible into the number of bytes needed to store in memory a datum
// indicated by type.
// ======================= Processing ================================
textureInternalFormat =
MG_Util::ConvertInternalFormatToSized(textureInternalFormat, textureInputFormat, texturePixelDataType);
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget);
@@ -1419,7 +1435,11 @@ namespace MobileGL::MG_Impl::GLImpl {
auto textureMipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
// Allocate in TextureObject
textureMipmapObject->AllocateStorage(textureUploadTarget, level, {{width, height, depth}, internalBytes});
if (isProxy) {
MGLOG_D("%s: isProxy = true, not allocating", __func__);
} else {
textureMipmapObject->AllocateStorage(textureUploadTarget, level, {{width, height, depth}, internalBytes});
}
if (!originalPixels) {
MGLOG_D("%s: No input pixel and no PBO bound, no pixel transfer", __func__);
@@ -1446,6 +1466,7 @@ namespace MobileGL::MG_Impl::GLImpl {
textureMipmapObject->MarkStorageDirty(textureUploadTarget, level, true);
free(processedPixels);
MaybeAutoGenerateMipmap(target, textureObject, isProxy, level);
}
void TexImage2D_State(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border,
+161
View File
@@ -774,6 +774,167 @@ TEST_F(TextureTest, TextureStorage3DAndSubImageModifyNamedObjectOnly) {
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
}
namespace {
const Uint8* GetBoundTexture3DLevelBytes(GLuint texture, Uint level = 0) {
const auto textureObject = MG_State::pGLContext->GetTextureObject(texture);
auto* mipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
return static_cast<const Uint8*>(mipmapObject->MapMipmapData(TextureUploadTarget::Texture3D, level));
}
} // namespace
TEST_F(TextureTest, BoundTexImage3DUnsizedRgbaInfersRgba8AndUnpacksBgra8888Rev) {
GLuint texture = 0;
MG_Impl::GLImpl::GenTextures(1, &texture);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_3D, texture);
const Uint8 pixels[] = {
10, 20, 30, 40,
50, 60, 70, 80,
90, 100, 110, 120,
130, 140, 150, 160,
};
MG_Impl::GLImpl::TexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, 2, 1, 2, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, pixels);
const auto textureObject = MG_State::pGLContext->GetTextureObject(texture);
EXPECT_EQ(textureObject->GetFormat(), TextureInternalFormat::RGBA8);
const auto* stored = GetBoundTexture3DLevelBytes(texture);
ASSERT_NE(stored, nullptr);
const Uint8 expected[] = {
30, 20, 10, 40,
70, 60, 50, 80,
110, 100, 90, 120,
150, 140, 130, 160,
};
for (SizeT i = 0; i < sizeof(expected); ++i) {
EXPECT_EQ(stored[i], expected[i]) << "byte " << i;
}
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
}
TEST_F(TextureTest, BoundTexImage3DHonorsImageHeightAndSkipImages) {
GLuint texture = 0;
MG_Impl::GLImpl::GenTextures(1, &texture);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_3D, texture);
// Source cuboid is 2x3 per image (IMAGE_HEIGHT = 3) with one leading image skipped;
// the upload reads a 2x2x2 sub-cuboid.
const Uint8 pixels[] = {
// image 0 (skipped)
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
// image 1: rows 0-1 are slice 0, row 2 is padding
1, 2, 3, 4, 5, 6, 7, 8,
9, 10, 11, 12, 13, 14, 15, 16,
0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
// image 2: rows 0-1 are slice 1, row 2 is padding
17, 18, 19, 20, 21, 22, 23, 24,
25, 26, 27, 28, 29, 30, 31, 32,
0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
};
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_IMAGE_HEIGHT, 3);
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_SKIP_IMAGES, 1);
MG_Impl::GLImpl::TexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, 2, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0);
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_SKIP_IMAGES, 0);
const auto* stored = GetBoundTexture3DLevelBytes(texture);
ASSERT_NE(stored, nullptr);
const Uint8 expected[] = {
1, 2, 3, 4, 5, 6, 7, 8,
9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24,
25, 26, 27, 28, 29, 30, 31, 32,
};
for (SizeT i = 0; i < sizeof(expected); ++i) {
EXPECT_EQ(stored[i], expected[i]) << "byte " << i;
}
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
}
TEST_F(TextureTest, BoundTexImage3DConvertsRedToRgba8WithImageHeightAndSkips) {
GLuint texture = 0;
MG_Impl::GLImpl::GenTextures(1, &texture);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_3D, texture);
// Source cuboid: ROW_LENGTH = 3 (1-byte texels, alignment 1), IMAGE_HEIGHT = 2,
// skip 1 image, 0 rows, 1 pixel; upload a 2x1x2 sub-cuboid of GL_RED texels.
const Uint8 pixels[] = {
// image 0 (skipped)
90, 91, 92,
93, 94, 95,
// image 1: row 0 holds slice 0 at x offset 1, row 1 is padding
80, 11, 12,
81, 82, 83,
// image 2: row 0 holds slice 1 at x offset 1, row 1 is padding
84, 21, 22,
85, 86, 87,
};
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 1);
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ROW_LENGTH, 3);
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_SKIP_PIXELS, 1);
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_IMAGE_HEIGHT, 2);
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_SKIP_IMAGES, 1);
MG_Impl::GLImpl::TexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, 2, 1, 2, 0, GL_RED, GL_UNSIGNED_BYTE, pixels);
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 4);
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ROW_LENGTH, 0);
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0);
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_SKIP_IMAGES, 0);
const auto* stored = GetBoundTexture3DLevelBytes(texture);
ASSERT_NE(stored, nullptr);
const Uint8 expected[] = {
11, 0, 0, 255, 12, 0, 0, 255,
21, 0, 0, 255, 22, 0, 0, 255,
};
for (SizeT i = 0; i < sizeof(expected); ++i) {
EXPECT_EQ(stored[i], expected[i]) << "byte " << i;
}
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
}
TEST_F(TextureTest, BoundTexSubImage3DUnpacksPackedBgra8888RevIntoCorrectSlice) {
GLuint texture = 0;
MG_Impl::GLImpl::GenTextures(1, &texture);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_3D, texture);
const Uint8 zeros[2 * 2 * 2 * 4] = {};
MG_Impl::GLImpl::TexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, 2, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, zeros);
const Uint8 pixels[] = {10, 20, 30, 40};
MG_Impl::GLImpl::TexSubImage3D(GL_TEXTURE_3D, 0, 1, 1, 1, 1, 1, 1, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, pixels);
const auto* stored = GetBoundTexture3DLevelBytes(texture);
ASSERT_NE(stored, nullptr);
Uint8 expected[2 * 2 * 2 * 4] = {};
expected[28] = 30;
expected[29] = 20;
expected[30] = 10;
expected[31] = 40;
for (SizeT i = 0; i < sizeof(expected); ++i) {
EXPECT_EQ(stored[i], expected[i]) << "byte " << i;
}
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
}
TEST_F(TextureTest, BoundTexSubImage3DRejectsOutOfRangeLevel) {
GLuint texture = 0;
MG_Impl::GLImpl::GenTextures(1, &texture);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_3D, texture);
const Uint8 zeros[2 * 2 * 2 * 4] = {};
MG_Impl::GLImpl::TexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, 2, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, zeros);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
const Uint8 pixels[] = {1, 2, 3, 4};
MG_Impl::GLImpl::TexSubImage3D(GL_TEXTURE_3D, 3, 0, 0, 0, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_VALUE);
}
TEST_F(TextureTest, NamedTextureVectorParametersAndGettersWorkWithoutBinding) {
GLuint texture = 0;
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_2D, 1, &texture);