@@ -88,29 +88,53 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
88
88
return {0 ,0 };
89
89
}
90
90
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
+
91
106
struct SPresentInfo
92
107
{
93
108
inline operator bool () const {return source.image ;}
94
109
95
110
SPresentSource source;
96
- uint8_t mostRecentFamilyOwningSource;
97
111
// only allow waiting for one semaphore, because there's only one source to present!
98
112
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
99
116
core::IReferenceCounted* frameResources;
100
117
};
101
118
// This is a present that you should regularly use from the main rendering thread or something.
102
119
// 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)
105
122
{
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.
109
133
return present_impl (presentInfo,getAssignedQueue ()->getFamilyIndex ()!=presentInfo.mostRecentFamilyOwningSource );
110
134
}
111
135
112
136
// 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)
114
138
{
115
139
// recreate the swapchain under a mutex
116
140
std::unique_lock guard (m_swapchainResourcesMutex);
@@ -130,9 +154,15 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
130
154
if (current->getSwapchain ()==oldSwapchain.get ())
131
155
return true ;
132
156
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.
134
158
// 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 );
136
166
}
137
167
138
168
protected:
@@ -154,17 +184,18 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
154
184
m_lastPresentSemaphore = nullptr ;
155
185
m_lastPresentSourceImage = nullptr ;
156
186
157
- if (m_blitSemaphore )
187
+ if (m_presentSemaphore )
158
188
{
159
- auto device = const_cast <ILogicalDevice*>(m_blitSemaphore ->getOriginDevice ());
189
+ auto device = const_cast <ILogicalDevice*>(m_presentSemaphore ->getOriginDevice ());
160
190
const ISemaphore::SWaitInfo info[1 ] = {{
161
- .semaphore = m_blitSemaphore .get (), .value = getAcquireCount ()
191
+ .semaphore = m_presentSemaphore .get (),.value = getAcquireCount ()
162
192
}};
163
193
device->blockForSemaphores (info);
164
194
}
165
195
166
196
std::fill (m_cmdbufs.begin (),m_cmdbufs.end (),nullptr );
167
- m_blitSemaphore = nullptr ;
197
+ m_pLastPresentSignalValue = nullptr ;
198
+ m_presentSemaphore = nullptr ;
168
199
}
169
200
170
201
//
@@ -180,14 +211,14 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
180
211
return false ;
181
212
182
213
// want to keep using the same semaphore throughout the lifetime to not run into sync issues
183
- if (!m_blitSemaphore )
214
+ if (!m_presentSemaphore )
184
215
{
185
- m_blitSemaphore = device->createSemaphore (0u );
186
- if (!m_blitSemaphore )
216
+ m_presentSemaphore = device->createSemaphore (0u );
217
+ if (!m_presentSemaphore )
187
218
return false ;
188
219
}
189
220
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
191
222
auto pool = device->createCommandPool (queue->getFamilyIndex (),IGPUCommandPool::CREATE_FLAGS::RESET_COMMAND_BUFFER_BIT);
192
223
if (!pool || !pool->createCommandBuffers (IGPUCommandPool::BUFFER_LEVEL::PRIMARY,{m_cmdbufs.data (),getMaxFramesInFlight ()}))
193
224
return false ;
@@ -292,6 +323,7 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
292
323
};
293
324
m_lastPresentSourceImage = core::smart_refctd_ptr<IGPUImage>(presentInfo.source .image );
294
325
m_lastPresentSemaphore = core::smart_refctd_ptr<ISemaphore>(presentInfo.wait .semaphore );
326
+ m_pLastPresentSignalValue = presentInfo.pPresentSemaphoreWaitValue ;
295
327
m_lastPresentSource = presentInfo.source ;
296
328
m_lastPresentWait = presentInfo.wait ;
297
329
@@ -305,7 +337,7 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
305
337
{
306
338
const ISemaphore::SWaitInfo cmdbufDonePending[1 ] = {
307
339
{
308
- .semaphore = m_blitSemaphore .get (),
340
+ .semaphore = m_presentSemaphore .get (),
309
341
.value = acquireCount-maxFramesInFlight
310
342
}
311
343
};
@@ -318,33 +350,37 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
318
350
auto cmdbuf = m_cmdbufs[cmdBufIx].get ();
319
351
320
352
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 ] = {
323
355
{
324
- .semaphore = m_blitSemaphore .get (),
356
+ .semaphore = m_presentSemaphore .get (),
325
357
.value = acquireCount,
326
358
// 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)
328
360
}
329
361
};
330
- willBlit &= bool (blitted [1 ].stageMask .value );
362
+ willBlit &= bool (presented [1 ].stageMask .value );
331
363
willBlit &= cmdbuf->end ();
332
364
333
365
const IQueue::SSubmitInfo::SCommandBufferInfo commandBuffers[1 ] = {{.cmdbuf =cmdbuf}};
334
366
const IQueue::SSubmitInfo submitInfos[1 ] = {{
335
367
.waitSemaphores = waitSemaphores,
336
368
.commandBuffers = commandBuffers,
337
- .signalSemaphores = blitted
369
+ .signalSemaphores = presented
338
370
}};
339
371
willBlit &= queue->submit (submitInfos)==IQueue::RESULT::SUCCESS;
340
372
341
373
// handle two cases of present
342
374
if (willBlit)
343
375
{
376
+ // let the others know we've enqueued another TBP
377
+ const auto oldVal = m_pLastPresentSignalValue->exchange (acquireCount);
378
+ assert (oldVal<acquireCount);
379
+
344
380
auto frameResources = core::make_refctd_dynamic_array<core::smart_refctd_dynamic_array<ISwapchain::void_refctd_ptr>>(2 );
345
381
frameResources->front () = core::smart_refctd_ptr<core::IReferenceCounted>(presentInfo.frameResources );
346
382
frameResources->back () = core::smart_refctd_ptr<IGPUCommandBuffer>(cmdbuf);
347
- return ISimpleManagedSurface::present (imageIx,blitted ,frameResources.get ());
383
+ return ISimpleManagedSurface::present (imageIx,presented ,frameResources.get ());
348
384
}
349
385
else
350
386
return ISimpleManagedSurface::present (imageIx,{waitSemaphores+1 ,1 },presentInfo.frameResources );
@@ -358,8 +394,8 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
358
394
359
395
private:
360
396
// 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
363
399
std::array<core::smart_refctd_ptr<IGPUCommandBuffer>,ISwapchain::MaxImages> m_cmdbufs = {};
364
400
365
401
// used to protect access to swapchain resources during present and recreateExplicit
@@ -369,6 +405,7 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
369
405
// 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.
370
406
IQueue::SSubmitInfo::SSemaphoreInfo m_lastPresentWait = {};
371
407
SPresentSource m_lastPresentSource = {};
408
+ std::atomic_uint64_t * m_pLastPresentSignalValue = nullptr ;
372
409
core::smart_refctd_ptr<ISemaphore> m_lastPresentSemaphore = {};
373
410
core::smart_refctd_ptr<IGPUImage> m_lastPresentSourceImage = {};
374
411
};
0 commit comments