[Fix] (MG_Backend/DirectVulkan, MG_State/TextureState, MG_Test): harden default-fbo clear lifetime tracking

This commit is contained in:
BZLZHH
2026-06-10 09:49:16 +08:00
parent 6051588458
commit a4261f8b19
16 changed files with 584 additions and 159 deletions
@@ -17,6 +17,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
target <= TextureUploadTarget::CubeMapNegativeZ; 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) { static Uint32 ResolveAttachmentBaseArrayLayer(TextureUploadTarget target) {
if (!IsCubeMapFaceUploadTarget(target)) { if (!IsCubeMapFaceUploadTarget(target)) {
return 0; return 0;
@@ -42,6 +46,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint32 baseArrayLayer, Uint32 layerCount) { Uint32 baseArrayLayer, Uint32 layerCount) {
return PendingClearKey { return PendingClearKey {
.texture = texture, .texture = texture,
.textureLifetimeId = texture ? texture->GetLifetimeId() : 0,
.mipLevel = mipLevel, .mipLevel = mipLevel,
.baseArrayLayer = baseArrayLayer, .baseArrayLayer = baseArrayLayer,
.layerCount = layerCount, .layerCount = layerCount,
@@ -66,7 +71,74 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} }
void VkClearManager::Shutdown() { void VkClearManager::Shutdown() {
const std::lock_guard<std::mutex> 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<PendingClearKey> 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<MG_State::GLState::ITextureObject>& 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<MG_State::GLState::ITextureObject>& outTexture) {
return LockTextureIdentityLocked(TextureIdentity{
.texture = key.texture,
.lifetimeId = key.textureLifetimeId,
}, outTexture);
} }
void VkClearManager::QueueClear(GLbitfield mask, const ClearFramebufferPayload& clearPayload, void VkClearManager::QueueClear(GLbitfield mask, const ClearFramebufferPayload& clearPayload,
@@ -121,25 +193,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void VkClearManager::QueueClear(const ClearAttachmentPayload& clearPayload, void VkClearManager::QueueClear(const ClearAttachmentPayload& clearPayload,
const SharedPtr<MG_State::GLState::ITextureObject>& texture) { const SharedPtr<MG_State::GLState::ITextureObject>& texture) {
if (clearPayload.mask == 0) { if (clearPayload.mask == 0 || !texture) {
return; return;
} }
WeakPtr<MG_State::GLState::ITextureObject> weakTexturePtr = texture;
if (weakTexturePtr.expired()) const PendingClearKey key = MakePendingClearKey(texture.get());
return; const std::lock_guard<std::mutex> lock(m_mutex);
auto* pTexture = weakTexturePtr.lock().get(); m_aliveObjects[MakeTextureIdentity(texture.get())] = texture;
m_aliveObjects[pTexture] = weakTexturePtr; auto& pending = m_pendingClears[key];
auto& pending = m_pendingClears[MakePendingClearKey(pTexture)]; MergeClearPayload(pending, clearPayload);
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;
}
} }
void VkClearManager::QueueClear(const ClearAttachmentPayload& clearPayload, void VkClearManager::QueueClear(const ClearAttachmentPayload& clearPayload,
@@ -151,35 +213,42 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (!texture) { if (!texture) {
return; return;
} }
WeakPtr<MG_State::GLState::ITextureObject> weakTexturePtr = texture;
if (weakTexturePtr.expired()) { const PendingClearKey key = MakePendingClearKey(attachment);
return; const std::lock_guard<std::mutex> lock(m_mutex);
} m_aliveObjects[MakeTextureIdentity(texture.get())] = texture;
auto* pTexture = weakTexturePtr.lock().get(); auto& pending = m_pendingClears[key];
m_aliveObjects[pTexture] = weakTexturePtr; MergeClearPayload(pending, clearPayload);
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;
}
} }
Bool VkClearManager::HasPendingClear(MG_State::GLState::ITextureObject* texture) { Bool VkClearManager::HasPendingClear(MG_State::GLState::ITextureObject* texture) {
if (texture == nullptr) { if (texture == nullptr) {
return false; 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<std::mutex> lock(m_mutex);
for (auto it = m_pendingClears.begin(); it != m_pendingClears.end(); ++it) {
if (it->first.texture == texture && it->first.textureLifetimeId == lifetimeId) {
SharedPtr<MG_State::GLState::ITextureObject> liveTexture;
return LockTextureLocked(it->first, liveTexture);
}
}
return false;
} }
Bool VkClearManager::HasPendingClear(const PendingClearKey& key) { 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<std::mutex> lock(m_mutex);
if (m_pendingClears.find(key) == m_pendingClears.end()) {
return false;
}
SharedPtr<MG_State::GLState::ITextureObject> liveTexture;
return LockTextureLocked(key, liveTexture);
} }
Bool VkClearManager::HasPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment) { 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) { Bool VkClearManager::GetPendingClear(const PendingClearKey& key, ClearAttachmentPayload& outPayload) {
if (key.texture == nullptr || m_aliveObjects.find(key.texture) == m_aliveObjects.end()) { SharedPtr<MG_State::GLState::ITextureObject> liveTexture;
return GetPendingClear(key, outPayload, liveTexture);
}
Bool VkClearManager::GetPendingClear(const PendingClearKey& key, ClearAttachmentPayload& outPayload,
SharedPtr<MG_State::GLState::ITextureObject>& outTexture) {
if (key.texture == nullptr) {
return false;
}
const std::lock_guard<std::mutex> lock(m_mutex);
if (!LockTextureLocked(key, outTexture)) {
return false; return false;
} }
auto it = m_pendingClears.find(key); auto it = m_pendingClears.find(key);
if (it == m_pendingClears.end()) { if (it == m_pendingClears.end()) {
MGLOG_D("%s: Failed getting pending clear for texture %d mip=%u layer=%u count=%u", __func__, outTexture.reset();
key.texture ? key.texture->GetExternalIndex() : 0, key.mipLevel, key.baseArrayLayer, key.layerCount);
return false; return false;
} }
outPayload = it->second; 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__, 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__,
key.texture->GetExternalIndex(), static_cast<void*>(key.texture),
MG_Util::ConvertTextureInternalFormatToString(key.texture->GetFormat()).c_str(), static_cast<unsigned long long>(key.textureLifetimeId),
key.mipLevel, key.baseArrayLayer, key.layerCount, key.mipLevel, key.baseArrayLayer, key.layerCount,
static_cast<Uint32>(outPayload.mask), static_cast<Uint32>(outPayload.mask),
outPayload.color[0], outPayload.color[1], outPayload.color[2], outPayload.color[3], 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, Bool VkClearManager::GetPendingClears(MG_State::GLState::ITextureObject* texture,
Vector<PendingClearEntry>& outEntries) { Vector<PendingClearEntry>& outEntries) {
outEntries.clear(); outEntries.clear();
if (texture == nullptr || m_aliveObjects.find(texture) == m_aliveObjects.end()) { if (texture == nullptr) {
return false; return false;
} }
for (const auto& [key, payload] : m_pendingClears) {
if (key.texture != texture) { const Uint64 lifetimeId = texture->GetLifetimeId();
continue; const std::lock_guard<std::mutex> lock(m_mutex);
SharedPtr<MG_State::GLState::ITextureObject> 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(); return !outEntries.empty();
} }
@@ -240,24 +325,29 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (texture == nullptr) { if (texture == nullptr) {
return; return;
} }
const TextureIdentity identity = MakeTextureIdentity(texture);
MGLOG_D("%s: Pop all pending clears for texture %d", __func__, texture->GetExternalIndex()); MGLOG_D("%s: Pop all pending clears for texture %d", __func__, texture->GetExternalIndex());
m_aliveObjects.erase(texture); const std::lock_guard<std::mutex> lock(m_mutex);
for (auto it = m_pendingClears.begin(); it != m_pendingClears.end();) { ErasePendingClearsForTextureLocked(identity);
if (it->first.texture == texture) {
it = m_pendingClears.erase(it);
} else {
++it;
}
}
} }
void VkClearManager::PopPendingClear(const PendingClearKey& key) { void VkClearManager::PopPendingClear(const PendingClearKey& key) {
if (key.texture == nullptr) { if (key.texture == nullptr) {
return; 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<std::mutex> 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<void*>(key.texture), static_cast<unsigned long long>(key.textureLifetimeId),
key.mipLevel, key.baseArrayLayer, key.layerCount);
} }
void VkClearManager::PopPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment) { void VkClearManager::PopPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment) {
@@ -268,26 +358,26 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} }
SizeT VkClearManager::CollectGarbage() { SizeT VkClearManager::CollectGarbage() {
const std::lock_guard<std::mutex> lock(m_mutex);
m_gcCounter++; m_gcCounter++;
if (m_gcCounter != 0) { if (m_gcCounter != 0) {
return 0; return 0;
} }
SizeT count = 0; Vector<TextureIdentity> expiredTextures;
for (auto it = m_aliveObjects.begin(); it != m_aliveObjects.end();) { expiredTextures.reserve(m_aliveObjects.size());
auto current = it++; for (auto it = m_aliveObjects.begin(); it != m_aliveObjects.end(); ++it) {
if (current->second.expired()) { if (it->second.expired()) {
for (auto clearIt = m_pendingClears.begin(); clearIt != m_pendingClears.end();) { expiredTextures.emplace_back(it->first);
if (clearIt->first.texture == current->first) {
clearIt = m_pendingClears.erase(clearIt);
} else {
++clearIt;
}
}
m_aliveObjects.erase(current);
++count;
} }
} }
return count; if (expiredTextures.empty()) {
return 0;
}
for (const auto& identity : expiredTextures) {
ErasePendingClearsForTextureLocked(identity);
}
return expiredTextures.size();
} }
} // namespace MobileGL::MG_Backend::DirectVulkan } // namespace MobileGL::MG_Backend::DirectVulkan
@@ -14,6 +14,7 @@
#include "MG_Util/Math/VectorTypes.h" #include "MG_Util/Math/VectorTypes.h"
#include <Includes.h> #include <Includes.h>
#include <unordered_map>
namespace MobileGL::MG_Backend::DirectVulkan { namespace MobileGL::MG_Backend::DirectVulkan {
struct ClearFramebufferPayload { struct ClearFramebufferPayload {
@@ -31,16 +32,27 @@ namespace MobileGL::MG_Backend::DirectVulkan {
struct PendingClearKey { struct PendingClearKey {
MG_State::GLState::ITextureObject* texture = nullptr; MG_State::GLState::ITextureObject* texture = nullptr;
Uint64 textureLifetimeId = 0;
Uint32 mipLevel = 0; Uint32 mipLevel = 0;
Uint32 baseArrayLayer = 0; Uint32 baseArrayLayer = 0;
Uint32 layerCount = 1; Uint32 layerCount = 1;
Bool operator==(const PendingClearKey& other) const { 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; 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 { struct PendingClearEntry {
PendingClearKey key{}; PendingClearKey key{};
ClearAttachmentPayload payload{}; ClearAttachmentPayload payload{};
@@ -49,10 +61,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
struct PendingClearKeyHash { struct PendingClearKeyHash {
SizeT operator()(const PendingClearKey& key) const { SizeT operator()(const PendingClearKey& key) const {
const SizeT textureHash = std::hash<MG_State::GLState::ITextureObject*>{}(key.texture); const SizeT textureHash = std::hash<MG_State::GLState::ITextureObject*>{}(key.texture);
const SizeT textureLifetimeHash = std::hash<Uint64>{}(key.textureLifetimeId);
const SizeT mipHash = std::hash<Uint32>{}(key.mipLevel); const SizeT mipHash = std::hash<Uint32>{}(key.mipLevel);
const SizeT layerHash = std::hash<Uint32>{}(key.baseArrayLayer); const SizeT layerHash = std::hash<Uint32>{}(key.baseArrayLayer);
const SizeT layerCountHash = std::hash<Uint32>{}(key.layerCount); const SizeT layerCountHash = std::hash<Uint32>{}(key.layerCount);
SizeT hash = textureHash; SizeT hash = textureHash;
hash ^= textureLifetimeHash + 0x9e3779b9u + (hash << 6) + (hash >> 2);
hash ^= mipHash + 0x9e3779b9u + (hash << 6) + (hash >> 2); hash ^= mipHash + 0x9e3779b9u + (hash << 6) + (hash >> 2);
hash ^= layerHash + 0x9e3779b9u + (hash << 6) + (hash >> 2); hash ^= layerHash + 0x9e3779b9u + (hash << 6) + (hash >> 2);
hash ^= layerCountHash + 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<MG_State::GLState::ITextureObject*>{}(key.texture);
hash ^= std::hash<Uint64>{}(key.lifetimeId) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
return hash;
}
};
class VkClearManager { class VkClearManager {
public: public:
static PendingClearKey MakePendingClearKey(const MG_State::GLState::FramebufferAttachmentObject& attachment); 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 PendingClearKey& key);
Bool HasPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment); Bool HasPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment);
Bool GetPendingClear(const PendingClearKey& key, ClearAttachmentPayload& outPayload); Bool GetPendingClear(const PendingClearKey& key, ClearAttachmentPayload& outPayload);
Bool GetPendingClear(const PendingClearKey& key, ClearAttachmentPayload& outPayload,
SharedPtr<MG_State::GLState::ITextureObject>& outTexture);
Bool GetPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment, Bool GetPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment,
ClearAttachmentPayload& outPayload); ClearAttachmentPayload& outPayload);
Bool GetPendingClears(MG_State::GLState::ITextureObject* texture, Vector<PendingClearEntry>& outEntries); Bool GetPendingClears(MG_State::GLState::ITextureObject* texture, Vector<PendingClearEntry>& outEntries);
@@ -87,8 +111,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void PopPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment); void PopPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment);
SizeT CollectGarbage(); SizeT CollectGarbage();
private: 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<MG_State::GLState::ITextureObject>& outTexture);
Bool LockTextureLocked(const PendingClearKey& key,
SharedPtr<MG_State::GLState::ITextureObject>& outTexture);
Uint8 m_gcCounter = 0; Uint8 m_gcCounter = 0;
UnorderedMap<PendingClearKey, ClearAttachmentPayload, PendingClearKeyHash> m_pendingClears; mutable std::mutex m_mutex;
UnorderedMap<MG_State::GLState::ITextureObject*, WeakPtr<MG_State::GLState::ITextureObject>> m_aliveObjects; std::unordered_map<PendingClearKey, ClearAttachmentPayload, PendingClearKeyHash> m_pendingClears;
std::unordered_map<TextureIdentity, WeakPtr<MG_State::GLState::ITextureObject>, TextureIdentityHash> m_aliveObjects;
}; };
} // namespace MobileGL::MG_Backend::DirectVulkan } // namespace MobileGL::MG_Backend::DirectVulkan
@@ -104,6 +104,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return info; return info;
} }
IntVec2 ResolveRenderPassFramebufferExtent(Bool isDefaultFbo, const TextureSize& attachmentExtent,
VkExtent2D swapchainExtent) {
if (isDefaultFbo) {
return {static_cast<Int>(swapchainExtent.width), static_cast<Int>(swapchainExtent.height)};
}
return {attachmentExtent.x(), attachmentExtent.y()};
}
VkRenderPassManager::VkRenderPassManager(VkDevice device, VkRenderPassManager::VkRenderPassManager(VkDevice device,
const VulkanRendererConfig& config, VkClearManager& clearManager, VkTextureManager& textureManager, const VulkanRendererConfig& config, VkClearManager& clearManager, VkTextureManager& textureManager,
SwapchainObject& swapchainObject): SwapchainObject& swapchainObject):
@@ -131,7 +139,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkRenderPassManager::HashType VkRenderPassManager::ComputeHash( VkRenderPassManager::HashType VkRenderPassManager::ComputeHash(
const MG_State::GLState::FramebufferObject& fbo, Uint32 swapchainImageIndex, Bool includePendingClear) const { const MG_State::GLState::FramebufferObject& fbo, Uint32 swapchainImageIndex, Bool includePendingClear) const {
XXHASH_VERIFY(XXH64_reset(m_hashState, m_config.CacheVersion)); 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) { if (isDefaultFbo) {
XXHASH_VERIFY(XXH64_update(m_hashState, &swapchainImageIndex, sizeof(swapchainImageIndex))); XXHASH_VERIFY(XXH64_update(m_hashState, &swapchainImageIndex, sizeof(swapchainImageIndex)));
} }
@@ -162,6 +170,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
contentPtr = att.GetRenderbuffer().get(); contentPtr = att.GetRenderbuffer().get();
XXHASH_VERIFY(XXH64_update(m_hashState, &contentPtr, sizeof(contentPtr))); XXHASH_VERIFY(XXH64_update(m_hashState, &contentPtr, sizeof(contentPtr)));
if (att.IsTexture()) { if (att.IsTexture()) {
const Uint64 textureLifetimeId = att.GetTexture()->GetLifetimeId();
XXHASH_VERIFY(XXH64_update(m_hashState, &textureLifetimeId, sizeof(textureLifetimeId)));
const Int textureLevel = att.GetTextureLevel(); const Int textureLevel = att.GetTextureLevel();
XXHASH_VERIFY(XXH64_update(m_hashState, &textureLevel, sizeof(textureLevel))); XXHASH_VERIFY(XXH64_update(m_hashState, &textureLevel, sizeof(textureLevel)));
const TextureUploadTarget textureUploadTarget = att.GetTextureUploadTarget(); const TextureUploadTarget textureUploadTarget = att.GetTextureUploadTarget();
@@ -196,8 +206,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkImageLayout currentLayout = VK_IMAGE_LAYOUT_UNDEFINED; VkImageLayout currentLayout = VK_IMAGE_LAYOUT_UNDEFINED;
if (isDefaultFbo) { if (isDefaultFbo) {
if (attachment >= FramebufferAttachmentType::Color0 && const Bool isDefaultColorAttachment =
attachment <= FramebufferAttachmentType::Color31) { attachment == FramebufferAttachmentType::Color0 ||
(attachment >= FramebufferAttachmentType::FrontLeft &&
attachment <= FramebufferAttachmentType::BackRight);
if (isDefaultColorAttachment) {
currentLayout = m_swapchainObject.GetImageLayout(swapchainImageIndex); currentLayout = m_swapchainObject.GetImageLayout(swapchainImageIndex);
} else if (attachment == FramebufferAttachmentType::Depth || } else if (attachment == FramebufferAttachmentType::Depth ||
attachment == FramebufferAttachmentType::Stencil) { attachment == FramebufferAttachmentType::Stencil) {
@@ -269,13 +282,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (it != m_renderPasses.end()) if (it != m_renderPasses.end())
return it->second; return it->second;
Bool isDefaultFbo = (&fbo == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO.get()); Bool isDefaultFbo = fbo.IsDefaultFramebuffer();
// Color attachment // Color attachment
auto& drawbufs = fbo.GetDrawBuffers(); auto& drawbufs = fbo.GetDrawBuffers();
const Uint32 colorAttachmentSlotCount = static_cast<Uint32>(drawbufs.size()); const Uint32 colorAttachmentSlotCount = static_cast<Uint32>(drawbufs.size());
Int width = 0; const VkExtent2D swapchainExtent = m_swapchainObject.GetExtent();
Int height = 0; // 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<VkAttachmentDescription> attachmentDescriptions; Vector<VkAttachmentDescription> attachmentDescriptions;
attachmentDescriptions.reserve(colorAttachmentSlotCount + 1); attachmentDescriptions.reserve(colorAttachmentSlotCount + 1);
// Keep the full GL draw buffer slot span so fragment outputs targeting GL_NONE map to VK_ATTACHMENT_UNUSED. // 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) .key = VkClearManager::MakePendingClearKey(att)
}); });
} }
const IntVec2 attachmentExtent =
ResolveRenderPassFramebufferExtent(isDefaultFbo, att.GetSize(), swapchainExtent);
if (width == 0) if (width == 0)
width = att.GetSize().x(); width = attachmentExtent.x();
if (height == 0) if (height == 0)
height = att.GetSize().y(); height = attachmentExtent.y();
if (isDefaultFbo) { if (isDefaultFbo) {
const auto& swapchainViews = m_swapchainObject.GetImageViews(); const auto& swapchainViews = m_swapchainObject.GetImageViews();
@@ -379,7 +398,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
trackedColorLayout = textureResource->layout; trackedColorLayout = textureResource->layout;
trackedAttachmentLayouts.emplace_back(TrackedAttachmentLayoutInfo { trackedAttachmentLayouts.emplace_back(TrackedAttachmentLayoutInfo {
.target = TrackedAttachmentTarget::Texture, .target = TrackedAttachmentTarget::Texture,
.texture = texture, .texture = att.GetTexture(),
.textureMipLevel = attachmentMipLevel, .textureMipLevel = attachmentMipLevel,
.finalLayout = desc.finalLayout, .finalLayout = desc.finalLayout,
}); });
@@ -505,7 +524,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
texture.GetExternalIndex()); texture.GetExternalIndex());
trackedAttachmentLayouts.emplace_back(TrackedAttachmentLayoutInfo { trackedAttachmentLayouts.emplace_back(TrackedAttachmentLayoutInfo {
.target = TrackedAttachmentTarget::Texture, .target = TrackedAttachmentTarget::Texture,
.texture = &texture, .texture = selectedDepthStencilAttachment->GetTexture(),
.textureMipLevel = attachmentMipLevel, .textureMipLevel = attachmentMipLevel,
.finalLayout = depthAttachmentDescription.finalLayout, .finalLayout = depthAttachmentDescription.finalLayout,
}); });
@@ -519,9 +538,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
texture, attachmentMipLevel, baseArrayLayer, layerCount, attachmentViewType)); texture, attachmentMipLevel, baseArrayLayer, layerCount, attachmentViewType));
MOBILEGL_ASSERT(attachmentViews.back() != VK_NULL_HANDLE, MOBILEGL_ASSERT(attachmentViews.back() != VK_NULL_HANDLE,
"GetOrCreateRenderPass: GetOrCreateAttachmentView failed at depth attachment"); "GetOrCreateRenderPass: GetOrCreateAttachmentView failed at depth attachment");
const IntVec2 attachmentExtent =
ResolveRenderPassFramebufferExtent(isDefaultFbo, selectedDepthStencilAttachment->GetSize(),
swapchainExtent);
if (width == 0 || height == 0) { if (width == 0 || height == 0) {
width = selectedDepthStencilAttachment->GetSize().x(); width = attachmentExtent.x();
height = selectedDepthStencilAttachment->GetSize().y(); height = attachmentExtent.y();
} }
} }
attachmentDescriptions.emplace_back(depthAttachmentDescription); attachmentDescriptions.emplace_back(depthAttachmentDescription);
@@ -617,7 +639,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
continue; continue;
} }
ClearAttachmentPayload clearPayload{}; ClearAttachmentPayload clearPayload{};
if (!s_clearManager->GetPendingClear(pending.key, clearPayload)) { SharedPtr<MG_State::GLState::ITextureObject> liveTexture;
if (!s_clearManager->GetPendingClear(pending.key, clearPayload, liveTexture)) {
continue; continue;
} }
if ((clearPayload.mask & GL_COLOR_BUFFER_BIT) != 0) { if ((clearPayload.mask & GL_COLOR_BUFFER_BIT) != 0) {
@@ -625,7 +648,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
clearPayload.color.x(), clearPayload.color.x(),
clearPayload.color.y(), clearPayload.color.y(),
clearPayload.color.z(), clearPayload.color.z(),
ResolveColorClearAlpha(pending.key.texture, clearPayload.color.w()) ResolveColorClearAlpha(liveTexture.get(), clearPayload.color.w())
}; };
} }
if ((clearPayload.mask & GL_DEPTH_BUFFER_BIT) != 0) { if ((clearPayload.mask & GL_DEPTH_BUFFER_BIT) != 0) {
@@ -660,11 +683,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
switch (trackedAttachment.target) { switch (trackedAttachment.target) {
case TrackedAttachmentTarget::Texture: case TrackedAttachmentTarget::Texture:
MOBILEGL_ASSERT(s_textureManager != nullptr, "EndRenderPass: texture manager is null"); MOBILEGL_ASSERT(s_textureManager != nullptr, "EndRenderPass: texture manager is null");
s_textureManager->UpdateTrackedImageLayoutAfterAttachmentWrite( if (const auto texture = trackedAttachment.texture.lock()) {
commandBuffer, s_textureManager->UpdateTrackedImageLayoutAfterAttachmentWrite(
trackedAttachment.texture, commandBuffer,
trackedAttachment.textureMipLevel, texture.get(),
trackedAttachment.finalLayout); trackedAttachment.textureMipLevel,
trackedAttachment.finalLayout);
}
break; break;
case TrackedAttachmentTarget::SwapchainColor: case TrackedAttachmentTarget::SwapchainColor:
MOBILEGL_ASSERT(s_swapchainObject != nullptr, "EndRenderPass: swapchain object is null"); MOBILEGL_ASSERT(s_swapchainObject != nullptr, "EndRenderPass: swapchain object is null");
@@ -31,7 +31,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
struct TrackedAttachmentLayoutInfo { struct TrackedAttachmentLayoutInfo {
TrackedAttachmentTarget target = TrackedAttachmentTarget::Texture; TrackedAttachmentTarget target = TrackedAttachmentTarget::Texture;
MG_State::GLState::ITextureObject* texture = nullptr; WeakPtr<MG_State::GLState::ITextureObject> texture;
Uint32 textureMipLevel = 0; Uint32 textureMipLevel = 0;
Uint32 swapchainImageIndex = 0; Uint32 swapchainImageIndex = 0;
VkImageLayout finalLayout = VK_IMAGE_LAYOUT_UNDEFINED; VkImageLayout finalLayout = VK_IMAGE_LAYOUT_UNDEFINED;
@@ -45,6 +45,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
DepthStencilAttachmentLoadInfo ResolveDepthStencilAttachmentLoadInfo( DepthStencilAttachmentLoadInfo ResolveDepthStencilAttachmentLoadInfo(
VkImageLayout trackedLayout, Bool clearDepth, Bool clearStencil); VkImageLayout trackedLayout, Bool clearDepth, Bool clearStencil);
IntVec2 ResolveRenderPassFramebufferExtent(Bool isDefaultFbo, const TextureSize& attachmentExtent,
VkExtent2D swapchainExtent);
struct RenderPassEntry { struct RenderPassEntry {
static inline VkDevice s_device; static inline VkDevice s_device;
@@ -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, static void GetImageTransitionDestinationState(VkImageLayout newLayout,
VkPipelineStageFlags& outDstStageMask, VkPipelineStageFlags& outDstStageMask,
VkAccessFlags& outDstAccessMask) { VkAccessFlags& outDstAccessMask) {
@@ -580,6 +588,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void VkTextureManager::Shutdown() { void VkTextureManager::Shutdown() {
DestroyDeferredReleases(); DestroyDeferredReleases();
m_textureResources.clear(); m_textureResources.clear();
m_aliveObjects.clear();
m_device = VK_NULL_HANDLE; m_device = VK_NULL_HANDLE;
m_physicalDevice = VK_NULL_HANDLE; m_physicalDevice = VK_NULL_HANDLE;
@@ -600,28 +609,56 @@ namespace MobileGL::MG_Backend::DirectVulkan {
CollectDeferredReleases(frameIndex); 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<TextureIdentity> 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) { VkTextureManager::TextureResource* VkTextureManager::SyncTextureAndGetDescriptor(MG_State::GLState::ITextureObject& texture) {
MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE, "SyncTextureAndGetDescriptor: m_device == VK_NULL_HANDLE"); 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()) { if (aliveIt != m_aliveObjects.end() && aliveIt->second.expired()) {
auto resourceIt = m_textureResources.find(&texture); EraseTrackedTexture(aliveIt->first);
if (resourceIt != m_textureResources.end()) {
DeferResourceRelease(Move(resourceIt->second));
m_textureResources.erase(resourceIt);
}
m_aliveObjects.erase(aliveIt);
} }
const auto& liveTexture = MG_State::pGLContext->GetTextureObject(texture.GetExternalIndex()); const auto& liveTexture = MG_State::pGLContext->GetTextureObject(texture.GetExternalIndex());
if (liveTexture && liveTexture.get() == &texture) { if (liveTexture && liveTexture.get() == &texture) {
m_aliveObjects[&texture] = WeakPtr<MG_State::GLState::ITextureObject>(liveTexture); m_aliveObjects[identity] = WeakPtr<MG_State::GLState::ITextureObject>(liveTexture);
PruneStaleTextureAliases(&texture);
} }
auto it = m_textureResources.find(&texture); auto it = m_textureResources.find(identity);
if (it == m_textureResources.end()) { if (it == m_textureResources.end()) {
TextureResource initial{}; TextureResource initial{};
auto [insertIt, _] = m_textureResources.emplace(&texture, Move(initial)); auto [insertIt, _] = m_textureResources.emplace(identity, Move(initial));
it = insertIt; it = insertIt;
} }
@@ -737,7 +774,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void VkTextureManager::UpdateTrackedImageLayout(MG_State::GLState::ITextureObject* texture, VkImageLayout newLayout) { void VkTextureManager::UpdateTrackedImageLayout(MG_State::GLState::ITextureObject* texture, VkImageLayout newLayout) {
MOBILEGL_ASSERT(texture != nullptr, "UpdateTrackedImageLayout: texture is null"); 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(), MOBILEGL_ASSERT(it != m_textureResources.end(),
"UpdateTrackedImageLayout: textureId=%d has no tracked resource", texture->GetExternalIndex()); "UpdateTrackedImageLayout: textureId=%d has no tracked resource", texture->GetExternalIndex());
MOBILEGL_ASSERT(it->second.image != VK_NULL_HANDLE, MOBILEGL_ASSERT(it->second.image != VK_NULL_HANDLE,
@@ -750,7 +787,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint32 writtenMipLevel, Uint32 writtenMipLevel,
VkImageLayout newLayout) { VkImageLayout newLayout) {
MOBILEGL_ASSERT(texture != nullptr, "UpdateTrackedImageLayoutAfterAttachmentWrite: texture is null"); 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(), MOBILEGL_ASSERT(it != m_textureResources.end(),
"UpdateTrackedImageLayoutAfterAttachmentWrite: textureId=%d has no tracked resource", "UpdateTrackedImageLayoutAfterAttachmentWrite: textureId=%d has no tracked resource",
texture->GetExternalIndex()); texture->GetExternalIndex());
@@ -920,20 +957,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (m_gcCounter != 0) { if (m_gcCounter != 0) {
return 0; return 0;
} }
SizeT count = 0;
for (auto it = m_aliveObjects.begin(); it != m_aliveObjects.end();) { Vector<MG_State::GLState::ITextureObject*> expiredTextures;
auto current = it++; expiredTextures.reserve(m_aliveObjects.size());
if (current->second.expired()) { for (auto it = m_aliveObjects.begin(); it != m_aliveObjects.end(); ++it) {
auto resourceIt = m_textureResources.find(current->first); if (it->second.expired()) {
if (resourceIt != m_textureResources.end()) { expiredTextures.emplace_back(it->first.texture);
DeferResourceRelease(Move(resourceIt->second));
m_textureResources.erase(resourceIt);
}
m_aliveObjects.erase(current);
++count;
} }
} }
return count; for (auto* texture : expiredTextures) {
PruneStaleTextureAliases(texture);
}
return expiredTextures.size();
} }
Bool VkTextureManager::SyncTexture(MG_State::GLState::ITextureObject &texture, Bool VkTextureManager::SyncTexture(MG_State::GLState::ITextureObject &texture,
@@ -12,6 +12,7 @@
#include <Includes.h> #include <Includes.h>
#include <MG_State/GLState/TextureState/TextureObject.h> #include <MG_State/GLState/TextureState/TextureObject.h>
#include <vk_mem_alloc.h> #include <vk_mem_alloc.h>
#include <unordered_map>
namespace MobileGL::MG_State::GLState { namespace MobileGL::MG_State::GLState {
class ITextureObject; class ITextureObject;
@@ -20,6 +21,23 @@ class ITextureObject;
namespace MobileGL::MG_Backend::DirectVulkan { namespace MobileGL::MG_Backend::DirectVulkan {
class VkTextureManager { class VkTextureManager {
public: 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<MG_State::GLState::ITextureObject*>{}(key.texture);
hash ^= std::hash<Uint64>{}(key.lifetimeId) + 0x9e3779b9u + (hash << 6) + (hash >> 2);
return hash;
}
};
struct InitInfo { struct InitInfo {
VkDevice device = VK_NULL_HANDLE; VkDevice device = VK_NULL_HANDLE;
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
@@ -212,6 +230,9 @@ private:
void DeferViewRelease(VkImageView view); void DeferViewRelease(VkImageView view);
void CollectDeferredReleases(Uint32 frameIndex); void CollectDeferredReleases(Uint32 frameIndex);
void DestroyDeferredReleases(); 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; VkDevice m_device = VK_NULL_HANDLE;
VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE; VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
@@ -221,8 +242,8 @@ private:
Uint32 m_currentFrameIndex = 0; Uint32 m_currentFrameIndex = 0;
Uint8 m_gcCounter = 0; Uint8 m_gcCounter = 0;
UnorderedMap<MG_State::GLState::ITextureObject*, WeakPtr<MG_State::GLState::ITextureObject>> m_aliveObjects; std::unordered_map<TextureIdentity, WeakPtr<MG_State::GLState::ITextureObject>, TextureIdentityHash> m_aliveObjects;
UnorderedMap<MG_State::GLState::ITextureObject*, TextureResource> m_textureResources; std::unordered_map<TextureIdentity, TextureResource, TextureIdentityHash> m_textureResources;
Vector<Vector<TextureResource>> m_deferredReleases; Vector<Vector<TextureResource>> m_deferredReleases;
Vector<Vector<VkImageView>> m_deferredViewReleases; Vector<Vector<VkImageView>> m_deferredViewReleases;
}; };
@@ -689,7 +689,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (trackedAttachment.target != TrackedAttachmentTarget::Texture) { if (trackedAttachment.target != TrackedAttachmentTarget::Texture) {
continue; continue;
} }
if (trackedAttachment.texture == &texture) { const auto trackedTexture = trackedAttachment.texture.lock();
if (trackedTexture && trackedTexture.get() == &texture) {
return true; return true;
} }
} }
@@ -1064,9 +1065,33 @@ void main() {
static Bool ResolveColorBlitBinding(MG_State::GLState::FramebufferObject& fbo, Bool isReadFramebuffer, static Bool ResolveColorBlitBinding(MG_State::GLState::FramebufferObject& fbo, Bool isReadFramebuffer,
Uint32 swapchainImageIndex, SwapchainObject& swapchainObject, Uint32 swapchainImageIndex, SwapchainObject& swapchainObject,
VkTextureManager& textureManager, BlitImageBinding& outBinding) { VkTextureManager& textureManager, BlitImageBinding& outBinding) {
const Bool isDefaultFbo = (&fbo == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO.get()); const Bool isDefaultFbo = fbo.IsDefaultFramebuffer();
const FramebufferAttachmentType attachmentType = const FramebufferAttachmentType attachmentType =
isReadFramebuffer ? fbo.GetReadBuffer() : fbo.GetDrawBuffers()[0]; 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<Int>(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<Int>(extent.width), static_cast<Int>(extent.height)};
outBinding.mipLevel = 0;
outBinding.mipLevelCount = 1;
outBinding.baseArrayLayer = 0;
outBinding.layerCount = 1;
return true;
}
if (attachmentType < FramebufferAttachmentType::Color0 || attachmentType > FramebufferAttachmentType::Color31) { if (attachmentType < FramebufferAttachmentType::Color0 || attachmentType > FramebufferAttachmentType::Color31) {
MGLOG_E("BlitFramebuffer only supports color attachments right now (attachment=%d)", MGLOG_E("BlitFramebuffer only supports color attachments right now (attachment=%d)",
static_cast<Int>(attachmentType)); static_cast<Int>(attachmentType));
@@ -1090,20 +1115,6 @@ void main() {
auto* texture = attachment.GetTexture().get(); auto* texture = attachment.GetTexture().get();
MOBILEGL_ASSERT(texture != nullptr, "ResolveColorBlitBinding: texture attachment is null"); 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<Int>(extent.width), static_cast<Int>(extent.height)};
outBinding.mipLevel = 0;
outBinding.mipLevelCount = 1;
outBinding.baseArrayLayer = 0;
outBinding.layerCount = 1;
return true;
}
auto* resource = textureManager.SyncTextureAndGetDescriptor(*texture); auto* resource = textureManager.SyncTextureAndGetDescriptor(*texture);
if (resource == nullptr) { if (resource == nullptr) {
@@ -1134,8 +1145,7 @@ void main() {
VkTextureManager& textureManager, VkTextureManager& textureManager,
VkImageAspectFlags requiredAspectMask, VkImageAspectFlags requiredAspectMask,
BlitImageBinding& outBinding) { BlitImageBinding& outBinding) {
const Bool isDefaultFbo = const Bool isDefaultFbo = fbo.IsDefaultFramebuffer();
(&fbo == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO.get());
const auto attachmentType = ResolveFramebufferCopyAttachmentType(fbo, isReadFramebuffer, requiredAspectMask); const auto attachmentType = ResolveFramebufferCopyAttachmentType(fbo, isReadFramebuffer, requiredAspectMask);
if (attachmentType == FramebufferAttachmentType::None) { if (attachmentType == FramebufferAttachmentType::None) {
MGLOG_E("BlitFramebuffer skipped: unsupported aspect mask=0x%x", MGLOG_E("BlitFramebuffer skipped: unsupported aspect mask=0x%x",
@@ -1250,8 +1260,7 @@ void main() {
VkTextureManager& textureManager, VkTextureManager& textureManager,
VkImageAspectFlags requiredAspectMask, VkImageAspectFlags requiredAspectMask,
BlitImageBinding& outBinding) { BlitImageBinding& outBinding) {
const Bool isDefaultFbo = const Bool isDefaultFbo = fbo.IsDefaultFramebuffer();
(&fbo == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO.get());
const auto attachmentType = ResolveFramebufferCopyAttachmentType(fbo, true, requiredAspectMask); const auto attachmentType = ResolveFramebufferCopyAttachmentType(fbo, true, requiredAspectMask);
if (attachmentType == FramebufferAttachmentType::None) { if (attachmentType == FramebufferAttachmentType::None) {
MGLOG_E("CopyTexSubImage2D skipped: unsupported source aspect mask=0x%x", MGLOG_E("CopyTexSubImage2D skipped: unsupported source aspect mask=0x%x",
@@ -1545,7 +1554,7 @@ void main() {
ProgramFactory::CompileOptionFlags flags = ProgramFactory::CompileOptionBit::PositionZRemap; ProgramFactory::CompileOptionFlags flags = ProgramFactory::CompileOptionBit::PositionZRemap;
const auto& currentDrawFBO = const auto& currentDrawFBO =
MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
if (currentDrawFBO == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO) { if (currentDrawFBO != nullptr && currentDrawFBO->IsDefaultFramebuffer()) {
flags |= ProgramFactory::CompileOptionBit::PositionYFlip; flags |= ProgramFactory::CompileOptionBit::PositionYFlip;
switch (preTransform) { switch (preTransform) {
case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR: case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR:
@@ -2703,8 +2712,7 @@ void main() {
const auto& drawFboBinding = const auto& drawFboBinding =
MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
MOBILEGL_ASSERT(drawFboBinding != nullptr, "GetOrCreatePipeline: draw framebuffer is null"); MOBILEGL_ASSERT(drawFboBinding != nullptr, "GetOrCreatePipeline: draw framebuffer is null");
const Bool isDefaultDrawFbo = const Bool isDefaultDrawFbo = drawFboBinding->IsDefaultFramebuffer();
drawFboBinding.get() == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO.get();
const auto& drawBuffers = drawFboBinding->GetDrawBuffers(); const auto& drawBuffers = drawFboBinding->GetDrawBuffers();
auto resolveCompleteColorAttachmentTexture = [&](Uint32 drawBufferIndex) -> MG_State::GLState::ITextureObject* { auto resolveCompleteColorAttachmentTexture = [&](Uint32 drawBufferIndex) -> MG_State::GLState::ITextureObject* {
if (isDefaultDrawFbo || drawBufferIndex >= drawBuffers.size()) { if (isDefaultDrawFbo || drawBufferIndex >= drawBuffers.size()) {
@@ -3430,8 +3438,7 @@ void main() {
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
GLenum filter) { GLenum filter) {
const Bool drawIsDefaultFbo = const Bool drawIsDefaultFbo = drawFbo.IsDefaultFramebuffer();
(&drawFbo == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO.get());
if (!drawIsDefaultFbo) { if (!drawIsDefaultFbo) {
return false; return false;
} }
@@ -3607,10 +3614,8 @@ void main() {
VkRenderPassManager::EndRenderPass(frame.commandBuffer); VkRenderPassManager::EndRenderPass(frame.commandBuffer);
} }
const Bool readIsDefaultFbo = const Bool readIsDefaultFbo = readFbo->IsDefaultFramebuffer();
(readFbo == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO); const Bool drawIsDefaultFbo = drawFbo->IsDefaultFramebuffer();
const Bool drawIsDefaultFbo =
(drawFbo == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO);
if (isColorBlit && drawIsDefaultFbo && if (isColorBlit && drawIsDefaultFbo &&
RequiresShaderBlitToDefaultFramebuffer(m_swapchainObject.GetPreTransform())) { RequiresShaderBlitToDefaultFramebuffer(m_swapchainObject.GetPreTransform())) {
if (TryBlitToDefaultFramebufferWithShader(frame, *readFbo, *drawFbo, if (TryBlitToDefaultFramebufferWithShader(frame, *readFbo, *drawFbo,
@@ -3959,8 +3964,7 @@ void main() {
VkRenderPassManager::EndRenderPass(frame.commandBuffer); VkRenderPassManager::EndRenderPass(frame.commandBuffer);
} }
const Bool readIsDefaultFbo = const Bool readIsDefaultFbo = readFbo->IsDefaultFramebuffer();
(readFbo == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO);
BlitImageBinding dstBinding{}; BlitImageBinding dstBinding{};
if (!ResolveTextureCopyDestinationBinding(*destinationTexture, static_cast<Uint32>(level), *m_textureManager, if (!ResolveTextureCopyDestinationBinding(*destinationTexture, static_cast<Uint32>(level), *m_textureManager,
@@ -4161,8 +4165,7 @@ void main() {
VkRenderPassManager::EndRenderPass(frame.commandBuffer); VkRenderPassManager::EndRenderPass(frame.commandBuffer);
} }
const Bool readIsDefaultFbo = const Bool readIsDefaultFbo = readFbo->IsDefaultFramebuffer();
(readFbo == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO);
BlitImageBinding srcBinding{}; BlitImageBinding srcBinding{};
if (!ResolveColorBlitBinding(*readFbo, true, m_imageIndexAcquired, m_swapchainObject, *m_textureManager, if (!ResolveColorBlitBinding(*readFbo, true, m_imageIndexAcquired, m_swapchainObject, *m_textureManager,
srcBinding)) { srcBinding)) {
@@ -5582,7 +5585,8 @@ void main() {
} }
ClearAttachmentPayload clearPayload{}; ClearAttachmentPayload clearPayload{};
if (!m_clearManager->GetPendingClear(pending.key, clearPayload)) { SharedPtr<MG_State::GLState::ITextureObject> liveTexture;
if (!m_clearManager->GetPendingClear(pending.key, clearPayload, liveTexture)) {
continue; continue;
} }
@@ -5595,7 +5599,7 @@ void main() {
clearPayload.color.x(), clearPayload.color.x(),
clearPayload.color.y(), clearPayload.color.y(),
clearPayload.color.z(), clearPayload.color.z(),
ResolveColorClearAlpha(pending.key.texture, clearPayload.color.w()) ResolveColorClearAlpha(liveTexture.get(), clearPayload.color.w())
}; };
} else { } else {
if ((clearPayload.mask & GL_DEPTH_BUFFER_BIT) != 0) { if ((clearPayload.mask & GL_DEPTH_BUFFER_BIT) != 0) {
+3
View File
@@ -35,7 +35,10 @@ namespace MobileGL::MG_Impl {
stencilTex->AllocateStorage(TextureUploadTarget::Texture2D, 0, {{512, 512, 1}, 0}); stencilTex->AllocateStorage(TextureUploadTarget::Texture2D, 0, {{512, 512, 1}, 0});
// stencilTex->SetMipmapLevel({{512, 512, 1}, 0, false, 0, {nullptr, 0}}); // stencilTex->SetMipmapLevel({{512, 512, 1}, 0, false, 0, {nullptr, 0}});
fbo0->AttachTexture(FramebufferAttachmentType::Color0, colorTex); fbo0->AttachTexture(FramebufferAttachmentType::Color0, colorTex);
fbo0->AttachTexture(FramebufferAttachmentType::FrontLeft, colorTex);
fbo0->AttachTexture(FramebufferAttachmentType::FrontRight, colorTex);
fbo0->AttachTexture(FramebufferAttachmentType::BackLeft, colorTex); fbo0->AttachTexture(FramebufferAttachmentType::BackLeft, colorTex);
fbo0->AttachTexture(FramebufferAttachmentType::BackRight, colorTex);
fbo0->AttachTexture(FramebufferAttachmentType::Depth, depthTex); fbo0->AttachTexture(FramebufferAttachmentType::Depth, depthTex);
fbo0->AttachTexture(FramebufferAttachmentType::Stencil, stencilTex); fbo0->AttachTexture(FramebufferAttachmentType::Stencil, stencilTex);
GLImpl::FramebufferImpl::pDefaultFramebufferInfo = GLImpl::FramebufferImpl::pDefaultFramebufferInfo =
@@ -131,6 +131,7 @@ namespace MobileGL {
Uint16 GetObjectVersion() const { return m_objectVersion; } Uint16 GetObjectVersion() const { return m_objectVersion; }
Uint GetExternalIndex() const; Uint GetExternalIndex() const;
Bool IsDefaultFramebuffer() const { return m_externalIndex == 0; }
private: private:
void BumpAttachmentVersion(FramebufferAttachmentType type); void BumpAttachmentVersion(FramebufferAttachmentType type);
@@ -13,9 +13,15 @@
namespace MobileGL { namespace MobileGL {
namespace MG_State { namespace MG_State {
namespace GLState { namespace GLState {
static std::atomic<Uint64> s_nextTextureLifetimeId = 1;
// TextureObjectBase implementations // TextureObjectBase implementations
Uint64 TextureObjectBase::AllocateLifetimeId() {
return s_nextTextureLifetimeId.fetch_add(1, std::memory_order_relaxed);
}
TextureObjectBase::TextureObjectBase(TextureTarget target, Uint externalIndex) TextureObjectBase::TextureObjectBase(TextureTarget target, Uint externalIndex)
: m_target(target), m_externalIndex(externalIndex) { : m_externalIndex(externalIndex), m_lifetimeId(AllocateLifetimeId()), m_target(target) {
m_sampler = MakeShared<SamplerObject>(0); m_sampler = MakeShared<SamplerObject>(0);
} }
@@ -155,6 +161,10 @@ namespace MobileGL {
m_fixedSampleLocations = fixedSampleLocations; m_fixedSampleLocations = fixedSampleLocations;
} }
Uint64 TextureObjectBase::GetLifetimeId() const {
return m_lifetimeId;
}
Uint TextureObjectWithOneMipmap::GetMipmapLevelCount() const { Uint TextureObjectWithOneMipmap::GetMipmapLevelCount() const {
return m_textureStorage.GetLevelCount(); return m_textureStorage.GetLevelCount();
} }
@@ -44,6 +44,7 @@ namespace MobileGL::MG_State::GLState {
virtual void SetSamples(Int samples) = 0; virtual void SetSamples(Int samples) = 0;
virtual Bool HasFixedSampleLocations() const = 0; virtual Bool HasFixedSampleLocations() const = 0;
virtual void SetFixedSampleLocations(Bool fixedSampleLocations) = 0; virtual void SetFixedSampleLocations(Bool fixedSampleLocations) = 0;
virtual Uint64 GetLifetimeId() const = 0;
protected: protected:
virtual Uint GetIndexOfTextureUploadTarget(TextureUploadTarget target) const = 0; virtual Uint GetIndexOfTextureUploadTarget(TextureUploadTarget target) const = 0;
@@ -75,9 +76,13 @@ namespace MobileGL::MG_State::GLState {
void SetSamples(Int samples) override; void SetSamples(Int samples) override;
Bool HasFixedSampleLocations() const override; Bool HasFixedSampleLocations() const override;
void SetFixedSampleLocations(Bool fixedSampleLocations) override; void SetFixedSampleLocations(Bool fixedSampleLocations) override;
Uint64 GetLifetimeId() const override;
protected: protected:
static Uint64 AllocateLifetimeId();
const Uint m_externalIndex; const Uint m_externalIndex;
const Uint64 m_lifetimeId;
const TextureTarget m_target = TextureTarget::Unknown; const TextureTarget m_target = TextureTarget::Unknown;
TextureInternalFormat m_internalFormat = TextureInternalFormat::Unknown; TextureInternalFormat m_internalFormat = TextureInternalFormat::Unknown;
SharedPtr<SamplerObject> m_sampler = nullptr; SharedPtr<SamplerObject> m_sampler = nullptr;
@@ -105,6 +105,20 @@ TEST_F(FramebufferTest, CreateFramebuffersCreatesObjectsImmediately) {
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); 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) { TEST_F(FramebufferTest, NamedFramebufferTextureAttachesWithoutChangingBindings) {
GLuint framebuffer = 0; GLuint framebuffer = 0;
MG_Impl::GLImpl::CreateFramebuffers(1, &framebuffer); MG_Impl::GLImpl::CreateFramebuffers(1, &framebuffer);
@@ -259,6 +273,46 @@ TEST_F(FramebufferTest, NamedFramebufferDrawBuffersDoNotModifyDefaultFramebuffer
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); 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) { TEST_F(FramebufferTest, ClearNamedFramebufferfvUsesNamedObjectWithoutChangingBindings) {
GLuint framebuffer = 0; GLuint framebuffer = 0;
MG_Impl::GLImpl::CreateFramebuffers(1, &framebuffer); MG_Impl::GLImpl::CreateFramebuffers(1, &framebuffer);
+12
View File
@@ -165,6 +165,18 @@ TEST(DirectVulkanSanity, AdvertisesTextureStorageForDirectStateAccess) {
EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_texture_storage), extensions.end()); 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) { TEST(DirectVulkanSanity, AdvertisesVoxyRequiredRenderingExtensionsWithoutRaisingGLVersion) {
MobileGL::MG_Backend::DirectVulkan::BackendObject_DirectVulkan backend; MobileGL::MG_Backend::DirectVulkan::BackendObject_DirectVulkan backend;
const auto& rendererInfo = backend.GetRendererInfo().RendererGLInfo; const auto& rendererInfo = backend.GetRendererInfo().RendererGLInfo;
+18
View File
@@ -18,3 +18,21 @@ target_link_libraries(
include(GoogleTest) include(GoogleTest)
gtest_discover_tests(TextureTest) 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)
@@ -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 <gtest/gtest.h>
#include "Includes.h"
#include "Init.h"
#include <MG_Backend/DirectVulkan/Renderer/VkClearManager.h>
#include <MG_Impl/GLImpl/Texture/GL_Texture.h>
#include <MG_State/GLState/Core.h>
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<GLuint> textureNames(kTextureCount, 0);
Vector<PendingClearKey> 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<GLsizei>(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();
}
@@ -36,10 +36,12 @@ namespace MobileGL {
return FramebufferAttachmentType::Depth; return FramebufferAttachmentType::Depth;
case GL_STENCIL_ATTACHMENT: case GL_STENCIL_ATTACHMENT:
return FramebufferAttachmentType::Stencil; return FramebufferAttachmentType::Stencil;
case GL_FRONT:
case GL_FRONT_LEFT: case GL_FRONT_LEFT:
return FramebufferAttachmentType::FrontLeft; return FramebufferAttachmentType::FrontLeft;
case GL_FRONT_RIGHT: case GL_FRONT_RIGHT:
return FramebufferAttachmentType::FrontRight; return FramebufferAttachmentType::FrontRight;
case GL_BACK:
case GL_BACK_LEFT: case GL_BACK_LEFT:
return FramebufferAttachmentType::BackLeft; return FramebufferAttachmentType::BackLeft;
case GL_BACK_RIGHT: case GL_BACK_RIGHT:
@@ -59,4 +61,4 @@ namespace MobileGL {
} }
} }
} // namespace MG_Util } // namespace MG_Util
} // namespace MobileGL } // namespace MobileGL