From d4501e0c23de049a41da9902225d6a75105fad26 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Mon, 12 Oct 2026 08:01:29 -0400 Subject: [PATCH] [Fix] (GL): raise INVALID_OPERATION from glTextureParameter* for sampler state on a multisample texture --- .../MG_Impl/GLImpl/Texture/GL_Texture.cpp | 36 ++++++++++++++++--- MobileGL/MG_Test/Texture/TextureTest.cpp | 35 ++++++++++++++++++ 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp index 62408a7c..dd14888b 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp +++ b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp @@ -100,6 +100,26 @@ namespace MobileGL::MG_Impl::GLImpl { return true; } + // GL 4.6 core 8.10 gives the multisample-target sampler-state rejection a different error + // per entry point: glTexParameter* raises INVALID_ENUM, glTextureParameter* INVALID_OPERATION. + // The by-name setters share the bound-target code (directly or through + // WithTemporarilyBoundNamedTexture), so they mark the call for its duration instead of + // threading a flag through every TexParameter*_State signature. + thread_local Bool g_inNamedTextureParameterCall = false; + + class ScopedNamedTextureParameterCall { + public: + ScopedNamedTextureParameterCall() : m_previous(g_inNamedTextureParameterCall) { + g_inNamedTextureParameterCall = true; + } + ~ScopedNamedTextureParameterCall() { g_inNamedTextureParameterCall = m_previous; } + ScopedNamedTextureParameterCall(const ScopedNamedTextureParameterCall&) = delete; + ScopedNamedTextureParameterCall& operator=(const ScopedNamedTextureParameterCall&) = delete; + + private: + Bool m_previous; + }; + Bool ValidateMaxAnisotropy(Float maxAnisotropy, const char* caller) { if (maxAnisotropy >= 1.0f) return true; @@ -1223,12 +1243,12 @@ namespace MobileGL::MG_Impl::GLImpl { pname == GL_TEXTURE_MAX_LOD || pname == GL_TEXTURE_LOD_BIAS || pname == GL_TEXTURE_COMPARE_MODE || pname == GL_TEXTURE_COMPARE_FUNC || pname == GL_TEXTURE_BORDER_COLOR || pname == GL_TEXTURE_MAX_ANISOTROPY_EXT)) { - // GL 4.6 core 8.10: a multisample target simply does not ACCEPT these pnames, which is - // an INVALID_ENUM - not the INVALID_OPERATION the two BASE_LEVEL gates above report. - // Those really are operation errors (the pname is accepted, the value is not), which is - // presumably how the wrong class got copied down here. + // GL 4.6 core 8.10: through glTexParameter* a multisample target simply does not ACCEPT + // these pnames, which is an INVALID_ENUM. The by-name glTextureParameter* spelling of + // the same rejection is an INVALID_OPERATION instead (the texture object, not the enum, + // is what is wrong there). MG_State::pGLContext->RecordError( - ErrorCode::InvalidEnum, + g_inNamedTextureParameterCall ? ErrorCode::InvalidOperation : ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", caller, "Multisample textures do not accept sampler-state pnames.")); return false; @@ -6376,12 +6396,14 @@ namespace MobileGL::MG_Impl::GLImpl { } void TextureParameteri(GLuint texture, GLenum pname, GLint param) { + ScopedNamedTextureParameterCall namedCall; auto textureObject = GetTextureObjectByName(texture, __func__); if (!ValidateNamedTextureParameterTarget(textureObject, __func__)) return; TextureParameterObject_State(textureObject, pname, param, __func__); } void TextureParameterf(GLuint texture, GLenum pname, GLfloat param) { + ScopedNamedTextureParameterCall namedCall; auto textureObject = GetTextureObjectByName(texture, __func__); if (!ValidateNamedTextureParameterTarget(textureObject, __func__)) return; TextureParameterObjectf_State(textureObject, pname, param, __func__); @@ -6389,18 +6411,21 @@ namespace MobileGL::MG_Impl::GLImpl { void TextureParameterfv(GLuint texture, GLenum pname, const GLfloat* params) { if (!params) return; + ScopedNamedTextureParameterCall namedCall; auto textureObject = GetTextureObjectByName(texture, __func__); WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { TexParameterfv_State(target, pname, params); }); } void TextureParameteriv(GLuint texture, GLenum pname, const GLint* params) { if (!params) return; + ScopedNamedTextureParameterCall namedCall; auto textureObject = GetTextureObjectByName(texture, __func__); WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { TexParameteriv_State(target, pname, params); }); } void TextureParameterIiv(GLuint texture, GLenum pname, const GLint* params) { if (!params) return; + ScopedNamedTextureParameterCall namedCall; auto textureObject = GetTextureObjectByName(texture, __func__); WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { TexParameterIiv_State(target, pname, params); @@ -6409,6 +6434,7 @@ namespace MobileGL::MG_Impl::GLImpl { void TextureParameterIuiv(GLuint texture, GLenum pname, const GLuint* params) { if (!params) return; + ScopedNamedTextureParameterCall namedCall; auto textureObject = GetTextureObjectByName(texture, __func__); WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { TexParameterIuiv_State(target, pname, params); diff --git a/MobileGL/MG_Test/Texture/TextureTest.cpp b/MobileGL/MG_Test/Texture/TextureTest.cpp index e3d60e73..1055f963 100644 --- a/MobileGL/MG_Test/Texture/TextureTest.cpp +++ b/MobileGL/MG_Test/Texture/TextureTest.cpp @@ -945,6 +945,41 @@ TEST_F(TextureTest, MultisampleSamplerStateIsInvalidEnumWhileNonZeroBaseLevelIsI DrainPendingGlErrors(); } +// The by-name spelling of the same rejection is INVALID_OPERATION (GL 4.6 core 8.10), for every +// glTextureParameter* form - including the vector ones that re-enter the bound-target code - while the +// bound-target spelling right after it stays INVALID_ENUM. +TEST_F(TextureTest, NamedMultisampleSamplerStateIsInvalidOperation) { + GLuint texture = 0; + MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_2D_MULTISAMPLE, 1, &texture); + ASSERT_NE(texture, 0u); + ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); + + MG_Impl::GLImpl::TextureParameterf(texture, GL_TEXTURE_LOD_BIAS, 1.0f); + ExpectSingleGlError(GL_INVALID_OPERATION); + MG_Impl::GLImpl::TextureParameteri(texture, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + ExpectSingleGlError(GL_INVALID_OPERATION); + const GLfloat lodBias = 1.0f; + MG_Impl::GLImpl::TextureParameterfv(texture, GL_TEXTURE_LOD_BIAS, &lodBias); + ExpectSingleGlError(GL_INVALID_OPERATION); + const GLint wrap = GL_REPEAT; + MG_Impl::GLImpl::TextureParameteriv(texture, GL_TEXTURE_WRAP_S, &wrap); + ExpectSingleGlError(GL_INVALID_OPERATION); + MG_Impl::GLImpl::TextureParameterIiv(texture, GL_TEXTURE_WRAP_T, &wrap); + ExpectSingleGlError(GL_INVALID_OPERATION); + const GLuint borderColor[4] = {0u, 0u, 0u, 0u}; + MG_Impl::GLImpl::TextureParameterIuiv(texture, GL_TEXTURE_BORDER_COLOR, borderColor); + ExpectSingleGlError(GL_INVALID_OPERATION); + + // The flag must not outlive the by-name call. + MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D_MULTISAMPLE, texture); + MG_Impl::GLImpl::TexParameterf(GL_TEXTURE_2D_MULTISAMPLE, GL_TEXTURE_LOD_BIAS, 1.0f); + ExpectSingleGlError(GL_INVALID_ENUM); + + MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0); + MG_Impl::GLImpl::DeleteTextures(1, &texture); + DrainPendingGlErrors(); +} + // The whole (pname x entry-point) matrix, because the sampler getters funnel three spellings through // one void* function and used to write a FIXED destination type per pname regardless of which // spelling called. That returned the other type's bit pattern: 10497 punned into a GLfloat reads