From f896c7396f4b707bf777562035aea52f92d232d5 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Thu, 16 Jul 2026 10:54:48 -0400 Subject: [PATCH] [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 --- .../MG_Impl/GLImpl/Texture/GL_Texture.cpp | 23 ++- MobileGL/MG_Test/Texture/TextureTest.cpp | 161 ++++++++++++++++++ 2 files changed, 183 insertions(+), 1 deletion(-) diff --git a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp index 88201752..d0e958ea 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp +++ b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp @@ -779,6 +779,12 @@ namespace MobileGL::MG_Impl::GLImpl { MOBILEGL_ASSERT(nullptr != static_cast(textureObject.get()), "Texture object here should always be an object with mipmap"); auto textureMipmapObject = static_cast(textureObject.get()); + if (static_cast(level) >= textureMipmapObject->GetMipmapLevelCount()) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeUnique("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(texelSize.x()) * internalBpp; const SizeT destSliceSize = static_cast(texelSize.y()) * destRowSize; + if (xoffset + width > static_cast(texelSize.x()) || + yoffset + height > static_cast(texelSize.y()) || + zoffset + depth > static_cast(texelSize.z())) { + MGLOG_E("TexSubImage3D_State: Specified region exceeds texture level dimensions"); + free(processedPixels); + return; + } + const auto* srcData = static_cast(processedPixels); Uint8* destData = static_cast(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(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, diff --git a/MobileGL/MG_Test/Texture/TextureTest.cpp b/MobileGL/MG_Test/Texture/TextureTest.cpp index c3039273..ab950d29 100644 --- a/MobileGL/MG_Test/Texture/TextureTest.cpp +++ b/MobileGL/MG_Test/Texture/TextureTest.cpp @@ -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(textureObject.get()); + return static_cast(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);