Skip to content

Commit ca6f5e4

Browse files
Thanks to Queues tracking resources in use, the Swapchain code is now far less dumb and free of circular references
1 parent 1708061 commit ca6f5e4

File tree

5 files changed

+38
-68
lines changed

5 files changed

+38
-68
lines changed

examples_tests

include/nbl/video/ISwapchain.h

Lines changed: 29 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,6 @@ class ISwapchain : public IBackendObject
320320
//
321321
inline uint64_t getAcquireCount() const {return m_acquireCounter;}
322322

323-
using void_refctd_ptr = core::smart_refctd_ptr<core::IReferenceCounted>;
324323
// acquire
325324
struct SAcquireInfo
326325
{
@@ -339,7 +338,7 @@ class ISwapchain : public IBackendObject
339338
_ERROR // GDI macros getting in the way of just ERROR
340339
};
341340
// Even though in Vulkan image acquisition is not a queue operation, we perform a micro-submit to adapt a Timeline Semaphore to work with it
342-
// If we return anything other than `SUCCESS` or `SUBOPTIMAL`, then `info.signalSemaphores` won't get signalled to the requested values!
341+
// If we return anything other than `SUCCESS` or `SUBOPTIMAL`, then `info.signalSemaphores` won't get signalled to the requested values or kept alive!
343342
inline ACQUIRE_IMAGE_RESULT acquireNextImage(SAcquireInfo info, uint32_t* const out_imgIx)
344343
{
345344
// Signalling a timeline semaphore on acquire isn't optional, how else will you protect against using an image before its ready!?
@@ -364,10 +363,12 @@ class ISwapchain : public IBackendObject
364363
// Now hold onto the signal-on-acquire semaphores for this image index till the acquire actually takes place
365364
{
366365
const auto& lastSignal = info.signalSemaphores.back();
367-
m_frameResources[*out_imgIx]->latch({.semaphore=lastSignal.semaphore,.value=lastSignal.value},DeferredFrameResourceDrop(info.signalSemaphores));
366+
m_frameResources[*out_imgIx]->latch({.semaphore=lastSignal.semaphore,.value=lastSignal.value},DeferredFrameSemaphoreDrop(info.signalSemaphores));
368367
}
369368
if (m_oldSwapchain && m_acquireCounter>core::max(m_oldSwapchain->getImageCount(),m_imageCount) && !m_oldSwapchain->acquiredImagesAwaitingPresent())
370369
m_oldSwapchain = nullptr;
370+
// kill a few frame resources
371+
m_frameResources[*out_imgIx]->poll(DeferredFrameSemaphoreDrop::single_poll);
371372
break;
372373
default:
373374
break;
@@ -390,8 +391,8 @@ class ISwapchain : public IBackendObject
390391
OUT_OF_DATE,
391392
_ERROR
392393
};
393-
// If `FATAL_ERROR` returned then the `frameResources` are not latched until the next acquire of the same image index or swapchain destruction (whichever comes first)
394-
inline PRESENT_RESULT present(SPresentInfo info, void_refctd_ptr&& _frameResources=nullptr)
394+
// If `FATAL_ERROR` returned then the `waitSemaphores` are kept alive by the swapchain until the next acquire of the same image index or swapchain destruction (whichever comes first)
395+
inline PRESENT_RESULT present(SPresentInfo info)
395396
{
396397
if (!info.queue || info.imgIndex>=m_imageCount)
397398
return PRESENT_RESULT::FATAL_ERROR;
@@ -406,16 +407,13 @@ class ISwapchain : public IBackendObject
406407
if (threadsafeQ)
407408
threadsafeQ->m.unlock();
408409

409-
// If not FATAL_ERROR then semaphore wait will actually occur in the future and resources should be released, either:
410-
// - on the next SIGNALLED acquire of the same index
411-
// - when dropping the swapchain entirely, but need to wait on all previous presents to finish (some manageable UB)
410+
// kill a few frame resources
411+
m_frameResources[info.imgIndex]->poll(DeferredFrameSemaphoreDrop::single_poll);
412412
if (retval!=PRESENT_RESULT::FATAL_ERROR)
413413
{
414414
auto& lastWait = info.waitSemaphores.back();
415-
m_frameResources[info.imgIndex]->latch({.semaphore=lastWait.semaphore,.value=lastWait.value},DeferredFrameResourceDrop(info.waitSemaphores,std::move(_frameResources)));
415+
m_frameResources[info.imgIndex]->latch({.semaphore=lastWait.semaphore,.value=lastWait.value},DeferredFrameSemaphoreDrop(info.waitSemaphores));
416416
}
417-
// kill a few frame resources
418-
m_frameResources[info.imgIndex]->poll();
419417
return retval;
420418
}
421419

@@ -467,30 +465,34 @@ class ISwapchain : public IBackendObject
467465
virtual const void* getNativeHandle() const = 0;
468466

469467
// only public because MultiTimelineEventHandlerST needs to know about it
470-
class DeferredFrameResourceDrop final
468+
class DeferredFrameSemaphoreDrop final
471469
{
472-
core::smart_refctd_dynamic_array<void_refctd_ptr> m_frameResources;
470+
using sema_refctd_ptr = core::smart_refctd_ptr<ISemaphore>;
471+
core::smart_refctd_dynamic_array<sema_refctd_ptr> m_otherSemaphores = nullptr;
473472

474473
public:
475-
inline DeferredFrameResourceDrop(const std::span<const IQueue::SSubmitInfo::SSemaphoreInfo> _semaphores, void_refctd_ptr&& _frameResources=nullptr)
474+
inline DeferredFrameSemaphoreDrop(const std::span<const IQueue::SSubmitInfo::SSemaphoreInfo> _semaphores)
476475
{
477-
m_frameResources = core::make_refctd_dynamic_array<decltype(m_frameResources)>(_semaphores.size()+(_frameResources ? 1:0));
478-
for (auto i=0ull; i< _semaphores.size(); i++)
479-
m_frameResources->operator[](i) = void_refctd_ptr(_semaphores[i].semaphore);
480-
if (_frameResources)
481-
m_frameResources->back() = std::move(_frameResources);
476+
const auto otherCount = _semaphores.size()-1;
477+
// first semaphore always serves as the timeline and will be refcounted in the event handler
478+
if (otherCount==0)
479+
return;
480+
m_otherSemaphores = core::make_refctd_dynamic_array<decltype(m_otherSemaphores)>(otherCount);
481+
482+
for (auto i=0ull; i<otherCount; i++)
483+
m_otherSemaphores->operator[](i) = sema_refctd_ptr(_semaphores[i].semaphore);
482484
}
483-
DeferredFrameResourceDrop(const DeferredFrameResourceDrop& other) = delete;
484-
inline DeferredFrameResourceDrop(DeferredFrameResourceDrop&& other) : m_frameResources(nullptr)
485+
DeferredFrameSemaphoreDrop(const DeferredFrameSemaphoreDrop& other) = delete;
486+
inline DeferredFrameSemaphoreDrop(DeferredFrameSemaphoreDrop&& other) : m_otherSemaphores(nullptr)
485487
{
486488
this->operator=(std::move(other));
487489
}
488490

489-
DeferredFrameResourceDrop& operator=(const DeferredFrameResourceDrop& other) = delete;
490-
inline DeferredFrameResourceDrop& operator=(DeferredFrameResourceDrop&& other)
491+
DeferredFrameSemaphoreDrop& operator=(const DeferredFrameSemaphoreDrop& other) = delete;
492+
inline DeferredFrameSemaphoreDrop& operator=(DeferredFrameSemaphoreDrop&& other)
491493
{
492-
m_frameResources = std::move(other.m_frameResources);
493-
other.m_frameResources = nullptr;
494+
m_otherSemaphores = std::move(other.m_otherSemaphores);
495+
other.m_otherSemaphores = nullptr;
494496
return *this;
495497
}
496498

@@ -502,17 +504,9 @@ class ISwapchain : public IBackendObject
502504
return true;
503505
}
504506

505-
struct exhaustive_poll_t {};
506-
static inline exhaustive_poll_t exhaustive_poll;
507-
inline bool operator()(exhaustive_poll_t _exhaustive_poll)
508-
{
509-
operator()();
510-
return false;
511-
}
512-
513507
inline void operator()()
514508
{
515-
m_frameResources = nullptr;
509+
m_otherSemaphores = nullptr;
516510
}
517511
};
518512

@@ -559,7 +553,7 @@ class ISwapchain : public IBackendObject
559553
const uint8_t m_imageCount;
560554
uint64_t m_acquireCounter = 0;
561555
// Resources to hold onto until a frame is done rendering (between; just before presenting, and next acquire of the same image index)
562-
std::array<std::unique_ptr<MultiTimelineEventHandlerST<DeferredFrameResourceDrop>>,MaxImages> m_frameResources;
556+
std::array<std::unique_ptr<MultiTimelineEventHandlerST<DeferredFrameSemaphoreDrop>>,MaxImages> m_frameResources;
563557
};
564558

565559
}

include/nbl/video/utilities/CResizableSurface.h

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,6 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
121121
struct SPresentInfo : SCachedPresentInfo
122122
{
123123
uint32_t mostRecentFamilyOwningSource; // TODO: change to uint8_t
124-
core::IReferenceCounted* frameResources;
125124
};
126125
// This is a present that you should regularly use from the main rendering thread or something.
127126
// Due to the constraints and mutexes on everything, its impossible to split this into a separate acquire and present call so this does both.
@@ -164,7 +163,7 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
164163

165164
// The triple present enqueue operations are fast enough to be done under a mutex, this is safer on some platforms. You need to "race to present" to avoid a flicker.
166165
// Queue family ownership acquire not needed, done by the the very first present when `m_lastPresentSource` wasset.
167-
return present_impl({{m_lastPresent},getAssignedQueue()->getFamilyIndex(),nullptr},false);
166+
return present_impl({{m_lastPresent},getAssignedQueue()->getFamilyIndex()},false);
168167
}
169168

170169
protected:
@@ -390,13 +389,10 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
390389
const auto oldVal = m_lastPresent.pPresentSemaphoreWaitValue->exchange(acquireCount);
391390
assert(oldVal<acquireCount);
392391

393-
auto frameResources = core::make_refctd_dynamic_array<core::smart_refctd_dynamic_array<ISwapchain::void_refctd_ptr>>(2);
394-
frameResources->front() = core::smart_refctd_ptr<core::IReferenceCounted>(presentInfo.frameResources);
395-
frameResources->back() = core::smart_refctd_ptr<IGPUCommandBuffer>(cmdbuf);
396-
return ISimpleManagedSurface::present(imageIx,presented,frameResources.get());
392+
return ISimpleManagedSurface::present(imageIx,presented);
397393
}
398394
else
399-
return ISimpleManagedSurface::present(imageIx,{waitSemaphores+1,1},presentInfo.frameResources);
395+
return ISimpleManagedSurface::present(imageIx,{waitSemaphores+1,1});
400396
}
401397

402398
// Assume it will execute under a mutex

include/nbl/video/utilities/ISimpleManagedSurface.h

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -125,25 +125,14 @@ class NBL_API2 ISimpleManagedSurface : public core::IReferenceCounted
125125
}
126126

127127
protected:
128-
virtual ~ISwapchainResources()
129-
{
130-
// just to get rid of circular refs
131-
if (swapchain)
132-
while (swapchain->acquiredImagesAwaitingPresent()) {}
133-
}
128+
virtual ~ISwapchainResources() = default;
134129

135130
//
136131
inline void becomeIrrecoverable()
137132
{
138133
// Want to nullify things in an order that leads to fastest drops (if possible) and shallowest callstacks when refcounting
139134
invalidate();
140135

141-
// We need to call this method manually to make sure resources latched on swapchain images are dropped and cycles broken, otherwise its
142-
// EXTERMELY LIKELY (if you don't reset CommandBuffers) that you'll end up with a circular reference, for example:
143-
// `CommandBuffer -> SC Image[i] -> Swapchain -> FrameResource[i] -> CommandBuffer`
144-
// and a memory leak of: Swapchain and its Images, CommandBuffer and its pool CommandPool, and any resource used by the CommandBuffer.
145-
if (swapchain)
146-
while (swapchain->acquiredImagesAwaitingPresent()) {}
147136
swapchain = nullptr;
148137
}
149138

@@ -283,18 +272,18 @@ class NBL_API2 ISimpleManagedSurface : public core::IReferenceCounted
283272
virtual uint8_t handleOutOfDate() = 0;
284273

285274
// Frame Resources are not optional, shouldn't be null!
286-
inline bool present(const uint8_t imageIndex, const std::span<const IQueue::SSubmitInfo::SSemaphoreInfo> waitSemaphores, core::IReferenceCounted* frameResources)
275+
inline bool present(const uint8_t imageIndex, const std::span<const IQueue::SSubmitInfo::SSemaphoreInfo> waitSemaphores)
287276
{
288277
auto swapchainResources = getSwapchainResources();
289-
if (!swapchainResources || swapchainResources->getStatus()!=ISwapchainResources::STATUS::USABLE || !frameResources)
278+
if (!swapchainResources || swapchainResources->getStatus()!=ISwapchainResources::STATUS::USABLE)
290279
return false;
291280

292281
const ISwapchain::SPresentInfo info = {
293282
.queue = m_queue,
294283
.imgIndex = imageIndex,
295284
.waitSemaphores = waitSemaphores
296285
};
297-
switch (swapchainResources->swapchain->present(info,core::smart_refctd_ptr<core::IReferenceCounted>(frameResources)))
286+
switch (swapchainResources->swapchain->present(info))
298287
{
299288
case ISwapchain::PRESENT_RESULT::SUBOPTIMAL: [[fallthrough]];
300289
case ISwapchain::PRESENT_RESULT::SUCCESS:
@@ -303,15 +292,6 @@ class NBL_API2 ISimpleManagedSurface : public core::IReferenceCounted
303292
swapchainResources->invalidate();
304293
break;
305294
default:
306-
// because we won't hold onto the `frameResources` we need to block for `waitSemaphores`
307-
if (frameResources)
308-
{
309-
core::vector<ISemaphore::SWaitInfo> waitInfos(waitSemaphores.size());
310-
auto outWait = waitInfos.data();
311-
for (auto wait : waitSemaphores)
312-
*(outWait++) = {.semaphore=wait.semaphore,.value=wait.value};
313-
const_cast<ILogicalDevice*>(m_queue->getOriginDevice())->blockForSemaphores(waitInfos);
314-
}
315295
becomeIrrecoverable();
316296
break;
317297
}

src/nbl/video/ISwapchain.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ ISwapchain::ISwapchain(core::smart_refctd_ptr<const ILogicalDevice>&& dev, SCrea
2828
m_params.queueFamilyIndices = {m_queueFamilies.data(),m_params.queueFamilyIndices.size()};
2929

3030
for (auto i=0; i<m_imageCount; i++)
31-
m_frameResources[i] = std::make_unique<MultiTimelineEventHandlerST<DeferredFrameResourceDrop>>(const_cast<ILogicalDevice*>(getOriginDevice()));
31+
m_frameResources[i] = std::make_unique<MultiTimelineEventHandlerST<DeferredFrameSemaphoreDrop>>(const_cast<ILogicalDevice*>(getOriginDevice()));
3232
}
3333

3434
}

0 commit comments

Comments
 (0)