Skip to content

Commit 240372a

Browse files
All sync problems on Smooth Resize taken care of
1 parent e432997 commit 240372a

File tree

2 files changed

+65
-28
lines changed

2 files changed

+65
-28
lines changed

examples_tests

include/nbl/video/utilities/CResizableSurface.h

Lines changed: 64 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -88,29 +88,53 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
8888
return {0,0};
8989
}
9090

91+
//
92+
inline ISemaphore* getPresentSemaphore() {return m_presentSemaphore.get();}
93+
94+
// We need to prevent a spontaneous Triple Buffer Present during a resize between when:
95+
// - we check the semaphore value we need to wait on before rendering to the `SPresentSource::image`
96+
// - and the present from `SPresentSource::image
97+
// This is so that the value we would need to wait on, does not change.
98+
// Ofcourse if the target addresses of the atomic counters of wait values differ, no lock needed!
99+
inline std::unique_lock<std::mutex> pseudoAcquire(std::atomic_uint64_t* pPresentSemaphoreWaitValue)
100+
{
101+
if (pPresentSemaphoreWaitValue!=m_pLastPresentSignalValue)
102+
return {}; // no lock
103+
return std::unique_lock<std::mutex>(m_swapchainResourcesMutex);
104+
}
105+
91106
struct SPresentInfo
92107
{
93108
inline operator bool() const {return source.image;}
94109

95110
SPresentSource source;
96-
uint8_t mostRecentFamilyOwningSource;
97111
// only allow waiting for one semaphore, because there's only one source to present!
98112
IQueue::SSubmitInfo::SSemaphoreInfo wait;
113+
// what value will be signalled by the enqueued Triple Buffer Presents so far
114+
std::atomic_uint64_t* pPresentSemaphoreWaitValue;
115+
uint32_t mostRecentFamilyOwningSource; // TODO: change to uint8_t
99116
core::IReferenceCounted* frameResources;
100117
};
101118
// This is a present that you should regularly use from the main rendering thread or something.
102119
// Due to the constraints and mutexes on everything, its impossible to split this into a separate acquire and present call so this does both.
103-
// So DON'T USE `acquireNextImage` for frame pacing, it was bad Vulkan practice anyway!
104-
inline bool present(const SPresentInfo& presentInfo)
120+
// So CAN'T USE `acquireNextImage` for frame pacing, it was bad Vulkan practice anyway!
121+
inline bool present(std::unique_lock<std::mutex>&& acquireLock, const SPresentInfo& presentInfo)
105122
{
106-
std::unique_lock guard(m_swapchainResourcesMutex);
107-
// The only thing we want to do under the mutex, is just enqueue a blit and a present, its not a lot.
108-
// Only acquire ownership if the Blit&Present queue is different to the current one.
123+
std::unique_lock<std::mutex> guard;
124+
if (acquireLock)
125+
guard = std::move(acquireLock);
126+
else
127+
{
128+
guard = std::unique_lock<std::mutex>(m_swapchainResourcesMutex);
129+
assert(presentInfo.pPresentSemaphoreWaitValue!=m_pLastPresentSignalValue);
130+
}
131+
// The only thing we want to do under the mutex, is just enqueue a triple buffer present and a swapchain present, its not a lot.
132+
// Only acquire ownership if the Present queue is different to the current one.
109133
return present_impl(presentInfo,getAssignedQueue()->getFamilyIndex()!=presentInfo.mostRecentFamilyOwningSource);
110134
}
111135

112136
// Call this when you want to recreate the swapchain with new extents
113-
inline bool explicitRecreateSwapchain(const uint32_t w, const uint32_t h, CThreadSafeQueueAdapter* blitAndPresentQueue=nullptr)
137+
inline bool explicitRecreateSwapchain(const uint32_t w, const uint32_t h)
114138
{
115139
// recreate the swapchain under a mutex
116140
std::unique_lock guard(m_swapchainResourcesMutex);
@@ -130,9 +154,15 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
130154
if (current->getSwapchain()==oldSwapchain.get())
131155
return true;
132156

133-
// The blit 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.
157+
// 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.
134158
// Queue family ownership acquire not needed, done by the the very first present when `m_lastPresentSource` wasset.
135-
return present_impl({.source=m_lastPresentSource,.wait=m_lastPresentWait,.frameResources=nullptr},false);
159+
return present_impl({
160+
.source = m_lastPresentSource,
161+
.wait = m_lastPresentWait,
162+
.pPresentSemaphoreWaitValue = m_pLastPresentSignalValue,
163+
.mostRecentFamilyOwningSource = getAssignedQueue()->getFamilyIndex(),
164+
.frameResources=nullptr
165+
},false);
136166
}
137167

138168
protected:
@@ -154,17 +184,18 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
154184
m_lastPresentSemaphore = nullptr;
155185
m_lastPresentSourceImage = nullptr;
156186

157-
if (m_blitSemaphore)
187+
if (m_presentSemaphore)
158188
{
159-
auto device = const_cast<ILogicalDevice*>(m_blitSemaphore->getOriginDevice());
189+
auto device = const_cast<ILogicalDevice*>(m_presentSemaphore->getOriginDevice());
160190
const ISemaphore::SWaitInfo info[1] = {{
161-
.semaphore = m_blitSemaphore.get(), .value = getAcquireCount()
191+
.semaphore = m_presentSemaphore.get(),.value=getAcquireCount()
162192
}};
163193
device->blockForSemaphores(info);
164194
}
165195

166196
std::fill(m_cmdbufs.begin(),m_cmdbufs.end(),nullptr);
167-
m_blitSemaphore = nullptr;
197+
m_pLastPresentSignalValue = nullptr;
198+
m_presentSemaphore = nullptr;
168199
}
169200

170201
//
@@ -180,14 +211,14 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
180211
return false;
181212

182213
// want to keep using the same semaphore throughout the lifetime to not run into sync issues
183-
if (!m_blitSemaphore)
214+
if (!m_presentSemaphore)
184215
{
185-
m_blitSemaphore = device->createSemaphore(0u);
186-
if (!m_blitSemaphore)
216+
m_presentSemaphore = device->createSemaphore(0u);
217+
if (!m_presentSemaphore)
187218
return false;
188219
}
189220

190-
// transient commandbuffer and pool to perform the blits or copies to SC images
221+
// transient commandbuffer and pool to perform the blits or other copies to SC images
191222
auto pool = device->createCommandPool(queue->getFamilyIndex(),IGPUCommandPool::CREATE_FLAGS::RESET_COMMAND_BUFFER_BIT);
192223
if (!pool || !pool->createCommandBuffers(IGPUCommandPool::BUFFER_LEVEL::PRIMARY,{m_cmdbufs.data(),getMaxFramesInFlight()}))
193224
return false;
@@ -292,6 +323,7 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
292323
};
293324
m_lastPresentSourceImage = core::smart_refctd_ptr<IGPUImage>(presentInfo.source.image);
294325
m_lastPresentSemaphore = core::smart_refctd_ptr<ISemaphore>(presentInfo.wait.semaphore);
326+
m_pLastPresentSignalValue = presentInfo.pPresentSemaphoreWaitValue;
295327
m_lastPresentSource = presentInfo.source;
296328
m_lastPresentWait = presentInfo.wait;
297329

@@ -305,7 +337,7 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
305337
{
306338
const ISemaphore::SWaitInfo cmdbufDonePending[1] = {
307339
{
308-
.semaphore = m_blitSemaphore.get(),
340+
.semaphore = m_presentSemaphore.get(),
309341
.value = acquireCount-maxFramesInFlight
310342
}
311343
};
@@ -318,33 +350,37 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
318350
auto cmdbuf = m_cmdbufs[cmdBufIx].get();
319351

320352
willBlit &= cmdbuf->begin(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT);
321-
// now enqueue the mini-blit
322-
const IQueue::SSubmitInfo::SSemaphoreInfo blitted[1] = {
353+
// now enqueue the Triple Buffer Present
354+
const IQueue::SSubmitInfo::SSemaphoreInfo presented[1] = {
323355
{
324-
.semaphore = m_blitSemaphore.get(),
356+
.semaphore = m_presentSemaphore.get(),
325357
.value = acquireCount,
326358
// don't need to predicate with `willBlit` because if `willBlit==false` cmdbuf not properly begun and validation will fail
327-
.stageMask = swapchainResources->tripleBufferPresent(cmdbuf,presentInfo.source,imageIx,acquireOwnership ? queue->getFamilyIndex():IQueue::FamilyIgnored)
359+
.stageMask = swapchainResources->tripleBufferPresent(cmdbuf,m_lastPresentSource,imageIx,acquireOwnership ? queue->getFamilyIndex():IQueue::FamilyIgnored)
328360
}
329361
};
330-
willBlit &= bool(blitted[1].stageMask.value);
362+
willBlit &= bool(presented[1].stageMask.value);
331363
willBlit &= cmdbuf->end();
332364

333365
const IQueue::SSubmitInfo::SCommandBufferInfo commandBuffers[1] = {{.cmdbuf=cmdbuf}};
334366
const IQueue::SSubmitInfo submitInfos[1] = {{
335367
.waitSemaphores = waitSemaphores,
336368
.commandBuffers = commandBuffers,
337-
.signalSemaphores = blitted
369+
.signalSemaphores = presented
338370
}};
339371
willBlit &= queue->submit(submitInfos)==IQueue::RESULT::SUCCESS;
340372

341373
// handle two cases of present
342374
if (willBlit)
343375
{
376+
// let the others know we've enqueued another TBP
377+
const auto oldVal = m_pLastPresentSignalValue->exchange(acquireCount);
378+
assert(oldVal<acquireCount);
379+
344380
auto frameResources = core::make_refctd_dynamic_array<core::smart_refctd_dynamic_array<ISwapchain::void_refctd_ptr>>(2);
345381
frameResources->front() = core::smart_refctd_ptr<core::IReferenceCounted>(presentInfo.frameResources);
346382
frameResources->back() = core::smart_refctd_ptr<IGPUCommandBuffer>(cmdbuf);
347-
return ISimpleManagedSurface::present(imageIx,blitted,frameResources.get());
383+
return ISimpleManagedSurface::present(imageIx,presented,frameResources.get());
348384
}
349385
else
350386
return ISimpleManagedSurface::present(imageIx,{waitSemaphores+1,1},presentInfo.frameResources);
@@ -358,8 +394,8 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
358394

359395
private:
360396
// Have to use a second semaphore to make acquire-present pairs independent of each other, also because there can be no ordering ensured between present->acquire
361-
core::smart_refctd_ptr<ISemaphore> m_blitSemaphore;
362-
// Command Buffers for blitting/copying to
397+
core::smart_refctd_ptr<ISemaphore> m_presentSemaphore;
398+
// Command Buffers for blitting/copying the Triple Buffers to Swapchain Images
363399
std::array<core::smart_refctd_ptr<IGPUCommandBuffer>,ISwapchain::MaxImages> m_cmdbufs = {};
364400

365401
// used to protect access to swapchain resources during present and recreateExplicit
@@ -369,6 +405,7 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
369405
// Unless you like your frames to go backwards in time in a special "rewind glitch" you need to blit the frame that has not been presented yet or is the same as most recently enqueued.
370406
IQueue::SSubmitInfo::SSemaphoreInfo m_lastPresentWait = {};
371407
SPresentSource m_lastPresentSource = {};
408+
std::atomic_uint64_t* m_pLastPresentSignalValue = nullptr;
372409
core::smart_refctd_ptr<ISemaphore> m_lastPresentSemaphore = {};
373410
core::smart_refctd_ptr<IGPUImage> m_lastPresentSourceImage = {};
374411
};

0 commit comments

Comments
 (0)