[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;
}
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<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,
@@ -121,25 +193,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void VkClearManager::QueueClear(const ClearAttachmentPayload& clearPayload,
const SharedPtr<MG_State::GLState::ITextureObject>& texture) {
if (clearPayload.mask == 0) {
if (clearPayload.mask == 0 || !texture) {
return;
}
WeakPtr<MG_State::GLState::ITextureObject> 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<std::mutex> 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<MG_State::GLState::ITextureObject> 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<std::mutex> 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<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) {
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) {
@@ -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<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;
}
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<void*>(key.texture),
static_cast<unsigned long long>(key.textureLifetimeId),
key.mipLevel, key.baseArrayLayer, key.layerCount,
static_cast<Uint32>(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<PendingClearEntry>& 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<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();
}
@@ -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<std::mutex> 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<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) {
@@ -268,26 +358,26 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
SizeT VkClearManager::CollectGarbage() {
const std::lock_guard<std::mutex> 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<TextureIdentity> 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
@@ -14,6 +14,7 @@
#include "MG_Util/Math/VectorTypes.h"
#include <Includes.h>
#include <unordered_map>
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<MG_State::GLState::ITextureObject*>{}(key.texture);
const SizeT textureLifetimeHash = std::hash<Uint64>{}(key.textureLifetimeId);
const SizeT mipHash = std::hash<Uint32>{}(key.mipLevel);
const SizeT layerHash = std::hash<Uint32>{}(key.baseArrayLayer);
const SizeT layerCountHash = std::hash<Uint32>{}(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<MG_State::GLState::ITextureObject*>{}(key.texture);
hash ^= std::hash<Uint64>{}(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<MG_State::GLState::ITextureObject>& outTexture);
Bool GetPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment,
ClearAttachmentPayload& outPayload);
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);
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<MG_State::GLState::ITextureObject>& outTexture);
Bool LockTextureLocked(const PendingClearKey& key,
SharedPtr<MG_State::GLState::ITextureObject>& outTexture);
Uint8 m_gcCounter = 0;
UnorderedMap<PendingClearKey, ClearAttachmentPayload, PendingClearKeyHash> m_pendingClears;
UnorderedMap<MG_State::GLState::ITextureObject*, WeakPtr<MG_State::GLState::ITextureObject>> m_aliveObjects;
mutable std::mutex m_mutex;
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
@@ -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<Int>(swapchainExtent.width), static_cast<Int>(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<Uint32>(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<VkAttachmentDescription> 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<MG_State::GLState::ITextureObject> 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");
@@ -31,7 +31,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
struct TrackedAttachmentLayoutInfo {
TrackedAttachmentTarget target = TrackedAttachmentTarget::Texture;
MG_State::GLState::ITextureObject* texture = nullptr;
WeakPtr<MG_State::GLState::ITextureObject> 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;
@@ -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<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) {
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<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()) {
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<MG_State::GLState::ITextureObject*> 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,
@@ -12,6 +12,7 @@
#include <Includes.h>
#include <MG_State/GLState/TextureState/TextureObject.h>
#include <vk_mem_alloc.h>
#include <unordered_map>
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<MG_State::GLState::ITextureObject*>{}(key.texture);
hash ^= std::hash<Uint64>{}(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<MG_State::GLState::ITextureObject*, WeakPtr<MG_State::GLState::ITextureObject>> m_aliveObjects;
UnorderedMap<MG_State::GLState::ITextureObject*, TextureResource> m_textureResources;
std::unordered_map<TextureIdentity, WeakPtr<MG_State::GLState::ITextureObject>, TextureIdentityHash> m_aliveObjects;
std::unordered_map<TextureIdentity, TextureResource, TextureIdentityHash> m_textureResources;
Vector<Vector<TextureResource>> m_deferredReleases;
Vector<Vector<VkImageView>> m_deferredViewReleases;
};
@@ -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<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) {
MGLOG_E("BlitFramebuffer only supports color attachments right now (attachment=%d)",
static_cast<Int>(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<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);
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<Uint32>(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<MG_State::GLState::ITextureObject> 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) {
+3
View File
@@ -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 =
@@ -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);
@@ -13,9 +13,15 @@
namespace MobileGL {
namespace MG_State {
namespace GLState {
static std::atomic<Uint64> 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<SamplerObject>(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();
}
@@ -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<SamplerObject> m_sampler = nullptr;
@@ -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);
+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());
}
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;
+18
View File
@@ -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)
@@ -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;
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
} // namespace MobileGL