diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.cpp index d6ee545a..20c2a16d 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.cpp @@ -17,6 +17,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { target <= TextureUploadTarget::CubeMapNegativeZ; } + static Bool PendingClearMatchesTextureIdentity(const PendingClearKey& key, const TextureIdentity& identity) { + return key.texture == identity.texture && key.textureLifetimeId == identity.lifetimeId; + } + static Uint32 ResolveAttachmentBaseArrayLayer(TextureUploadTarget target) { if (!IsCubeMapFaceUploadTarget(target)) { return 0; @@ -42,6 +46,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { Uint32 baseArrayLayer, Uint32 layerCount) { return PendingClearKey { .texture = texture, + .textureLifetimeId = texture ? texture->GetLifetimeId() : 0, .mipLevel = mipLevel, .baseArrayLayer = baseArrayLayer, .layerCount = layerCount, @@ -66,7 +71,74 @@ namespace MobileGL::MG_Backend::DirectVulkan { } void VkClearManager::Shutdown() { + const std::lock_guard lock(m_mutex); + m_pendingClears.clear(); + m_aliveObjects.clear(); + } + TextureIdentity VkClearManager::MakeTextureIdentity(MG_State::GLState::ITextureObject* texture) { + return TextureIdentity { + .texture = texture, + .lifetimeId = texture ? texture->GetLifetimeId() : 0, + }; + } + + void VkClearManager::MergeClearPayload(ClearAttachmentPayload& dst, const ClearAttachmentPayload& src) { + dst.mask |= src.mask; + if ((src.mask & GL_COLOR_BUFFER_BIT) != 0) { + dst.color = src.color; + } + if ((src.mask & GL_DEPTH_BUFFER_BIT) != 0) { + dst.depth = src.depth; + } + if ((src.mask & GL_STENCIL_BUFFER_BIT) != 0) { + dst.stencil = src.stencil; + } + } + + void VkClearManager::ErasePendingClearsForTextureLocked(const TextureIdentity& identity) { + Vector keysToErase; + keysToErase.reserve(m_pendingClears.size()); + for (auto it = m_pendingClears.begin(); it != m_pendingClears.end(); ++it) { + if (PendingClearMatchesTextureIdentity(it->first, identity)) { + keysToErase.emplace_back(it->first); + } + } + for (const auto& key : keysToErase) { + m_pendingClears.erase(key); + } + m_aliveObjects.erase(identity); + } + + Bool VkClearManager::LockTextureIdentityLocked(const TextureIdentity& identity, + SharedPtr& outTexture) { + outTexture.reset(); + if (identity.texture == nullptr) { + return false; + } + + auto aliveIt = m_aliveObjects.find(identity); + if (aliveIt == m_aliveObjects.end()) { + ErasePendingClearsForTextureLocked(identity); + return false; + } + + outTexture = aliveIt->second.lock(); + if (!outTexture || outTexture.get() != identity.texture || outTexture->GetLifetimeId() != identity.lifetimeId) { + ErasePendingClearsForTextureLocked(identity); + outTexture.reset(); + return false; + } + + return true; + } + + Bool VkClearManager::LockTextureLocked(const PendingClearKey& key, + SharedPtr& outTexture) { + return LockTextureIdentityLocked(TextureIdentity{ + .texture = key.texture, + .lifetimeId = key.textureLifetimeId, + }, outTexture); } void VkClearManager::QueueClear(GLbitfield mask, const ClearFramebufferPayload& clearPayload, @@ -121,25 +193,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { void VkClearManager::QueueClear(const ClearAttachmentPayload& clearPayload, const SharedPtr& texture) { - if (clearPayload.mask == 0) { + if (clearPayload.mask == 0 || !texture) { return; } - WeakPtr weakTexturePtr = texture; - if (weakTexturePtr.expired()) - return; - auto* pTexture = weakTexturePtr.lock().get(); - m_aliveObjects[pTexture] = weakTexturePtr; - auto& pending = m_pendingClears[MakePendingClearKey(pTexture)]; - pending.mask |= clearPayload.mask; - if (clearPayload.mask & GL_COLOR_BUFFER_BIT) { - pending.color = clearPayload.color; - } - if (clearPayload.mask & GL_DEPTH_BUFFER_BIT) { - pending.depth = clearPayload.depth; - } - if (clearPayload.mask & GL_STENCIL_BUFFER_BIT) { - pending.stencil = clearPayload.stencil; - } + + const PendingClearKey key = MakePendingClearKey(texture.get()); + const std::lock_guard lock(m_mutex); + m_aliveObjects[MakeTextureIdentity(texture.get())] = texture; + auto& pending = m_pendingClears[key]; + MergeClearPayload(pending, clearPayload); } void VkClearManager::QueueClear(const ClearAttachmentPayload& clearPayload, @@ -151,35 +213,42 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (!texture) { return; } - WeakPtr weakTexturePtr = texture; - if (weakTexturePtr.expired()) { - return; - } - auto* pTexture = weakTexturePtr.lock().get(); - m_aliveObjects[pTexture] = weakTexturePtr; - auto& pending = m_pendingClears[MakePendingClearKey(attachment)]; - pending.mask |= clearPayload.mask; - if (clearPayload.mask & GL_COLOR_BUFFER_BIT) { - pending.color = clearPayload.color; - } - if (clearPayload.mask & GL_DEPTH_BUFFER_BIT) { - pending.depth = clearPayload.depth; - } - if (clearPayload.mask & GL_STENCIL_BUFFER_BIT) { - pending.stencil = clearPayload.stencil; - } + + const PendingClearKey key = MakePendingClearKey(attachment); + const std::lock_guard lock(m_mutex); + m_aliveObjects[MakeTextureIdentity(texture.get())] = texture; + auto& pending = m_pendingClears[key]; + MergeClearPayload(pending, clearPayload); } Bool VkClearManager::HasPendingClear(MG_State::GLState::ITextureObject* texture) { if (texture == nullptr) { return false; } - return std::any_of(m_pendingClears.begin(), m_pendingClears.end(), - [texture](const auto& item) { return item.first.texture == texture; }); + + const Uint64 lifetimeId = texture->GetLifetimeId(); + const std::lock_guard lock(m_mutex); + for (auto it = m_pendingClears.begin(); it != m_pendingClears.end(); ++it) { + if (it->first.texture == texture && it->first.textureLifetimeId == lifetimeId) { + SharedPtr liveTexture; + return LockTextureLocked(it->first, liveTexture); + } + } + return false; } Bool VkClearManager::HasPendingClear(const PendingClearKey& key) { - return key.texture != nullptr && m_pendingClears.find(key) != m_pendingClears.end(); + if (key.texture == nullptr) { + return false; + } + + const std::lock_guard lock(m_mutex); + if (m_pendingClears.find(key) == m_pendingClears.end()) { + return false; + } + + SharedPtr liveTexture; + return LockTextureLocked(key, liveTexture); } Bool VkClearManager::HasPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment) { @@ -190,20 +259,30 @@ namespace MobileGL::MG_Backend::DirectVulkan { } Bool VkClearManager::GetPendingClear(const PendingClearKey& key, ClearAttachmentPayload& outPayload) { - if (key.texture == nullptr || m_aliveObjects.find(key.texture) == m_aliveObjects.end()) { + SharedPtr liveTexture; + return GetPendingClear(key, outPayload, liveTexture); + } + + Bool VkClearManager::GetPendingClear(const PendingClearKey& key, ClearAttachmentPayload& outPayload, + SharedPtr& outTexture) { + if (key.texture == nullptr) { + return false; + } + + const std::lock_guard lock(m_mutex); + if (!LockTextureLocked(key, outTexture)) { return false; } auto it = m_pendingClears.find(key); if (it == m_pendingClears.end()) { - MGLOG_D("%s: Failed getting pending clear for texture %d mip=%u layer=%u count=%u", __func__, - key.texture ? key.texture->GetExternalIndex() : 0, key.mipLevel, key.baseArrayLayer, key.layerCount); + outTexture.reset(); return false; } outPayload = it->second; - MGLOG_D("%s: Got pending clear for texture %d (%s), mip=%u layer=%u count=%u mask=0x%x clear value: color = (%.2f, %.2f, %.2f, %.2f), depth = (%.2f), stencil = (%u)", __func__, - key.texture->GetExternalIndex(), - MG_Util::ConvertTextureInternalFormatToString(key.texture->GetFormat()).c_str(), + MGLOG_D("%s: Got pending clear for texture@%p lifetime=%llu, mip=%u layer=%u count=%u mask=0x%x clear value: color = (%.2f, %.2f, %.2f, %.2f), depth = (%.2f), stencil = (%u)", __func__, + static_cast(key.texture), + static_cast(key.textureLifetimeId), key.mipLevel, key.baseArrayLayer, key.layerCount, static_cast(outPayload.mask), outPayload.color[0], outPayload.color[1], outPayload.color[2], outPayload.color[3], @@ -224,14 +303,20 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool VkClearManager::GetPendingClears(MG_State::GLState::ITextureObject* texture, Vector& outEntries) { outEntries.clear(); - if (texture == nullptr || m_aliveObjects.find(texture) == m_aliveObjects.end()) { + if (texture == nullptr) { return false; } - for (const auto& [key, payload] : m_pendingClears) { - if (key.texture != texture) { - continue; + + const Uint64 lifetimeId = texture->GetLifetimeId(); + const std::lock_guard lock(m_mutex); + SharedPtr liveTexture; + if (!LockTextureIdentityLocked(MakeTextureIdentity(texture), liveTexture)) { + return false; + } + for (auto it = m_pendingClears.begin(); it != m_pendingClears.end(); ++it) { + if (it->first.texture == texture && it->first.textureLifetimeId == lifetimeId) { + outEntries.emplace_back(PendingClearEntry{.key = it->first, .payload = it->second}); } - outEntries.emplace_back(PendingClearEntry{.key = key, .payload = payload}); } return !outEntries.empty(); } @@ -240,24 +325,29 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (texture == nullptr) { return; } + + const TextureIdentity identity = MakeTextureIdentity(texture); MGLOG_D("%s: Pop all pending clears for texture %d", __func__, texture->GetExternalIndex()); - m_aliveObjects.erase(texture); - for (auto it = m_pendingClears.begin(); it != m_pendingClears.end();) { - if (it->first.texture == texture) { - it = m_pendingClears.erase(it); - } else { - ++it; - } - } + const std::lock_guard lock(m_mutex); + ErasePendingClearsForTextureLocked(identity); } void VkClearManager::PopPendingClear(const PendingClearKey& key) { if (key.texture == nullptr) { return; } - MGLOG_D("%s: Pop pending clear for texture %d mip=%u layer=%u count=%u", __func__, - key.texture->GetExternalIndex(), key.mipLevel, key.baseArrayLayer, key.layerCount); - m_pendingClears.erase(key); + + { + const std::lock_guard lock(m_mutex); + auto it = m_pendingClears.find(key); + if (it != m_pendingClears.end()) { + m_pendingClears.erase(it); + } + } + + MGLOG_D("%s: Pop pending clear for texture@%p lifetime=%llu mip=%u layer=%u count=%u", __func__, + static_cast(key.texture), static_cast(key.textureLifetimeId), + key.mipLevel, key.baseArrayLayer, key.layerCount); } void VkClearManager::PopPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment) { @@ -268,26 +358,26 @@ namespace MobileGL::MG_Backend::DirectVulkan { } SizeT VkClearManager::CollectGarbage() { + const std::lock_guard lock(m_mutex); m_gcCounter++; if (m_gcCounter != 0) { return 0; } - SizeT count = 0; - for (auto it = m_aliveObjects.begin(); it != m_aliveObjects.end();) { - auto current = it++; - if (current->second.expired()) { - for (auto clearIt = m_pendingClears.begin(); clearIt != m_pendingClears.end();) { - if (clearIt->first.texture == current->first) { - clearIt = m_pendingClears.erase(clearIt); - } else { - ++clearIt; - } - } - m_aliveObjects.erase(current); - ++count; + Vector expiredTextures; + expiredTextures.reserve(m_aliveObjects.size()); + for (auto it = m_aliveObjects.begin(); it != m_aliveObjects.end(); ++it) { + if (it->second.expired()) { + expiredTextures.emplace_back(it->first); } } - return count; + if (expiredTextures.empty()) { + return 0; + } + + for (const auto& identity : expiredTextures) { + ErasePendingClearsForTextureLocked(identity); + } + return expiredTextures.size(); } } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.h index 817e5429..3400c214 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.h @@ -14,6 +14,7 @@ #include "MG_Util/Math/VectorTypes.h" #include +#include namespace MobileGL::MG_Backend::DirectVulkan { struct ClearFramebufferPayload { @@ -31,16 +32,27 @@ namespace MobileGL::MG_Backend::DirectVulkan { struct PendingClearKey { MG_State::GLState::ITextureObject* texture = nullptr; + Uint64 textureLifetimeId = 0; Uint32 mipLevel = 0; Uint32 baseArrayLayer = 0; Uint32 layerCount = 1; Bool operator==(const PendingClearKey& other) const { - return texture == other.texture && mipLevel == other.mipLevel && + return texture == other.texture && textureLifetimeId == other.textureLifetimeId && + mipLevel == other.mipLevel && baseArrayLayer == other.baseArrayLayer && layerCount == other.layerCount; } }; + struct TextureIdentity { + MG_State::GLState::ITextureObject* texture = nullptr; + Uint64 lifetimeId = 0; + + Bool operator==(const TextureIdentity& other) const { + return texture == other.texture && lifetimeId == other.lifetimeId; + } + }; + struct PendingClearEntry { PendingClearKey key{}; ClearAttachmentPayload payload{}; @@ -49,10 +61,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { struct PendingClearKeyHash { SizeT operator()(const PendingClearKey& key) const { const SizeT textureHash = std::hash{}(key.texture); + const SizeT textureLifetimeHash = std::hash{}(key.textureLifetimeId); const SizeT mipHash = std::hash{}(key.mipLevel); const SizeT layerHash = std::hash{}(key.baseArrayLayer); const SizeT layerCountHash = std::hash{}(key.layerCount); SizeT hash = textureHash; + hash ^= textureLifetimeHash + 0x9e3779b9u + (hash << 6) + (hash >> 2); hash ^= mipHash + 0x9e3779b9u + (hash << 6) + (hash >> 2); hash ^= layerHash + 0x9e3779b9u + (hash << 6) + (hash >> 2); hash ^= layerCountHash + 0x9e3779b9u + (hash << 6) + (hash >> 2); @@ -60,6 +74,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { } }; + struct TextureIdentityHash { + SizeT operator()(const TextureIdentity& key) const { + SizeT hash = std::hash{}(key.texture); + hash ^= std::hash{}(key.lifetimeId) + 0x9e3779b9u + (hash << 6) + (hash >> 2); + return hash; + } + }; + class VkClearManager { public: static PendingClearKey MakePendingClearKey(const MG_State::GLState::FramebufferAttachmentObject& attachment); @@ -79,6 +101,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool HasPendingClear(const PendingClearKey& key); Bool HasPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment); Bool GetPendingClear(const PendingClearKey& key, ClearAttachmentPayload& outPayload); + Bool GetPendingClear(const PendingClearKey& key, ClearAttachmentPayload& outPayload, + SharedPtr& outTexture); Bool GetPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment, ClearAttachmentPayload& outPayload); Bool GetPendingClears(MG_State::GLState::ITextureObject* texture, Vector& outEntries); @@ -87,8 +111,17 @@ namespace MobileGL::MG_Backend::DirectVulkan { void PopPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment); SizeT CollectGarbage(); private: + static TextureIdentity MakeTextureIdentity(MG_State::GLState::ITextureObject* texture); + static void MergeClearPayload(ClearAttachmentPayload& dst, const ClearAttachmentPayload& src); + void ErasePendingClearsForTextureLocked(const TextureIdentity& identity); + Bool LockTextureIdentityLocked(const TextureIdentity& identity, + SharedPtr& outTexture); + Bool LockTextureLocked(const PendingClearKey& key, + SharedPtr& outTexture); + Uint8 m_gcCounter = 0; - UnorderedMap m_pendingClears; - UnorderedMap> m_aliveObjects; + mutable std::mutex m_mutex; + std::unordered_map m_pendingClears; + std::unordered_map, TextureIdentityHash> m_aliveObjects; }; } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp index c53e4c63..b7f736e5 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp @@ -104,6 +104,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { return info; } + IntVec2 ResolveRenderPassFramebufferExtent(Bool isDefaultFbo, const TextureSize& attachmentExtent, + VkExtent2D swapchainExtent) { + if (isDefaultFbo) { + return {static_cast(swapchainExtent.width), static_cast(swapchainExtent.height)}; + } + return {attachmentExtent.x(), attachmentExtent.y()}; + } + VkRenderPassManager::VkRenderPassManager(VkDevice device, const VulkanRendererConfig& config, VkClearManager& clearManager, VkTextureManager& textureManager, SwapchainObject& swapchainObject): @@ -131,7 +139,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkRenderPassManager::HashType VkRenderPassManager::ComputeHash( const MG_State::GLState::FramebufferObject& fbo, Uint32 swapchainImageIndex, Bool includePendingClear) const { XXHASH_VERIFY(XXH64_reset(m_hashState, m_config.CacheVersion)); - const Bool isDefaultFbo = (&fbo == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO.get()); + const Bool isDefaultFbo = fbo.IsDefaultFramebuffer(); if (isDefaultFbo) { XXHASH_VERIFY(XXH64_update(m_hashState, &swapchainImageIndex, sizeof(swapchainImageIndex))); } @@ -162,6 +170,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { contentPtr = att.GetRenderbuffer().get(); XXHASH_VERIFY(XXH64_update(m_hashState, &contentPtr, sizeof(contentPtr))); if (att.IsTexture()) { + const Uint64 textureLifetimeId = att.GetTexture()->GetLifetimeId(); + XXHASH_VERIFY(XXH64_update(m_hashState, &textureLifetimeId, sizeof(textureLifetimeId))); const Int textureLevel = att.GetTextureLevel(); XXHASH_VERIFY(XXH64_update(m_hashState, &textureLevel, sizeof(textureLevel))); const TextureUploadTarget textureUploadTarget = att.GetTextureUploadTarget(); @@ -196,8 +206,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkImageLayout currentLayout = VK_IMAGE_LAYOUT_UNDEFINED; if (isDefaultFbo) { - if (attachment >= FramebufferAttachmentType::Color0 && - attachment <= FramebufferAttachmentType::Color31) { + const Bool isDefaultColorAttachment = + attachment == FramebufferAttachmentType::Color0 || + (attachment >= FramebufferAttachmentType::FrontLeft && + attachment <= FramebufferAttachmentType::BackRight); + if (isDefaultColorAttachment) { currentLayout = m_swapchainObject.GetImageLayout(swapchainImageIndex); } else if (attachment == FramebufferAttachmentType::Depth || attachment == FramebufferAttachmentType::Stencil) { @@ -269,13 +282,17 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (it != m_renderPasses.end()) return it->second; - Bool isDefaultFbo = (&fbo == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO.get()); + Bool isDefaultFbo = fbo.IsDefaultFramebuffer(); // Color attachment auto& drawbufs = fbo.GetDrawBuffers(); const Uint32 colorAttachmentSlotCount = static_cast(drawbufs.size()); - Int width = 0; - Int height = 0; + const VkExtent2D swapchainExtent = m_swapchainObject.GetExtent(); + // Default framebuffer attachments are frontend placeholders; Vulkan framebuffer extent must match the swapchain. + const IntVec2 defaultFramebufferExtent = + ResolveRenderPassFramebufferExtent(isDefaultFbo, {0, 0, 0}, swapchainExtent); + Int width = defaultFramebufferExtent.x(); + Int height = defaultFramebufferExtent.y(); Vector attachmentDescriptions; attachmentDescriptions.reserve(colorAttachmentSlotCount + 1); // Keep the full GL draw buffer slot span so fragment outputs targeting GL_NONE map to VK_ATTACHMENT_UNUSED. @@ -351,10 +368,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { .key = VkClearManager::MakePendingClearKey(att) }); } + const IntVec2 attachmentExtent = + ResolveRenderPassFramebufferExtent(isDefaultFbo, att.GetSize(), swapchainExtent); if (width == 0) - width = att.GetSize().x(); + width = attachmentExtent.x(); if (height == 0) - height = att.GetSize().y(); + height = attachmentExtent.y(); if (isDefaultFbo) { const auto& swapchainViews = m_swapchainObject.GetImageViews(); @@ -379,7 +398,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { trackedColorLayout = textureResource->layout; trackedAttachmentLayouts.emplace_back(TrackedAttachmentLayoutInfo { .target = TrackedAttachmentTarget::Texture, - .texture = texture, + .texture = att.GetTexture(), .textureMipLevel = attachmentMipLevel, .finalLayout = desc.finalLayout, }); @@ -505,7 +524,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { texture.GetExternalIndex()); trackedAttachmentLayouts.emplace_back(TrackedAttachmentLayoutInfo { .target = TrackedAttachmentTarget::Texture, - .texture = &texture, + .texture = selectedDepthStencilAttachment->GetTexture(), .textureMipLevel = attachmentMipLevel, .finalLayout = depthAttachmentDescription.finalLayout, }); @@ -519,9 +538,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { texture, attachmentMipLevel, baseArrayLayer, layerCount, attachmentViewType)); MOBILEGL_ASSERT(attachmentViews.back() != VK_NULL_HANDLE, "GetOrCreateRenderPass: GetOrCreateAttachmentView failed at depth attachment"); + const IntVec2 attachmentExtent = + ResolveRenderPassFramebufferExtent(isDefaultFbo, selectedDepthStencilAttachment->GetSize(), + swapchainExtent); if (width == 0 || height == 0) { - width = selectedDepthStencilAttachment->GetSize().x(); - height = selectedDepthStencilAttachment->GetSize().y(); + width = attachmentExtent.x(); + height = attachmentExtent.y(); } } attachmentDescriptions.emplace_back(depthAttachmentDescription); @@ -617,7 +639,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { continue; } ClearAttachmentPayload clearPayload{}; - if (!s_clearManager->GetPendingClear(pending.key, clearPayload)) { + SharedPtr liveTexture; + if (!s_clearManager->GetPendingClear(pending.key, clearPayload, liveTexture)) { continue; } if ((clearPayload.mask & GL_COLOR_BUFFER_BIT) != 0) { @@ -625,7 +648,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { clearPayload.color.x(), clearPayload.color.y(), clearPayload.color.z(), - ResolveColorClearAlpha(pending.key.texture, clearPayload.color.w()) + ResolveColorClearAlpha(liveTexture.get(), clearPayload.color.w()) }; } if ((clearPayload.mask & GL_DEPTH_BUFFER_BIT) != 0) { @@ -660,11 +683,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { switch (trackedAttachment.target) { case TrackedAttachmentTarget::Texture: MOBILEGL_ASSERT(s_textureManager != nullptr, "EndRenderPass: texture manager is null"); - s_textureManager->UpdateTrackedImageLayoutAfterAttachmentWrite( - commandBuffer, - trackedAttachment.texture, - trackedAttachment.textureMipLevel, - trackedAttachment.finalLayout); + if (const auto texture = trackedAttachment.texture.lock()) { + s_textureManager->UpdateTrackedImageLayoutAfterAttachmentWrite( + commandBuffer, + texture.get(), + trackedAttachment.textureMipLevel, + trackedAttachment.finalLayout); + } break; case TrackedAttachmentTarget::SwapchainColor: MOBILEGL_ASSERT(s_swapchainObject != nullptr, "EndRenderPass: swapchain object is null"); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h index 2ae81943..c6eec76a 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h @@ -31,7 +31,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { struct TrackedAttachmentLayoutInfo { TrackedAttachmentTarget target = TrackedAttachmentTarget::Texture; - MG_State::GLState::ITextureObject* texture = nullptr; + WeakPtr texture; Uint32 textureMipLevel = 0; Uint32 swapchainImageIndex = 0; VkImageLayout finalLayout = VK_IMAGE_LAYOUT_UNDEFINED; @@ -45,6 +45,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { DepthStencilAttachmentLoadInfo ResolveDepthStencilAttachmentLoadInfo( VkImageLayout trackedLayout, Bool clearDepth, Bool clearStencil); + IntVec2 ResolveRenderPassFramebufferExtent(Bool isDefaultFbo, const TextureSize& attachmentExtent, + VkExtent2D swapchainExtent); struct RenderPassEntry { static inline VkDevice s_device; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp index 7e33a1af..0223d7b2 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp @@ -156,6 +156,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } + VkTextureManager::TextureIdentity VkTextureManager::MakeTextureIdentity( + MG_State::GLState::ITextureObject* texture) { + return TextureIdentity{ + .texture = texture, + .lifetimeId = texture ? texture->GetLifetimeId() : 0, + }; + } + static void GetImageTransitionDestinationState(VkImageLayout newLayout, VkPipelineStageFlags& outDstStageMask, VkAccessFlags& outDstAccessMask) { @@ -580,6 +588,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { void VkTextureManager::Shutdown() { DestroyDeferredReleases(); m_textureResources.clear(); + m_aliveObjects.clear(); m_device = VK_NULL_HANDLE; m_physicalDevice = VK_NULL_HANDLE; @@ -600,28 +609,56 @@ namespace MobileGL::MG_Backend::DirectVulkan { CollectDeferredReleases(frameIndex); } + void VkTextureManager::EraseTrackedTexture(const TextureIdentity& identity) { + auto resourceIt = m_textureResources.find(identity); + if (resourceIt != m_textureResources.end()) { + DeferResourceRelease(Move(resourceIt->second)); + m_textureResources.erase(resourceIt); + } + m_aliveObjects.erase(identity); + } + + void VkTextureManager::PruneStaleTextureAliases(MG_State::GLState::ITextureObject* texture) { + if (texture == nullptr) { + return; + } + + Vector staleAliases; + staleAliases.reserve(m_aliveObjects.size()); + for (auto it = m_aliveObjects.begin(); it != m_aliveObjects.end(); ++it) { + if (it->first.texture != texture) { + continue; + } + const auto liveTexture = it->second.lock(); + if (!liveTexture || liveTexture.get() != texture || + liveTexture->GetLifetimeId() != it->first.lifetimeId) { + staleAliases.emplace_back(it->first); + } + } + for (const auto& identity : staleAliases) { + EraseTrackedTexture(identity); + } + } + VkTextureManager::TextureResource* VkTextureManager::SyncTextureAndGetDescriptor(MG_State::GLState::ITextureObject& texture) { MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE, "SyncTextureAndGetDescriptor: m_device == VK_NULL_HANDLE"); - auto aliveIt = m_aliveObjects.find(&texture); + const TextureIdentity identity = MakeTextureIdentity(&texture); + auto aliveIt = m_aliveObjects.find(identity); if (aliveIt != m_aliveObjects.end() && aliveIt->second.expired()) { - auto resourceIt = m_textureResources.find(&texture); - if (resourceIt != m_textureResources.end()) { - DeferResourceRelease(Move(resourceIt->second)); - m_textureResources.erase(resourceIt); - } - m_aliveObjects.erase(aliveIt); + EraseTrackedTexture(aliveIt->first); } const auto& liveTexture = MG_State::pGLContext->GetTextureObject(texture.GetExternalIndex()); if (liveTexture && liveTexture.get() == &texture) { - m_aliveObjects[&texture] = WeakPtr(liveTexture); + m_aliveObjects[identity] = WeakPtr(liveTexture); + PruneStaleTextureAliases(&texture); } - auto it = m_textureResources.find(&texture); + auto it = m_textureResources.find(identity); if (it == m_textureResources.end()) { TextureResource initial{}; - auto [insertIt, _] = m_textureResources.emplace(&texture, Move(initial)); + auto [insertIt, _] = m_textureResources.emplace(identity, Move(initial)); it = insertIt; } @@ -737,7 +774,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { void VkTextureManager::UpdateTrackedImageLayout(MG_State::GLState::ITextureObject* texture, VkImageLayout newLayout) { MOBILEGL_ASSERT(texture != nullptr, "UpdateTrackedImageLayout: texture is null"); - auto it = m_textureResources.find(texture); + auto it = m_textureResources.find(MakeTextureIdentity(texture)); MOBILEGL_ASSERT(it != m_textureResources.end(), "UpdateTrackedImageLayout: textureId=%d has no tracked resource", texture->GetExternalIndex()); MOBILEGL_ASSERT(it->second.image != VK_NULL_HANDLE, @@ -750,7 +787,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { Uint32 writtenMipLevel, VkImageLayout newLayout) { MOBILEGL_ASSERT(texture != nullptr, "UpdateTrackedImageLayoutAfterAttachmentWrite: texture is null"); - auto it = m_textureResources.find(texture); + auto it = m_textureResources.find(MakeTextureIdentity(texture)); MOBILEGL_ASSERT(it != m_textureResources.end(), "UpdateTrackedImageLayoutAfterAttachmentWrite: textureId=%d has no tracked resource", texture->GetExternalIndex()); @@ -920,20 +957,18 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (m_gcCounter != 0) { return 0; } - SizeT count = 0; - for (auto it = m_aliveObjects.begin(); it != m_aliveObjects.end();) { - auto current = it++; - if (current->second.expired()) { - auto resourceIt = m_textureResources.find(current->first); - if (resourceIt != m_textureResources.end()) { - DeferResourceRelease(Move(resourceIt->second)); - m_textureResources.erase(resourceIt); - } - m_aliveObjects.erase(current); - ++count; + + Vector expiredTextures; + expiredTextures.reserve(m_aliveObjects.size()); + for (auto it = m_aliveObjects.begin(); it != m_aliveObjects.end(); ++it) { + if (it->second.expired()) { + expiredTextures.emplace_back(it->first.texture); } } - return count; + for (auto* texture : expiredTextures) { + PruneStaleTextureAliases(texture); + } + return expiredTextures.size(); } Bool VkTextureManager::SyncTexture(MG_State::GLState::ITextureObject &texture, diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h index 394a8ead..1727481f 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h @@ -12,6 +12,7 @@ #include #include #include +#include namespace MobileGL::MG_State::GLState { class ITextureObject; @@ -20,6 +21,23 @@ class ITextureObject; namespace MobileGL::MG_Backend::DirectVulkan { class VkTextureManager { public: + struct TextureIdentity { + MG_State::GLState::ITextureObject* texture = nullptr; + Uint64 lifetimeId = 0; + + Bool operator==(const TextureIdentity& other) const { + return texture == other.texture && lifetimeId == other.lifetimeId; + } + }; + + struct TextureIdentityHash { + SizeT operator()(const TextureIdentity& key) const { + SizeT hash = std::hash{}(key.texture); + hash ^= std::hash{}(key.lifetimeId) + 0x9e3779b9u + (hash << 6) + (hash >> 2); + return hash; + } + }; + struct InitInfo { VkDevice device = VK_NULL_HANDLE; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; @@ -212,6 +230,9 @@ private: void DeferViewRelease(VkImageView view); void CollectDeferredReleases(Uint32 frameIndex); void DestroyDeferredReleases(); + static TextureIdentity MakeTextureIdentity(MG_State::GLState::ITextureObject* texture); + void EraseTrackedTexture(const TextureIdentity& identity); + void PruneStaleTextureAliases(MG_State::GLState::ITextureObject* texture); VkDevice m_device = VK_NULL_HANDLE; VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE; @@ -221,8 +242,8 @@ private: Uint32 m_currentFrameIndex = 0; Uint8 m_gcCounter = 0; - UnorderedMap> m_aliveObjects; - UnorderedMap m_textureResources; + std::unordered_map, TextureIdentityHash> m_aliveObjects; + std::unordered_map m_textureResources; Vector> m_deferredReleases; Vector> m_deferredViewReleases; }; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index c207a874..8f7853c9 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -689,7 +689,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (trackedAttachment.target != TrackedAttachmentTarget::Texture) { continue; } - if (trackedAttachment.texture == &texture) { + const auto trackedTexture = trackedAttachment.texture.lock(); + if (trackedTexture && trackedTexture.get() == &texture) { return true; } } @@ -1064,9 +1065,33 @@ void main() { static Bool ResolveColorBlitBinding(MG_State::GLState::FramebufferObject& fbo, Bool isReadFramebuffer, Uint32 swapchainImageIndex, SwapchainObject& swapchainObject, VkTextureManager& textureManager, BlitImageBinding& outBinding) { - const Bool isDefaultFbo = (&fbo == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO.get()); + const Bool isDefaultFbo = fbo.IsDefaultFramebuffer(); const FramebufferAttachmentType attachmentType = isReadFramebuffer ? fbo.GetReadBuffer() : fbo.GetDrawBuffers()[0]; + outBinding.label = isReadFramebuffer ? "read" : "draw"; + + if (isDefaultFbo) { + const Bool defaultColorAttachment = + attachmentType == FramebufferAttachmentType::Color0 || + (attachmentType >= FramebufferAttachmentType::FrontLeft && + attachmentType <= FramebufferAttachmentType::BackRight); + if (!defaultColorAttachment) { + MGLOG_E("BlitFramebuffer skipped: default framebuffer color attachment %d is not supported", + static_cast(attachmentType)); + return false; + } + outBinding.image = swapchainObject.GetImage(swapchainImageIndex); + outBinding.trackedLayout = nullptr; + outBinding.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + const auto extent = swapchainObject.GetExtent(); + outBinding.extent = {static_cast(extent.width), static_cast(extent.height)}; + outBinding.mipLevel = 0; + outBinding.mipLevelCount = 1; + outBinding.baseArrayLayer = 0; + outBinding.layerCount = 1; + return true; + } + if (attachmentType < FramebufferAttachmentType::Color0 || attachmentType > FramebufferAttachmentType::Color31) { MGLOG_E("BlitFramebuffer only supports color attachments right now (attachment=%d)", static_cast(attachmentType)); @@ -1090,20 +1115,6 @@ void main() { auto* texture = attachment.GetTexture().get(); MOBILEGL_ASSERT(texture != nullptr, "ResolveColorBlitBinding: texture attachment is null"); - outBinding.label = isReadFramebuffer ? "read" : "draw"; - - if (isDefaultFbo) { - outBinding.image = swapchainObject.GetImage(swapchainImageIndex); - outBinding.trackedLayout = nullptr; - outBinding.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - const auto extent = swapchainObject.GetExtent(); - outBinding.extent = {static_cast(extent.width), static_cast(extent.height)}; - outBinding.mipLevel = 0; - outBinding.mipLevelCount = 1; - outBinding.baseArrayLayer = 0; - outBinding.layerCount = 1; - return true; - } auto* resource = textureManager.SyncTextureAndGetDescriptor(*texture); if (resource == nullptr) { @@ -1134,8 +1145,7 @@ void main() { VkTextureManager& textureManager, VkImageAspectFlags requiredAspectMask, BlitImageBinding& outBinding) { - const Bool isDefaultFbo = - (&fbo == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO.get()); + const Bool isDefaultFbo = fbo.IsDefaultFramebuffer(); const auto attachmentType = ResolveFramebufferCopyAttachmentType(fbo, isReadFramebuffer, requiredAspectMask); if (attachmentType == FramebufferAttachmentType::None) { MGLOG_E("BlitFramebuffer skipped: unsupported aspect mask=0x%x", @@ -1250,8 +1260,7 @@ void main() { VkTextureManager& textureManager, VkImageAspectFlags requiredAspectMask, BlitImageBinding& outBinding) { - const Bool isDefaultFbo = - (&fbo == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO.get()); + const Bool isDefaultFbo = fbo.IsDefaultFramebuffer(); const auto attachmentType = ResolveFramebufferCopyAttachmentType(fbo, true, requiredAspectMask); if (attachmentType == FramebufferAttachmentType::None) { MGLOG_E("CopyTexSubImage2D skipped: unsupported source aspect mask=0x%x", @@ -1545,7 +1554,7 @@ void main() { ProgramFactory::CompileOptionFlags flags = ProgramFactory::CompileOptionBit::PositionZRemap; const auto& currentDrawFBO = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); - if (currentDrawFBO == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO) { + if (currentDrawFBO != nullptr && currentDrawFBO->IsDefaultFramebuffer()) { flags |= ProgramFactory::CompileOptionBit::PositionYFlip; switch (preTransform) { case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR: @@ -2703,8 +2712,7 @@ void main() { const auto& drawFboBinding = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); MOBILEGL_ASSERT(drawFboBinding != nullptr, "GetOrCreatePipeline: draw framebuffer is null"); - const Bool isDefaultDrawFbo = - drawFboBinding.get() == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO.get(); + const Bool isDefaultDrawFbo = drawFboBinding->IsDefaultFramebuffer(); const auto& drawBuffers = drawFboBinding->GetDrawBuffers(); auto resolveCompleteColorAttachmentTexture = [&](Uint32 drawBufferIndex) -> MG_State::GLState::ITextureObject* { if (isDefaultDrawFbo || drawBufferIndex >= drawBuffers.size()) { @@ -3430,8 +3438,7 @@ void main() { GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLenum filter) { - const Bool drawIsDefaultFbo = - (&drawFbo == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO.get()); + const Bool drawIsDefaultFbo = drawFbo.IsDefaultFramebuffer(); if (!drawIsDefaultFbo) { return false; } @@ -3607,10 +3614,8 @@ void main() { VkRenderPassManager::EndRenderPass(frame.commandBuffer); } - const Bool readIsDefaultFbo = - (readFbo == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO); - const Bool drawIsDefaultFbo = - (drawFbo == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO); + const Bool readIsDefaultFbo = readFbo->IsDefaultFramebuffer(); + const Bool drawIsDefaultFbo = drawFbo->IsDefaultFramebuffer(); if (isColorBlit && drawIsDefaultFbo && RequiresShaderBlitToDefaultFramebuffer(m_swapchainObject.GetPreTransform())) { if (TryBlitToDefaultFramebufferWithShader(frame, *readFbo, *drawFbo, @@ -3959,8 +3964,7 @@ void main() { VkRenderPassManager::EndRenderPass(frame.commandBuffer); } - const Bool readIsDefaultFbo = - (readFbo == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO); + const Bool readIsDefaultFbo = readFbo->IsDefaultFramebuffer(); BlitImageBinding dstBinding{}; if (!ResolveTextureCopyDestinationBinding(*destinationTexture, static_cast(level), *m_textureManager, @@ -4161,8 +4165,7 @@ void main() { VkRenderPassManager::EndRenderPass(frame.commandBuffer); } - const Bool readIsDefaultFbo = - (readFbo == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO); + const Bool readIsDefaultFbo = readFbo->IsDefaultFramebuffer(); BlitImageBinding srcBinding{}; if (!ResolveColorBlitBinding(*readFbo, true, m_imageIndexAcquired, m_swapchainObject, *m_textureManager, srcBinding)) { @@ -5582,7 +5585,8 @@ void main() { } ClearAttachmentPayload clearPayload{}; - if (!m_clearManager->GetPendingClear(pending.key, clearPayload)) { + SharedPtr liveTexture; + if (!m_clearManager->GetPendingClear(pending.key, clearPayload, liveTexture)) { continue; } @@ -5595,7 +5599,7 @@ void main() { clearPayload.color.x(), clearPayload.color.y(), clearPayload.color.z(), - ResolveColorClearAlpha(pending.key.texture, clearPayload.color.w()) + ResolveColorClearAlpha(liveTexture.get(), clearPayload.color.w()) }; } else { if ((clearPayload.mask & GL_DEPTH_BUFFER_BIT) != 0) { diff --git a/MobileGL/MG_Impl/Init.cpp b/MobileGL/MG_Impl/Init.cpp index 044ba292..b80a8a96 100644 --- a/MobileGL/MG_Impl/Init.cpp +++ b/MobileGL/MG_Impl/Init.cpp @@ -35,7 +35,10 @@ namespace MobileGL::MG_Impl { stencilTex->AllocateStorage(TextureUploadTarget::Texture2D, 0, {{512, 512, 1}, 0}); // stencilTex->SetMipmapLevel({{512, 512, 1}, 0, false, 0, {nullptr, 0}}); fbo0->AttachTexture(FramebufferAttachmentType::Color0, colorTex); + fbo0->AttachTexture(FramebufferAttachmentType::FrontLeft, colorTex); + fbo0->AttachTexture(FramebufferAttachmentType::FrontRight, colorTex); fbo0->AttachTexture(FramebufferAttachmentType::BackLeft, colorTex); + fbo0->AttachTexture(FramebufferAttachmentType::BackRight, colorTex); fbo0->AttachTexture(FramebufferAttachmentType::Depth, depthTex); fbo0->AttachTexture(FramebufferAttachmentType::Stencil, stencilTex); GLImpl::FramebufferImpl::pDefaultFramebufferInfo = diff --git a/MobileGL/MG_State/GLState/FramebufferState/FramebufferObject.h b/MobileGL/MG_State/GLState/FramebufferState/FramebufferObject.h index b0e2f21d..ef85ad40 100644 --- a/MobileGL/MG_State/GLState/FramebufferState/FramebufferObject.h +++ b/MobileGL/MG_State/GLState/FramebufferState/FramebufferObject.h @@ -131,6 +131,7 @@ namespace MobileGL { Uint16 GetObjectVersion() const { return m_objectVersion; } Uint GetExternalIndex() const; + Bool IsDefaultFramebuffer() const { return m_externalIndex == 0; } private: void BumpAttachmentVersion(FramebufferAttachmentType type); diff --git a/MobileGL/MG_State/GLState/TextureState/TextureObject.cpp b/MobileGL/MG_State/GLState/TextureState/TextureObject.cpp index 58fb9bb1..25d428c3 100644 --- a/MobileGL/MG_State/GLState/TextureState/TextureObject.cpp +++ b/MobileGL/MG_State/GLState/TextureState/TextureObject.cpp @@ -13,9 +13,15 @@ namespace MobileGL { namespace MG_State { namespace GLState { + static std::atomic s_nextTextureLifetimeId = 1; + // TextureObjectBase implementations + Uint64 TextureObjectBase::AllocateLifetimeId() { + return s_nextTextureLifetimeId.fetch_add(1, std::memory_order_relaxed); + } + TextureObjectBase::TextureObjectBase(TextureTarget target, Uint externalIndex) - : m_target(target), m_externalIndex(externalIndex) { + : m_externalIndex(externalIndex), m_lifetimeId(AllocateLifetimeId()), m_target(target) { m_sampler = MakeShared(0); } @@ -155,6 +161,10 @@ namespace MobileGL { m_fixedSampleLocations = fixedSampleLocations; } + Uint64 TextureObjectBase::GetLifetimeId() const { + return m_lifetimeId; + } + Uint TextureObjectWithOneMipmap::GetMipmapLevelCount() const { return m_textureStorage.GetLevelCount(); } diff --git a/MobileGL/MG_State/GLState/TextureState/TextureObject.h b/MobileGL/MG_State/GLState/TextureState/TextureObject.h index b1947e54..897ea6eb 100644 --- a/MobileGL/MG_State/GLState/TextureState/TextureObject.h +++ b/MobileGL/MG_State/GLState/TextureState/TextureObject.h @@ -44,6 +44,7 @@ namespace MobileGL::MG_State::GLState { virtual void SetSamples(Int samples) = 0; virtual Bool HasFixedSampleLocations() const = 0; virtual void SetFixedSampleLocations(Bool fixedSampleLocations) = 0; + virtual Uint64 GetLifetimeId() const = 0; protected: virtual Uint GetIndexOfTextureUploadTarget(TextureUploadTarget target) const = 0; @@ -75,9 +76,13 @@ namespace MobileGL::MG_State::GLState { void SetSamples(Int samples) override; Bool HasFixedSampleLocations() const override; void SetFixedSampleLocations(Bool fixedSampleLocations) override; + Uint64 GetLifetimeId() const override; protected: + static Uint64 AllocateLifetimeId(); + const Uint m_externalIndex; + const Uint64 m_lifetimeId; const TextureTarget m_target = TextureTarget::Unknown; TextureInternalFormat m_internalFormat = TextureInternalFormat::Unknown; SharedPtr m_sampler = nullptr; diff --git a/MobileGL/MG_Test/Framebuffer/FramebufferTest.cpp b/MobileGL/MG_Test/Framebuffer/FramebufferTest.cpp index 4bc45990..11221c5f 100644 --- a/MobileGL/MG_Test/Framebuffer/FramebufferTest.cpp +++ b/MobileGL/MG_Test/Framebuffer/FramebufferTest.cpp @@ -105,6 +105,20 @@ TEST_F(FramebufferTest, CreateFramebuffersCreatesObjectsImmediately) { EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); } +TEST_F(FramebufferTest, DefaultFramebufferIdentityTracksFramebufferNameZero) { + const auto defaultFramebuffer = MG_State::pGLContext->GetFramebufferObject(0); + ASSERT_NE(defaultFramebuffer, nullptr); + EXPECT_TRUE(defaultFramebuffer->IsDefaultFramebuffer()); + const auto defaultFramebufferCopy = *defaultFramebuffer; + EXPECT_TRUE(defaultFramebufferCopy.IsDefaultFramebuffer()); + + GLuint framebuffer = 0; + MG_Impl::GLImpl::CreateFramebuffers(1, &framebuffer); + const auto userFramebuffer = MG_State::pGLContext->GetFramebufferObject(framebuffer); + ASSERT_NE(userFramebuffer, nullptr); + EXPECT_FALSE(userFramebuffer->IsDefaultFramebuffer()); +} + TEST_F(FramebufferTest, NamedFramebufferTextureAttachesWithoutChangingBindings) { GLuint framebuffer = 0; MG_Impl::GLImpl::CreateFramebuffers(1, &framebuffer); @@ -259,6 +273,46 @@ TEST_F(FramebufferTest, NamedFramebufferDrawBuffersDoNotModifyDefaultFramebuffer EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); } +TEST_F(FramebufferTest, DefaultFramebufferReadBufferAcceptsGLBackAlias) { + const auto defaultRead = + MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(); + + MG_Impl::GLImpl::ReadBuffer(GL_BACK); + + EXPECT_EQ(defaultRead->GetReadBuffer(), FramebufferAttachmentType::BackLeft); + EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); +} + +TEST_F(FramebufferTest, DefaultFramebufferDrawBufferAcceptsGLFrontAlias) { + const auto defaultDraw = + MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); + + MG_Impl::GLImpl::DrawBuffer(GL_FRONT); + + EXPECT_EQ(defaultDraw->GetDrawBuffers()[0], FramebufferAttachmentType::FrontLeft); + EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); +} + +TEST_F(FramebufferTest, DefaultFramebufferProvidesTextureAttachmentsForFrontAndBackAliases) { + const auto defaultFramebuffer = + MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); + ASSERT_NE(defaultFramebuffer, nullptr); + + const auto& frontLeft = defaultFramebuffer->GetAttachment(FramebufferAttachmentType::FrontLeft); + const auto& frontRight = defaultFramebuffer->GetAttachment(FramebufferAttachmentType::FrontRight); + const auto& backLeft = defaultFramebuffer->GetAttachment(FramebufferAttachmentType::BackLeft); + const auto& backRight = defaultFramebuffer->GetAttachment(FramebufferAttachmentType::BackRight); + + EXPECT_TRUE(frontLeft.IsTexture()); + EXPECT_TRUE(frontRight.IsTexture()); + EXPECT_TRUE(backLeft.IsTexture()); + EXPECT_TRUE(backRight.IsTexture()); + EXPECT_TRUE(frontLeft.IsComplete()); + EXPECT_TRUE(frontRight.IsComplete()); + EXPECT_TRUE(backLeft.IsComplete()); + EXPECT_TRUE(backRight.IsComplete()); +} + TEST_F(FramebufferTest, ClearNamedFramebufferfvUsesNamedObjectWithoutChangingBindings) { GLuint framebuffer = 0; MG_Impl::GLImpl::CreateFramebuffers(1, &framebuffer); diff --git a/MobileGL/MG_Test/SanityTest.cpp b/MobileGL/MG_Test/SanityTest.cpp index 4b506476..c462696d 100644 --- a/MobileGL/MG_Test/SanityTest.cpp +++ b/MobileGL/MG_Test/SanityTest.cpp @@ -165,6 +165,18 @@ TEST(DirectVulkanSanity, AdvertisesTextureStorageForDirectStateAccess) { EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_texture_storage), extensions.end()); } +TEST(DirectVulkanSanity, RenderPassExtentUsesSwapchainSizeOnlyForDefaultFramebuffer) { + using MobileGL::MG_Backend::DirectVulkan::ResolveRenderPassFramebufferExtent; + + const MobileGL::TextureSize attachmentExtent = {512, 512, 1}; + const VkExtent2D swapchainExtent = {3200u, 1440u}; + + EXPECT_EQ(ResolveRenderPassFramebufferExtent(true, attachmentExtent, swapchainExtent), + MobileGL::IntVec2(3200, 1440)); + EXPECT_EQ(ResolveRenderPassFramebufferExtent(false, attachmentExtent, swapchainExtent), + MobileGL::IntVec2(512, 512)); +} + TEST(DirectVulkanSanity, AdvertisesVoxyRequiredRenderingExtensionsWithoutRaisingGLVersion) { MobileGL::MG_Backend::DirectVulkan::BackendObject_DirectVulkan backend; const auto& rendererInfo = backend.GetRendererInfo().RendererGLInfo; diff --git a/MobileGL/MG_Test/Texture/CMakeLists.txt b/MobileGL/MG_Test/Texture/CMakeLists.txt index b6860ba7..b1ca17ea 100644 --- a/MobileGL/MG_Test/Texture/CMakeLists.txt +++ b/MobileGL/MG_Test/Texture/CMakeLists.txt @@ -18,3 +18,21 @@ target_link_libraries( include(GoogleTest) gtest_discover_tests(TextureTest) + +add_executable( + VkClearManagerTest + VkClearManagerTest.cpp +) + +target_include_directories(VkClearManagerTest PRIVATE + ${MGL_ROOT}/include + ${MGL_ROOT}/MobileGL +) + +target_link_libraries( + VkClearManagerTest PRIVATE + GTest::gtest_main + ${LINK_LIBRARIES} +) + +gtest_discover_tests(VkClearManagerTest) diff --git a/MobileGL/MG_Test/Texture/VkClearManagerTest.cpp b/MobileGL/MG_Test/Texture/VkClearManagerTest.cpp new file mode 100644 index 00000000..c142100d --- /dev/null +++ b/MobileGL/MG_Test/Texture/VkClearManagerTest.cpp @@ -0,0 +1,110 @@ +// MobileGL - MobileGL/MG_Test/Texture/VkClearManagerTest.cpp +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#include + +#include "Includes.h" +#include "Init.h" +#include +#include +#include + +using namespace MobileGL; +using namespace MobileGL::MG_Backend::DirectVulkan; + +class VkClearManagerTest : public ::testing::Test { +protected: + void SetUp() override { MobileGL::Initialize(); } +}; + +TEST_F(VkClearManagerTest, CollectGarbageRemovesExpiredTexturesAndTheirPendingClears) { + VkClearManager clearManager; + ASSERT_TRUE(clearManager.Initialize()); + + constexpr SizeT kTextureCount = 64; + Vector textureNames(kTextureCount, 0); + Vector pendingKeys; + pendingKeys.reserve(kTextureCount); + + const ClearAttachmentPayload clearPayload{ + .mask = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, + .color = FloatVec4(0.25f, 0.5f, 0.75f, 1.0f), + .depth = 0.5f, + }; + + for (SizeT i = 0; i < kTextureCount; ++i) { + MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_2D, 1, &textureNames[i]); + ASSERT_NE(textureNames[i], 0u); + + { + const auto textureObject = MG_State::pGLContext->GetTextureObject(textureNames[i]); + ASSERT_NE(textureObject, nullptr); + + const PendingClearKey key = VkClearManager::MakePendingClearKey(textureObject.get()); + clearManager.QueueClear(clearPayload, textureObject); + + EXPECT_TRUE(clearManager.HasPendingClear(key)); + pendingKeys.emplace_back(key); + } + } + + MG_Impl::GLImpl::DeleteTextures(static_cast(textureNames.size()), textureNames.data()); + + for (Int i = 0; i < 255; ++i) { + EXPECT_EQ(clearManager.CollectGarbage(), 0u); + } + EXPECT_EQ(clearManager.CollectGarbage(), kTextureCount); + + ClearAttachmentPayload outPayload{}; + for (const auto& key : pendingKeys) { + EXPECT_FALSE(clearManager.HasPendingClear(key)); + EXPECT_FALSE(clearManager.GetPendingClear(key, outPayload)); + } + + GLuint freshTexture = 0; + MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_2D, 1, &freshTexture); + ASSERT_NE(freshTexture, 0u); + + const auto freshTextureObject = MG_State::pGLContext->GetTextureObject(freshTexture); + ASSERT_NE(freshTextureObject, nullptr); + EXPECT_FALSE(clearManager.HasPendingClear(freshTextureObject.get())); + + MG_Impl::GLImpl::DeleteTextures(1, &freshTexture); + clearManager.Shutdown(); +} + +TEST_F(VkClearManagerTest, StalePendingClearsAreRejectedBeforePeriodicGarbageCollection) { + VkClearManager clearManager; + ASSERT_TRUE(clearManager.Initialize()); + + GLuint texture = 0; + MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_2D, 1, &texture); + ASSERT_NE(texture, 0u); + + PendingClearKey key{}; + { + const auto textureObject = MG_State::pGLContext->GetTextureObject(texture); + ASSERT_NE(textureObject, nullptr); + + key = VkClearManager::MakePendingClearKey(textureObject.get()); + clearManager.QueueClear(ClearAttachmentPayload{ + .mask = GL_COLOR_BUFFER_BIT, + .color = FloatVec4(1.0f, 0.25f, 0.5f, 0.75f), + }, textureObject); + EXPECT_TRUE(clearManager.HasPendingClear(key)); + } + + MG_Impl::GLImpl::DeleteTextures(1, &texture); + + ClearAttachmentPayload outPayload{}; + EXPECT_FALSE(clearManager.HasPendingClear(key)); + EXPECT_FALSE(clearManager.GetPendingClear(key, outPayload)); + EXPECT_FALSE(clearManager.HasPendingClear(key)); + + clearManager.Shutdown(); +} diff --git a/MobileGL/MG_Util/Converters/GLToMG/FramebufferEnumConverter.cpp b/MobileGL/MG_Util/Converters/GLToMG/FramebufferEnumConverter.cpp index 60718d75..98b7c379 100644 --- a/MobileGL/MG_Util/Converters/GLToMG/FramebufferEnumConverter.cpp +++ b/MobileGL/MG_Util/Converters/GLToMG/FramebufferEnumConverter.cpp @@ -36,10 +36,12 @@ namespace MobileGL { return FramebufferAttachmentType::Depth; case GL_STENCIL_ATTACHMENT: return FramebufferAttachmentType::Stencil; + case GL_FRONT: case GL_FRONT_LEFT: return FramebufferAttachmentType::FrontLeft; case GL_FRONT_RIGHT: return FramebufferAttachmentType::FrontRight; + case GL_BACK: case GL_BACK_LEFT: return FramebufferAttachmentType::BackLeft; case GL_BACK_RIGHT: @@ -59,4 +61,4 @@ namespace MobileGL { } } } // namespace MG_Util -} // namespace MobileGL \ No newline at end of file +} // namespace MobileGL