mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-09 20:58:31 +09:00
[Fix] (DirectVulkan): harden the leak-fix round after adversarial review - pipeline memo now drops at every command-buffer boundary (a flush-loop-memoized pipeline could age out and be destroyed while its submission was in flight), mid-frame drains no longer rewind the arena or advance the cache-aging clocks in presenting apps (gated to every 8th drain since the last Present, so readback/fence-heavy frames neither churn conversions nor shrink the 1024-boundary retire window), render-pass eviction notifies the pipeline cache once per sweep batch instead of once per dying pass, descriptor pools use FREE_DESCRIPTOR_SET_BIT so a destroyed layout's cached sets are freed back and credited instead of abandoning pool slots (the live-layout age sweep that could orphan slots is removed - layout destruction is the sole purge path), and renderbuffer respecify parks the old backing for aged destruction instead of destroying it while possibly in flight
This commit is contained in:
@@ -7228,11 +7228,10 @@ void main() {
|
||||
}
|
||||
|
||||
// Every submission is complete and nothing recorded references the
|
||||
// per-frame transients, so the drains Present's tail performs are safe
|
||||
// here too. Raise the buffer manager's completed floor first: the
|
||||
// serial inference (frameSerial - frameCount) is only justified by
|
||||
// Present's slot-fence cadence, and the extra BeginFrame below would
|
||||
// otherwise inflate it past reality.
|
||||
// per-frame transients. Pure-reclaim work runs on every drain: it only
|
||||
// releases memory that is provably dead, never invalidates anything a
|
||||
// later draw would have to rebuild. Raise the buffer manager's
|
||||
// completed floor first so busy-tracking reflects the proven idleness.
|
||||
m_bufferManager.NotifyDeviceIdle();
|
||||
|
||||
const Uint32 frameIndex = m_frameContext.GetCurrentFrameIndex();
|
||||
@@ -7242,23 +7241,38 @@ void main() {
|
||||
}
|
||||
if (m_textureManager) {
|
||||
m_textureManager->CollectAllDeferredReleases();
|
||||
m_textureManager->BeginFrame(frameIndex);
|
||||
}
|
||||
m_bufferManager.CollectAllDeferredReleases();
|
||||
// Descriptor cursors rewind on every drain (the pre-drain readback path
|
||||
// already did exactly this), keeping fence/readback loops' set usage bounded.
|
||||
if (m_uniformManager) {
|
||||
m_uniformManager->BeginFrame(frameIndex);
|
||||
}
|
||||
|
||||
// Frame-boundary-equivalent work - transient arena rewind (which invalidates
|
||||
// the conversion cache) and the cache-aging clocks - is gated to every 8th
|
||||
// drain since the last Present: a presenting app's mid-frame readbacks/waits
|
||||
// must neither force re-conversion/re-upload churn for the rest of the frame
|
||||
// nor multiply the aging rate (which would shrink the 1024-boundary retire
|
||||
// window and thrash periodically-used pipelines/programs), while present-less
|
||||
// loops still rewind the arena and age their caches every 8 iterations -
|
||||
// bounded by 8 iterations' transient usage.
|
||||
++m_drainsSinceLastPresent;
|
||||
if ((m_drainsSinceLastPresent % 8) != 0) {
|
||||
return true;
|
||||
}
|
||||
if (m_textureManager) {
|
||||
m_textureManager->BeginFrame(frameIndex);
|
||||
}
|
||||
m_bufferManager.BeginFrame(frameIndex);
|
||||
// The cached conversion slices point into the transient arena the
|
||||
// BeginFrame above just rewound; drop them together.
|
||||
m_convertedVertexStreams.clear();
|
||||
if (m_uniformManager) {
|
||||
m_uniformManager->BeginFrame(frameIndex);
|
||||
}
|
||||
if (m_renderPassManager) {
|
||||
m_renderPassManager->OnPresent();
|
||||
}
|
||||
// Present-less loops cross frame boundaries here, so the content-addressed
|
||||
// caches age on the same cadence as Present's tail. The pipeline memo can
|
||||
// survive across these boundaries (no per-frame reset on this path), so it
|
||||
// must drop whenever the sweep destroys anything.
|
||||
// The pipeline memo can survive across these boundaries (no per-frame reset
|
||||
// on this path), so it must drop whenever the sweep destroys anything.
|
||||
if (m_programFactory) {
|
||||
m_programFactory->OnFrameBoundary();
|
||||
}
|
||||
@@ -7266,9 +7280,6 @@ void main() {
|
||||
m_lastPipelineValid = false;
|
||||
m_lastPipelineResult = VK_NULL_HANDLE;
|
||||
}
|
||||
if (m_uniformManager) {
|
||||
m_uniformManager->OnFrameBoundary();
|
||||
}
|
||||
if (m_vertexInputStateFactory) {
|
||||
m_vertexInputStateFactory->OnFrameBoundary();
|
||||
}
|
||||
@@ -7366,6 +7377,14 @@ void main() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Command-buffer boundary: the pipeline memo must not survive it, or a
|
||||
// pipeline bound only through memo hits is never re-stamped in the factory
|
||||
// cache and the aging sweep could destroy it while the flushed submission
|
||||
// still references it. Mirrors the drops at the readback and Present
|
||||
// boundaries; costs one full pipeline lookup on the next draw.
|
||||
m_lastPipelineValid = false;
|
||||
m_lastPipelineResult = VK_NULL_HANDLE;
|
||||
|
||||
// The submitted command buffer may still be executing; recording must
|
||||
// restart on a fresh one. If none can be allocated, fall back to
|
||||
// draining this submission so reusing the buffer stays legal.
|
||||
@@ -7530,16 +7549,20 @@ void main() {
|
||||
MOBILEGL_ASSERT(m_imageIndexAcquired < m_swapchainObject.GetImageCount(),
|
||||
"Present, acquired image index out of range");
|
||||
m_renderPassManager->OnPresent();
|
||||
// A real presented frame is the canonical aging cadence; mid-frame drains
|
||||
// count against this and only age when presents stop coming.
|
||||
m_drainsSinceLastPresent = 0;
|
||||
// Age the content-addressed caches on the same frame-boundary cadence. Each
|
||||
// keeps its own internal 256-sweep gate, so the per-frame cost is one counter
|
||||
// increment and compare per cache; entries used by this frame's still-
|
||||
// unsubmitted recording were stamped this boundary and can never age out.
|
||||
// unsubmitted recording were stamped this boundary (every command-buffer
|
||||
// boundary drops the pipeline memo, so the first draw of each recording
|
||||
// performs a real, stamping lookup) and can never age out.
|
||||
m_programFactory->OnFrameBoundary();
|
||||
if (m_pipelineFactory->OnFrameBoundary() > 0) {
|
||||
m_lastPipelineValid = false; // an aged-out pipeline may still be memoized
|
||||
m_lastPipelineResult = VK_NULL_HANDLE;
|
||||
}
|
||||
m_uniformManager->OnFrameBoundary();
|
||||
m_vertexInputStateFactory->OnFrameBoundary();
|
||||
m_samplerManager->OnFrameBoundary();
|
||||
auto& frame = m_frameContext.GetCurrent();
|
||||
@@ -8574,15 +8597,15 @@ void main() {
|
||||
m_computePipelines.clear();
|
||||
}
|
||||
|
||||
void VulkanRenderer::OnRenderPassDestroyed(VkRenderPass renderPass) {
|
||||
void VulkanRenderer::OnRenderPassesDestroyed(const Vector<VkRenderPass>& renderPasses) {
|
||||
if (m_pipelineFactory == nullptr) {
|
||||
return;
|
||||
}
|
||||
// The render-pass sweep's >1024-boundary idle guarantee covers these pipelines
|
||||
// too (they are only bound by draws that hit the dying entry), so the factory
|
||||
// too (they are only bound by draws that hit the dying entries), so the factory
|
||||
// destroys them immediately. The memo must drop as well: it can hand out a
|
||||
// cached handle without touching the factory.
|
||||
if (m_pipelineFactory->EvictByRenderPass(renderPass) > 0) {
|
||||
if (m_pipelineFactory->EvictByRenderPasses(renderPasses) > 0) {
|
||||
m_lastPipelineValid = false;
|
||||
m_lastPipelineResult = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user