From a3dbe234d7163a5024d3fc5c5271ab78f1207a03 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Thu, 20 Aug 2026 13:27:40 -0400 Subject: [PATCH] [Fix, Test] (GLImpl, MG_IntegrationTest): answer glGetTexLevelParameter for buffer textures instead of erroring --- .../MG_Impl/GLImpl/Texture/GL_Texture.cpp | 103 ++++++++++++++++-- .../Scenarios/BufferTextureScenario.cpp | 95 ++++++++++++++++ 2 files changed, 189 insertions(+), 9 deletions(-) diff --git a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp index db15206f..16a459be 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp +++ b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp @@ -661,21 +661,42 @@ namespace MobileGL::MG_Impl::GLImpl { "Compressed texture formats are not supported.")); } - // glGetTexLevelParameter{i,f}v answers WIDTH/HEIGHT/DEPTH out of the mipmap chain. The only - // other storage type the state layer knows is GL_TEXTURE_BUFFER (TextureStorageType is - // {Mipmap, Buffer}), whose level geometry this stack does not track yet. Report that instead - // of throwing: THROW_UNIMPL_EXCEPTION unwinds a C++ exception through the C GL ABI and takes - // the process down, which is never an acceptable answer to a query - see the same reasoning - // above for the compressed-format path. + // GL_TEXTURE_WIDTH of a buffer texture: how many texels of the texture's internal format fit + // in the buffer range it addresses, CLAMPED to GL_MAX_TEXTURE_BUFFER_SIZE. Attaching a larger + // buffer is legal (GL 4.6 core 8.9) - the texture simply addresses the first + // MAX_TEXTURE_BUFFER_SIZE texels of it, and that clamped count is what WIDTH reports. + // + // GL_TEXTURE_BUFFER_SIZE is deliberately NOT clamped the same way: it reports the range in + // basic machine units exactly as glTexBuffer/glTexBufferRange were given it. Swapping the two + // fails KHR-GL43.texture_buffer.texture_buffer_max_size in the opposite direction. + GLint GetBufferTextureTexelWidth(const MG_State::GLState::ITextureObject* textureObject) { + const SizeT texelByteSize = MG_Util::GetSizedInternalFormatSizeInBytes(textureObject->GetFormat()); + // A format with no known footprint has no texel count to report; answering 0 beats + // dividing by it. + if (texelByteSize == 0) return 0; + const auto* bufferTextureObject = + static_cast(textureObject); + const SizeT texelCount = bufferTextureObject->GetBufferRangeSizeInBytes() / texelByteSize; + const SizeT maxTexelCount = static_cast( + std::max(0, MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxTextureBufferSize)); + return static_cast(std::min(texelCount, maxTexelCount)); + } + + // glGetTexLevelParameter{i,f}v answers WIDTH/HEIGHT/DEPTH out of the mipmap chain, and (since + // the buffer-texture arms above) out of the attached buffer range for GL_TEXTURE_BUFFER. This + // is what is left: a storage class with no level geometry at all. Report it instead of + // throwing - THROW_UNIMPL_EXCEPTION unwinds a C++ exception through the C GL ABI and takes the + // process down, which is never an acceptable answer to a query - see the same reasoning above + // for the compressed-format path. void RecordUnsupportedLevelQueryStorage(const char* caller, GLenum pname) { - MGLOG_W_ONCE("%s: glGetTexLevelParameter(pname=%s) is not implemented for texture-buffer " - "storage; recording GL_INVALID_OPERATION instead of terminating", + MGLOG_W_ONCE("%s: glGetTexLevelParameter(pname=%s) is not implemented for this texture's " + "storage class; recording GL_INVALID_OPERATION instead of terminating", caller, MG_Util::ConvertGLEnumToString(pname).c_str()); MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique( "MG_Impl/GLImpl", caller, - "Level queries are not supported for texture-buffer storage.")); + "Level queries are not supported for this texture's storage class.")); } } // namespace @@ -3117,6 +3138,9 @@ namespace MobileGL::MG_Impl::GLImpl { *params = textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, level).x(); break; } + case TextureStorageType::Buffer: + *params = GetBufferTextureTexelWidth(textureObject.get()); + break; default: RecordUnsupportedLevelQueryStorage("GetTexLevelParameteriv_State", pname); break; @@ -3132,6 +3156,9 @@ namespace MobileGL::MG_Impl::GLImpl { *params = textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, level).y(); break; } + case TextureStorageType::Buffer: + *params = 1; // a buffer texture is one-dimensional + break; default: RecordUnsupportedLevelQueryStorage("GetTexLevelParameteriv_State", pname); break; @@ -3147,6 +3174,9 @@ namespace MobileGL::MG_Impl::GLImpl { *params = textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, level).z(); break; } + case TextureStorageType::Buffer: + *params = 1; // a buffer texture is one-dimensional + break; default: RecordUnsupportedLevelQueryStorage("GetTexLevelParameteriv_State", pname); break; @@ -3216,6 +3246,31 @@ namespace MobileGL::MG_Impl::GLImpl { } break; } + case GL_TEXTURE_BUFFER_SIZE: + case GL_TEXTURE_BUFFER_OFFSET: { + // GL 4.6 core 8.9: both describe the window of the attached buffer a GL_TEXTURE_BUFFER + // texture addresses, so there is nothing to report for any other storage - which is + // INVALID_OPERATION, the same shape GL_TEXTURE_COMPRESSED_IMAGE_SIZE guards itself with + // above. + if (textureObject->GetStorageType() != TextureStorageType::Buffer) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique( + "MG_Impl/GLImpl", "GetTexLevelParameteriv_State", + "GL_TEXTURE_BUFFER_SIZE / GL_TEXTURE_BUFFER_OFFSET need a buffer texture.")); + return; + } + if (params) { + const auto* bufferTextureObject = + static_cast(textureObject.get()); + // Basic machine units, and UNCLAMPED - see GetBufferTextureTexelWidth for why this + // half does not take the GL_MAX_TEXTURE_BUFFER_SIZE clamp that WIDTH does. + *params = static_cast(pname == GL_TEXTURE_BUFFER_SIZE + ? bufferTextureObject->GetBufferRangeSizeInBytes() + : bufferTextureObject->GetBufferRangeOffset()); + } + break; + } default: MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", "GetTexLevelParameteriv_State", @@ -3255,6 +3310,9 @@ namespace MobileGL::MG_Impl::GLImpl { *params = (GLfloat)textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, level).x(); break; } + case TextureStorageType::Buffer: + *params = (GLfloat)GetBufferTextureTexelWidth(textureObject.get()); + break; default: RecordUnsupportedLevelQueryStorage("GetTexLevelParameterfv_State", pname); break; @@ -3270,6 +3328,9 @@ namespace MobileGL::MG_Impl::GLImpl { *params = (GLfloat)textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, level).y(); break; } + case TextureStorageType::Buffer: + *params = 1.0f; // a buffer texture is one-dimensional + break; default: RecordUnsupportedLevelQueryStorage("GetTexLevelParameterfv_State", pname); break; @@ -3285,6 +3346,9 @@ namespace MobileGL::MG_Impl::GLImpl { *params = (GLfloat)textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, level).z(); break; } + case TextureStorageType::Buffer: + *params = 1.0f; // a buffer texture is one-dimensional + break; default: RecordUnsupportedLevelQueryStorage("GetTexLevelParameterfv_State", pname); break; @@ -3352,6 +3416,27 @@ namespace MobileGL::MG_Impl::GLImpl { } break; } + case GL_TEXTURE_BUFFER_SIZE: + case GL_TEXTURE_BUFFER_OFFSET: { + // See GetTexLevelParameteriv_State: both describe the attached buffer range of a + // GL_TEXTURE_BUFFER texture, so any other storage makes the query INVALID_OPERATION. + if (textureObject->GetStorageType() != TextureStorageType::Buffer) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique( + "MG_Impl/GLImpl", "GetTexLevelParameterfv_State", + "GL_TEXTURE_BUFFER_SIZE / GL_TEXTURE_BUFFER_OFFSET need a buffer texture.")); + return; + } + if (params) { + const auto* bufferTextureObject = + static_cast(textureObject.get()); + *params = static_cast(pname == GL_TEXTURE_BUFFER_SIZE + ? bufferTextureObject->GetBufferRangeSizeInBytes() + : bufferTextureObject->GetBufferRangeOffset()); + } + break; + } default: MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", "GetTexLevelParameterfv_State", diff --git a/MobileGL/MG_IntegrationTest/Scenarios/BufferTextureScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/BufferTextureScenario.cpp index 83c62bc5..392ebad8 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/BufferTextureScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/BufferTextureScenario.cpp @@ -299,4 +299,99 @@ void main() { EXPECT_EQ(FirstGLError(), 0u); } + // glGetTexLevelParameter used to refuse EVERY pname on a buffer texture: WIDTH/HEIGHT/DEPTH + // fell out of a mipmap-only switch as GL_INVALID_OPERATION, and GL_TEXTURE_BUFFER_SIZE / + // GL_TEXTURE_BUFFER_OFFSET were not in the switch at all, so they came back GL_INVALID_ENUM. + // KHR-GL43.texture_buffer wraps both queries in GLU_EXPECT_NO_ERROR, so the error alone fails + // the case before any value is compared. + // + // The two halves report DIFFERENT units and only one of them is clamped, which is the thing + // easiest to get backwards: WIDTH is a TEXEL count clamped to GL_MAX_TEXTURE_BUFFER_SIZE, + // BUFFER_SIZE is the range in basic machine units exactly as it was given. + TEST_F(BufferTextureScenario, LevelQueriesDescribeTheAttachedBufferRange) { + if (!Ready()) return; + FirstGLError(); + + GLint offsetAlignment = 1; + glGetIntegerv(GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT, &offsetAlignment); + if (offsetAlignment < 1) offsetAlignment = 1; + GLint maxTexels = 0; + glGetIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE, &maxTexels); + ASSERT_EQ(FirstGLError(), 0u); + ASSERT_GT(maxTexels, 0) << "an OpenGL 4.x context may not advertise a zero buffer-texture limit"; + + constexpr GLint kTexelBytes = 4; // GL_RGBA8 + const GLsizeiptr rangeOffset = static_cast(offsetAlignment); + const GLsizeiptr rangeBytes = 32 * kTexelBytes; + // Deliberately bigger than the range, so a getter that answered out of the BUFFER rather + // than out of the texture's window would be caught. + const GLsizeiptr bufferBytes = rangeOffset + rangeBytes + 16 * kTexelBytes; + + const std::vector zeros(static_cast(bufferBytes), 0); + GLuint buffer = 0; + glGenBuffers(1, &buffer); + glBindBuffer(GL_TEXTURE_BUFFER, buffer); + glBufferData(GL_TEXTURE_BUFFER, bufferBytes, zeros.data(), GL_STATIC_DRAW); + + GLuint texture = 0; + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_BUFFER, texture); + glTexBufferRange(GL_TEXTURE_BUFFER, GL_RGBA8, buffer, rangeOffset, rangeBytes); + ASSERT_EQ(FirstGLError(), 0u) << "glTexBufferRange(GL_RGBA8) was refused"; + + const auto levelQuery = [](GLenum pname) { + GLint value = -1; + glGetTexLevelParameteriv(GL_TEXTURE_BUFFER, 0, pname, &value); + return value; + }; + const auto levelQueryF = [](GLenum pname) { + GLfloat value = -1.0f; + glGetTexLevelParameterfv(GL_TEXTURE_BUFFER, 0, pname, &value); + return value; + }; + + EXPECT_EQ(levelQuery(GL_TEXTURE_WIDTH), static_cast(rangeBytes / kTexelBytes)) + << "GL_TEXTURE_WIDTH is a texel count over the attached RANGE"; + EXPECT_EQ(levelQuery(GL_TEXTURE_HEIGHT), 1); + EXPECT_EQ(levelQuery(GL_TEXTURE_DEPTH), 1); + EXPECT_EQ(levelQuery(GL_TEXTURE_BUFFER_SIZE), static_cast(rangeBytes)) + << "GL_TEXTURE_BUFFER_SIZE reports basic machine units, not texels"; + EXPECT_EQ(levelQuery(GL_TEXTURE_BUFFER_OFFSET), static_cast(rangeOffset)); + EXPECT_EQ(FirstGLError(), 0u) << "a buffer-texture level query raised an error"; + EXPECT_LE(levelQuery(GL_TEXTURE_WIDTH), maxTexels) + << "GL_TEXTURE_WIDTH must stay clamped to GL_MAX_TEXTURE_BUFFER_SIZE"; + + // The float getter is a separate switch and has drifted from the integer one before. + EXPECT_FLOAT_EQ(levelQueryF(GL_TEXTURE_WIDTH), static_cast(rangeBytes / kTexelBytes)); + EXPECT_FLOAT_EQ(levelQueryF(GL_TEXTURE_HEIGHT), 1.0f); + EXPECT_FLOAT_EQ(levelQueryF(GL_TEXTURE_BUFFER_SIZE), static_cast(rangeBytes)); + EXPECT_EQ(FirstGLError(), 0u) << "the float form of a buffer-texture level query raised an error"; + + // The whole-buffer form follows the buffer's current size instead of freezing a window. + glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA8, buffer); + EXPECT_EQ(levelQuery(GL_TEXTURE_BUFFER_OFFSET), 0); + EXPECT_EQ(levelQuery(GL_TEXTURE_BUFFER_SIZE), static_cast(bufferBytes)); + EXPECT_EQ(levelQuery(GL_TEXTURE_WIDTH), static_cast(bufferBytes / kTexelBytes)); + EXPECT_EQ(FirstGLError(), 0u); + + // Both buffer pnames belong to buffer textures alone; anything else is INVALID_OPERATION, + // the same shape GL_TEXTURE_COMPRESSED_IMAGE_SIZE uses for an uncompressed image. + GLuint plainTexture = 0; + glGenTextures(1, &plainTexture); + glBindTexture(GL_TEXTURE_2D, plainTexture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + EXPECT_EQ(FirstGLError(), 0u); + GLint unused = -1; + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_BUFFER_SIZE, &unused); + EXPECT_EQ(FirstGLError(), static_cast(GL_INVALID_OPERATION)); + + glBindTexture(GL_TEXTURE_2D, 0); + glBindTexture(GL_TEXTURE_BUFFER, 0); + glBindBuffer(GL_TEXTURE_BUFFER, 0); + glDeleteTextures(1, &plainTexture); + glDeleteTextures(1, &texture); + glDeleteBuffers(1, &buffer); + EXPECT_EQ(FirstGLError(), 0u); + } + } // namespace MGITest