diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp index 4fa257c9..d3af72bb 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp @@ -613,7 +613,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { MG_State::GLState::ITextureObject* texture = ResolveSamplerTextureRaw(program, programObj, binding); if (!texture) { - continue; + // ResolveSamplerDescriptor will substitute the fallback texture for this binding; + // include it in the sampled set so the pre-render-pass sync/transition pass covers + // its first use instead of leaving that work to happen inside an active pass. + const TextureTarget preferredTarget = programObj.samplerTextureTargetByBinding[binding]; + if (preferredTarget != TextureTarget::Texture2D && + preferredTarget != TextureTarget::TextureRectangle) { + continue; + } + texture = GetFallbackTexture(preferredTarget).get(); } auto found = std::find(outTextures.begin(), outTextures.end(), texture); diff --git a/MobileGL/MG_State/GLState/Core.h b/MobileGL/MG_State/GLState/Core.h index 58369501..51a93d90 100644 --- a/MobileGL/MG_State/GLState/Core.h +++ b/MobileGL/MG_State/GLState/Core.h @@ -108,6 +108,7 @@ namespace MobileGL { // texture is bound at a unit; lets a backend skip re-resolving an unchanged // per-draw sampled-texture set. Uint64 GetTextureBindGeneration() const { return m_textureState.GetTextureBindGeneration(); } + void BumpTextureBindGeneration() { m_textureState.BumpTextureBindGeneration(); } Bool ValidateTextureName(Uint index) const; Bool ValidateTextureObject(Uint index) const; Int GetActiveTextureUnit() const; diff --git a/MobileGL/MG_State/GLState/TextureState/TextureObject.cpp b/MobileGL/MG_State/GLState/TextureState/TextureObject.cpp index a06614c2..ee095cb4 100644 --- a/MobileGL/MG_State/GLState/TextureState/TextureObject.cpp +++ b/MobileGL/MG_State/GLState/TextureState/TextureObject.cpp @@ -7,6 +7,7 @@ // End of Source File Header #include "TextureObject.h" +#include "MG_State/GLState/Core.h" #include "MG_Util/Types.h" #include @@ -52,6 +53,20 @@ namespace MobileGL { void TextureObjectBase::SetInternalFormat(TextureInternalFormat format) { if (format == m_internalFormat) return; + // A default texture (name 0) changes IsUndefinedDefaultTexture on the + // Unknown<->defined transition, which changes per-draw sampled-set membership + // without any bind happening; bump the bind generation so cached sampled sets + // re-resolve instead of replaying the stale membership. The identity check + // excludes the other externalIndex-0 objects (proxy textures, default-FBO + // attachments) whose definedness never feeds sampled-set membership, so e.g. + // proxy probes cannot churn the cache. + if (m_externalIndex == 0 && pGLContext && + (m_internalFormat == TextureInternalFormat::Unknown) != + (format == TextureInternalFormat::Unknown) && + pGLContext->GetDefaultTextureObject(GetTarget()).get() == this) { + pGLContext->BumpTextureBindGeneration(); + } + m_internalFormat = format; ++m_textureParamsVersion; } diff --git a/MobileGL/MG_Test/Texture/TextureTest.cpp b/MobileGL/MG_Test/Texture/TextureTest.cpp index c16a07a7..6d147e3b 100644 --- a/MobileGL/MG_Test/Texture/TextureTest.cpp +++ b/MobileGL/MG_Test/Texture/TextureTest.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -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(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);