[Fix] (MG_State, MG_Backend/DirectVulkan): bump the texture bind generation when a context default texture crosses the undefined<->defined boundary so cached sampled sets re-resolve, and collect the fallback texture into the sampled set so its first use transitions outside the render pass

This commit is contained in:
2026-07-17 22:33:31 -04:00
parent c718d6bad8
commit 594916850f
4 changed files with 66 additions and 1 deletions
+41
View File
@@ -22,6 +22,7 @@
#include <MG_State/EGLState/Core.h>
#include <MG_State/GLState/Core.h>
#include <MG_State/GLState/TextureState/TextureObject.h>
#include <MG_State/GLState/TextureState/TextureObject2D.h>
#include <MG_Util/Converters/GLToMG/TextureEnumConverter.h>
#include <MG_Util/Converters/MGToGL/TextureEnumConverter.h>
#include <MG_Util/Converters/MGToMG/TextureEnumConverter.h>
@@ -1840,6 +1841,46 @@ TEST_F(TextureTest, DefaultTextureIsBoundInitiallyAndIsPerTarget) {
default2D);
}
// Backend sampled-set membership keys off IsUndefinedDefaultTexture, so a default texture
// crossing the Unknown<->defined boundary must move the bind generation even though no bind
// happened - a cached sampled set (DirectVulkan walk-skip) would otherwise replay stale
// membership and never sync/transition the now-image-bearing default. Positioned while the 2D
// default is still undefined in a single-process run (later tests define it and definedness is
// irreversible through the GL API); the reverse transition at the end restores that state.
TEST_F(TextureTest, DefiningImageOnBoundDefaultTextureBumpsBindGeneration) {
MG_Impl::GLImpl::ActiveTexture(GL_TEXTURE0);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, 0);
const auto& defaultTexture =
MG_State::pGLContext->GetTextureUnitObject(0).GetBindingSlot(TextureTarget::Texture2D).GetBoundObject();
ASSERT_TRUE(MG_State::GLState::IsUndefinedDefaultTexture(defaultTexture.get()))
<< "an earlier test defined the 2D default texture; move this test before it";
// glTexImage2D on the BOUND name-0 texture (no rebind anywhere) moves the generation
// exactly once: the next draw re-collects the sampled set and references the default's
// image instead of the fallback.
const Uint64 base = MG_State::pGLContext->GetTextureBindGeneration();
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
EXPECT_EQ(MG_State::pGLContext->GetTextureBindGeneration(), base + 1);
// Re-specifying an already-defined default keeps the cache hot.
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
EXPECT_EQ(MG_State::pGLContext->GetTextureBindGeneration(), base + 1);
// Other externalIndex-0 objects (proxy textures, default-FBO attachments) are not the
// context's default texture; their (re)specification must not churn the cache.
auto proxyLike = MakeShared<MG_State::GLState::TextureObject2D>(0u);
proxyLike->SetInternalFormat(TextureInternalFormat::RGBA8);
EXPECT_EQ(MG_State::pGLContext->GetTextureBindGeneration(), base + 1);
// The reverse transition (no GL entry point produces it today) is symmetric, and restores
// the undefined 2D default the rest of the suite expects.
defaultTexture->SetInternalFormat(TextureInternalFormat::Unknown);
EXPECT_EQ(MG_State::pGLContext->GetTextureBindGeneration(), base + 2);
EXPECT_TRUE(MG_State::GLState::IsUndefinedDefaultTexture(defaultTexture.get()));
}
TEST_F(TextureTest, DefaultTextureAcceptsImageAndParameterCalls) {
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, 0);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);