diff --git a/MobileGL/MG_State/GLState/TextureState/TextureObject.cpp b/MobileGL/MG_State/GLState/TextureState/TextureObject.cpp index 712325b7..d5791390 100644 --- a/MobileGL/MG_State/GLState/TextureState/TextureObject.cpp +++ b/MobileGL/MG_State/GLState/TextureState/TextureObject.cpp @@ -16,6 +16,10 @@ namespace MobileGL { namespace GLState { static std::atomic s_nextTextureLifetimeId = 1; + // Defined further down next to the other sampling-completeness rules; the + // memo in TextureObjectBase is its only caller. + static Bool ComputeMipmapCompleteForFilter(const ITextureObject* texture, Bool mipmapped); + // TextureObjectBase implementations Uint64 TextureObjectBase::AllocateLifetimeId() { return s_nextTextureLifetimeId.fetch_add(1, std::memory_order_relaxed); @@ -81,6 +85,7 @@ namespace MobileGL { } m_internalFormat = format; + ++m_shapeVersion; ++m_textureParamsVersion; } @@ -192,6 +197,7 @@ namespace MobileGL { m_levelRange.y() = m_levelRange.x(); } ++m_textureParamsVersion; + ++m_shapeVersion; } void TextureObjectBase::SetMaxLevel(Uint maxLevel) { @@ -202,6 +208,7 @@ namespace MobileGL { m_levelRange.y() = maxLevel; ++m_textureParamsVersion; + ++m_shapeVersion; } Bool TextureObjectBase::IsImmutable() const { @@ -231,6 +238,17 @@ namespace MobileGL { return m_contentVersion; } + Bool TextureObjectBase::IsMipmapCompleteForFilterCached(Bool mipmapped) const { + const int slot = mipmapped ? 1 : 0; + if (m_completeMemoShapeVersion[slot] == m_shapeVersion) { + return m_completeMemoValue[slot]; + } + const Bool value = ComputeMipmapCompleteForFilter(this, mipmapped); + m_completeMemoShapeVersion[slot] = m_shapeVersion; + m_completeMemoValue[slot] = value; + return value; + } + void TextureObjectBase::BumpContentVersion() { ++m_contentVersion; } @@ -273,10 +291,12 @@ namespace MobileGL { void TextureObjectWithOneMipmap::AllocateStorage(TextureUploadTarget uploadTarget, Uint mipmapLevel, MipmapInput input) { + ++m_shapeVersion; m_textureStorage.AllocateLevel(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel, input); } void TextureObjectWithOneMipmap::TruncateMipmapLevels(TextureUploadTarget uploadTarget, Uint levelCount) { + ++m_shapeVersion; m_textureStorage.TruncateToLevelCount(GetIndexOfTextureUploadTarget(uploadTarget), levelCount); } @@ -373,13 +393,18 @@ namespace MobileGL { // TODO: add other texture types as needed + Bool IsMipmapCompleteForFilter(const ITextureObject* texture, Bool mipmapped) { + if (texture == nullptr) return true; + return texture->IsMipmapCompleteForFilterCached(mipmapped); + } + Bool SamplesAsIncompleteTexture(const ITextureObject* texture, const SamplerObject* effectiveSampler) { const Bool mipmapped = effectiveSampler != nullptr && effectiveSampler->GetMipmapMode() != SamplerMipmapMode::None; return !IsMipmapCompleteForFilter(texture, mipmapped); } - Bool IsMipmapCompleteForFilter(const ITextureObject* texture, Bool mipmapped) { + static Bool ComputeMipmapCompleteForFilter(const ITextureObject* texture, Bool mipmapped) { if (texture == nullptr) return true; if (!texture->IsComplete()) return false; if (!mipmapped) return true; diff --git a/MobileGL/MG_State/GLState/TextureState/TextureObject.h b/MobileGL/MG_State/GLState/TextureState/TextureObject.h index b5ae5c29..848a0a36 100644 --- a/MobileGL/MG_State/GLState/TextureState/TextureObject.h +++ b/MobileGL/MG_State/GLState/TextureState/TextureObject.h @@ -55,6 +55,13 @@ namespace MobileGL::MG_State::GLState { // Backends compare it against a per-resource snapshot to skip re-syncing unchanged // textures across draws (e.g. the block atlas bound across a whole terrain batch). virtual Uint64 GetContentVersion() const = 0; + // Answers IsMipmapCompleteForFilter() from a memo. Sampling completeness is a + // property of the texture's SHAPE - level sizes, level count, level range, + // internal format - and never of its texel content, but every draw asks about + // every bound texture, which made recomputing it one of the hottest things both + // backends did (the walk plus its GetTexelSize calls measured ~8% of the render + // thread). Shape mutations invalidate the memo; uploads do not. + virtual Bool IsMipmapCompleteForFilterCached(Bool mipmapped) const = 0; virtual Int GetSamples() const = 0; virtual void SetSamples(Int samples) = 0; virtual Bool HasFixedSampleLocations() const = 0; @@ -99,6 +106,7 @@ namespace MobileGL::MG_State::GLState { void SetImmutableLevels(Uint levels) override; Uint16 GetTextureParamsVersion() const override; Uint64 GetContentVersion() const override; + Bool IsMipmapCompleteForFilterCached(Bool mipmapped) const override; // Bumps the content version without touching per-level storage-dirty flags. Used when the // set of defined mip levels grows via GPU-side mip generation (glGenerateMipmap): the level // set changed (so a cached sampled view's level range is stale) but no CPU data is dirty. @@ -124,6 +132,14 @@ namespace MobileGL::MG_State::GLState { UintVec2 m_levelRange = {0, 1000}; Uint m_immutableLevels = 0; Uint16 m_textureParamsVersion = 0; + // Bumped by every mutation the completeness answer depends on - internal + // format, level range, and the stored level set - and by nothing else, so a + // texel upload leaves the memo below valid. + Uint64 m_shapeVersion = 1; + // [0] = the non-mipmapped answer, [1] = the mipmapped one. Mutable because + // completeness is a query; a zero version means "never computed". + mutable Uint64 m_completeMemoShapeVersion[2] = {0, 0}; + mutable Bool m_completeMemoValue[2] = {false, false}; // Starts at 1 so a freshly-created backend resource (snapshot 0) never spuriously // matches before its first sync. Bumped only on dirty=true in MarkStorageDirty. Uint64 m_contentVersion = 1; diff --git a/MobileGL/MG_State/GLState/TextureState/TextureObject2DCube.cpp b/MobileGL/MG_State/GLState/TextureState/TextureObject2DCube.cpp index 738c9531..7732a9b5 100644 --- a/MobileGL/MG_State/GLState/TextureState/TextureObject2DCube.cpp +++ b/MobileGL/MG_State/GLState/TextureState/TextureObject2DCube.cpp @@ -28,10 +28,12 @@ namespace MobileGL { void TextureObject2DCube::AllocateStorage(TextureUploadTarget uploadTarget, Uint mipmapLevel, MipmapInput input) { + ++m_shapeVersion; m_textureStorage.AllocateLevel(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel, input); } void TextureObject2DCube::TruncateMipmapLevels(TextureUploadTarget uploadTarget, Uint levelCount) { + ++m_shapeVersion; m_textureStorage.TruncateToLevelCount(GetIndexOfTextureUploadTarget(uploadTarget), levelCount); }