@@ -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