mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +09:00
[Fix] (DirectVulkan): stop fence-waiting out-of-band texture uploads; reclaim transients asynchronously
This commit is contained in:
@@ -607,6 +607,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VkTextureManager::Shutdown() {
|
void VkTextureManager::Shutdown() {
|
||||||
|
if (m_device != VK_NULL_HANDLE) {
|
||||||
|
ReclaimCompletedUploads(/*waitAll=*/true);
|
||||||
|
}
|
||||||
DestroyDeferredReleases();
|
DestroyDeferredReleases();
|
||||||
m_textureResources.clear();
|
m_textureResources.clear();
|
||||||
m_aliveObjects.clear();
|
m_aliveObjects.clear();
|
||||||
@@ -629,6 +632,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
frameIndex, m_deferredViewReleases.size());
|
frameIndex, m_deferredViewReleases.size());
|
||||||
m_currentFrameIndex = frameIndex;
|
m_currentFrameIndex = frameIndex;
|
||||||
CollectDeferredReleases(frameIndex);
|
CollectDeferredReleases(frameIndex);
|
||||||
|
ReclaimCompletedUploads();
|
||||||
|
|
||||||
// Frame-boundary GC: every 64 frame boundaries (~1 s at 60 fps) bounds the reclaim
|
// Frame-boundary GC: every 64 frame boundaries (~1 s at 60 fps) bounds the reclaim
|
||||||
// latency for dead textures regardless of draw traffic — workloads that churn
|
// latency for dead textures regardless of draw traffic — workloads that churn
|
||||||
@@ -1708,6 +1712,28 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
m_deferredViewReleases[frameIndex].clear();
|
m_deferredViewReleases[frameIndex].clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VkTextureManager::ReclaimCompletedUploads(Bool waitAll) {
|
||||||
|
if (m_pendingUploadReclaims.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SizeT completed = 0;
|
||||||
|
for (; completed < m_pendingUploadReclaims.size(); ++completed) {
|
||||||
|
PendingUploadReclaim& entry = m_pendingUploadReclaims[completed];
|
||||||
|
if (waitAll) {
|
||||||
|
VK_VERIFY(vkWaitForFences(m_device, 1, &entry.fence, VK_TRUE, UINT64_MAX),
|
||||||
|
"vkWaitForFences(texture upload reclaim)");
|
||||||
|
} else if (vkGetFenceStatus(m_device, entry.fence) != VK_SUCCESS) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
vkDestroyFence(m_device, entry.fence, nullptr);
|
||||||
|
vkFreeCommandBuffers(m_device, m_commandPool, 1, &entry.commandBuffer);
|
||||||
|
vmaDestroyBuffer(m_allocator, entry.stagingBuffer, entry.stagingAllocation);
|
||||||
|
}
|
||||||
|
m_pendingUploadReclaims.erase(m_pendingUploadReclaims.begin(),
|
||||||
|
m_pendingUploadReclaims.begin() + static_cast<std::ptrdiff_t>(completed));
|
||||||
|
}
|
||||||
|
|
||||||
void VkTextureManager::DestroyDeferredReleases() {
|
void VkTextureManager::DestroyDeferredReleases() {
|
||||||
for (auto& deferredReleases : m_deferredReleases) {
|
for (auto& deferredReleases : m_deferredReleases) {
|
||||||
deferredReleases.clear();
|
deferredReleases.clear();
|
||||||
@@ -2014,11 +2040,23 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VK_VERIFY(vkCreateFence(m_device, &fenceInfo, nullptr, &uploadFence), "vkCreateFence(texture upload)");
|
VK_VERIFY(vkCreateFence(m_device, &fenceInfo, nullptr, &uploadFence), "vkCreateFence(texture upload)");
|
||||||
|
|
||||||
VK_VERIFY(vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, uploadFence), "vkQueueSubmit(texture)");
|
VK_VERIFY(vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, uploadFence), "vkQueueSubmit(texture)");
|
||||||
VK_VERIFY(vkWaitForFences(m_device, 1, &uploadFence, VK_TRUE, UINT64_MAX), "vkWaitForFences(texture upload)");
|
// Do NOT wait the fence here: this submit sits behind the previous
|
||||||
vkDestroyFence(m_device, uploadFence, nullptr);
|
// frame's rendering on the queue, so a synchronous wait stalls the CPU
|
||||||
vkFreeCommandBuffers(m_device, m_commandPool, 1, &commandBuffer);
|
// until the GPU drains - a per-frame vkQueueWaitIdle for any workload
|
||||||
|
// with animated textures. Ordering against the current frame's draws is
|
||||||
vmaDestroyBuffer(m_allocator, stagingBuffer, stagingAllocation);
|
// already guaranteed (its command buffer is submitted later, at
|
||||||
|
// present), so only the transient objects need to survive execution;
|
||||||
|
// park them until the fence signals.
|
||||||
|
m_pendingUploadReclaims.push_back({uploadFence, commandBuffer, stagingBuffer, stagingAllocation});
|
||||||
|
ReclaimCompletedUploads();
|
||||||
|
// Backstop for pathological upload storms: bound in-flight staging
|
||||||
|
// memory by blocking on the oldest upload only once the list is deep.
|
||||||
|
constexpr SizeT kMaxPendingTextureUploads = 16;
|
||||||
|
if (m_pendingUploadReclaims.size() > kMaxPendingTextureUploads) {
|
||||||
|
VK_VERIFY(vkWaitForFences(m_device, 1, &m_pendingUploadReclaims.front().fence, VK_TRUE, UINT64_MAX),
|
||||||
|
"vkWaitForFences(texture upload backstop)");
|
||||||
|
ReclaimCompletedUploads();
|
||||||
|
}
|
||||||
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
MGLOG_D("%s: texture upload cmd failed", __func__);
|
MGLOG_D("%s: texture upload cmd failed", __func__);
|
||||||
|
|||||||
@@ -420,6 +420,11 @@ private:
|
|||||||
void DeferViewRelease(VkImageView view);
|
void DeferViewRelease(VkImageView view);
|
||||||
void CollectDeferredReleases(Uint32 frameIndex);
|
void CollectDeferredReleases(Uint32 frameIndex);
|
||||||
void DestroyDeferredReleases();
|
void DestroyDeferredReleases();
|
||||||
|
// Frees the fence/command buffer/staging buffer of every in-flight texture
|
||||||
|
// upload whose fence has signaled (submission order = completion order on
|
||||||
|
// the single queue, so the scan stops at the first still-pending entry).
|
||||||
|
// waitAll blocks on every entry - Shutdown's drain.
|
||||||
|
void ReclaimCompletedUploads(Bool waitAll = false);
|
||||||
static TextureIdentity MakeTextureIdentity(MG_State::GLState::ITextureObject* texture);
|
static TextureIdentity MakeTextureIdentity(MG_State::GLState::ITextureObject* texture);
|
||||||
void EraseTrackedTexture(const TextureIdentity& identity);
|
void EraseTrackedTexture(const TextureIdentity& identity);
|
||||||
void PruneStaleTextureAliases(MG_State::GLState::ITextureObject* texture);
|
void PruneStaleTextureAliases(MG_State::GLState::ITextureObject* texture);
|
||||||
@@ -458,5 +463,16 @@ private:
|
|||||||
std::unordered_set<TextureIdentity, TextureIdentityHash> m_storageImageTextures;
|
std::unordered_set<TextureIdentity, TextureIdentityHash> m_storageImageTextures;
|
||||||
Vector<Vector<TextureResource>> m_deferredReleases;
|
Vector<Vector<TextureResource>> m_deferredReleases;
|
||||||
Vector<Vector<VkImageView>> m_deferredViewReleases;
|
Vector<Vector<VkImageView>> m_deferredViewReleases;
|
||||||
|
// Texture uploads are submitted out-of-band but NOT waited on (waiting
|
||||||
|
// behind the queue serialized the CPU against the previous frame's GPU
|
||||||
|
// work every time an animated atlas re-uploaded). Their transient objects
|
||||||
|
// are parked here and reclaimed once the upload fence signals.
|
||||||
|
struct PendingUploadReclaim {
|
||||||
|
VkFence fence = VK_NULL_HANDLE;
|
||||||
|
VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
|
||||||
|
VkBuffer stagingBuffer = VK_NULL_HANDLE;
|
||||||
|
VmaAllocation stagingAllocation = nullptr;
|
||||||
|
};
|
||||||
|
Vector<PendingUploadReclaim> m_pendingUploadReclaims;
|
||||||
};
|
};
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
Reference in New Issue
Block a user