@@ -45,12 +45,16 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
45
45
protected:
46
46
friend class IResizableSurface ;
47
47
48
- // Returns what stage the submit signal semaphore should signal from for the presentation to wait on.
48
+ // Returns what Pipeline Stages will be used for performing the `tripleBufferPresent`
49
+ // We use this to set the waitStage for the Semaphore Wait, but only when ownership does not need to be acquired.
50
+ virtual core::bitflag<asset::PIPELINE_STAGE_FLAGS> getTripleBufferPresentStages () const = 0;
51
+
52
+ // Returns what if the commands to present `source` into `dstIx` Swapchain Image were successfully recorded to `cmdbuf` and can be submitted
49
53
// The `cmdbuf` is already begun when given to the callback, and will be ended outside.
50
54
// User is responsible for transitioning the image layouts (most notably the swapchain), acquiring ownership etc.
51
55
// Performance Tip: DO NOT transition the layout of `source` inside this callback, have it already in the correct Layout you need!
52
56
// However, if `qFamToAcquireSrcFrom!=IQueue::FamilyIgnored`, you need to acquire the ownership of the `source.image`
53
- virtual asset::PIPELINE_STAGE_FLAGS tripleBufferPresent (IGPUCommandBuffer* cmdbuf, const SPresentSource& source, const uint8_t dstIx, const uint32_t qFamToAcquireSrcFrom) = 0;
57
+ virtual bool tripleBufferPresent (IGPUCommandBuffer* cmdbuf, const SPresentSource& source, const uint8_t dstIx, const uint32_t qFamToAcquireSrcFrom) = 0;
54
58
};
55
59
56
60
//
@@ -98,20 +102,24 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
98
102
// Ofcourse if the target addresses of the atomic counters of wait values differ, no lock needed!
99
103
inline std::unique_lock<std::mutex> pseudoAcquire (std::atomic_uint64_t * pPresentSemaphoreWaitValue)
100
104
{
101
- if (pPresentSemaphoreWaitValue!=m_pLastPresentSignalValue )
105
+ if (pPresentSemaphoreWaitValue!=m_lastPresent. pPresentSemaphoreWaitValue )
102
106
return {}; // no lock
103
107
return std::unique_lock<std::mutex>(m_swapchainResourcesMutex);
104
108
}
105
109
106
- struct SPresentInfo
110
+ struct SCachedPresentInfo
107
111
{
108
- inline operator bool () const {return source.image ;}
112
+ inline operator bool () const {return source.image && waitSemaphore && waitValue && pPresentSemaphoreWaitValue ;}
109
113
110
- SPresentSource source;
114
+ SPresentSource source = {} ;
111
115
// only allow waiting for one semaphore, because there's only one source to present!
112
- IQueue::SSubmitInfo::SSemaphoreInfo wait;
116
+ ISemaphore* waitSemaphore = nullptr ;
117
+ uint64_t waitValue = 0 ;
113
118
// what value will be signalled by the enqueued Triple Buffer Presents so far
114
- std::atomic_uint64_t * pPresentSemaphoreWaitValue;
119
+ std::atomic_uint64_t * pPresentSemaphoreWaitValue = nullptr ;
120
+ };
121
+ struct SPresentInfo : SCachedPresentInfo
122
+ {
115
123
uint32_t mostRecentFamilyOwningSource; // TODO: change to uint8_t
116
124
core::IReferenceCounted* frameResources;
117
125
};
@@ -126,7 +134,7 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
126
134
else
127
135
{
128
136
guard = std::unique_lock<std::mutex>(m_swapchainResourcesMutex);
129
- assert (presentInfo.pPresentSemaphoreWaitValue !=m_pLastPresentSignalValue );
137
+ assert (presentInfo.pPresentSemaphoreWaitValue !=m_lastPresent. pPresentSemaphoreWaitValue );
130
138
}
131
139
// 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
140
// Only acquire ownership if the Present queue is different to the current one.
@@ -156,13 +164,7 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
156
164
157
165
// 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.
158
166
// Queue family ownership acquire not needed, done by the the very first present when `m_lastPresentSource` wasset.
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 );
167
+ return present_impl ({{m_lastPresent},getAssignedQueue ()->getFamilyIndex (),nullptr },false );
166
168
}
167
169
168
170
protected:
@@ -179,10 +181,11 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
179
181
std::unique_lock guard (m_swapchainResourcesMutex);
180
182
static_cast <ICallback*>(m_cb)->setSwapchainRecreator (nullptr );
181
183
182
- m_lastPresentWait = {};
183
- m_lastPresentSource = {};
184
- m_lastPresentSemaphore = nullptr ;
184
+ m_lastPresent.source = {};
185
185
m_lastPresentSourceImage = nullptr ;
186
+ m_lastPresent.waitSemaphore = nullptr ;
187
+ m_lastPresent.waitValue = 0 ;
188
+ m_lastPresentSemaphore = nullptr ;
186
189
187
190
if (m_presentSemaphore)
188
191
{
@@ -194,7 +197,7 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
194
197
}
195
198
196
199
std::fill (m_cmdbufs.begin (),m_cmdbufs.end (),nullptr );
197
- m_pLastPresentSignalValue = nullptr ;
200
+ m_lastPresent. pPresentSemaphoreWaitValue = {} ;
198
201
m_presentSemaphore = nullptr ;
199
202
}
200
203
@@ -311,21 +314,31 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
311
314
return false ;
312
315
313
316
// now that an image is acquired, we HAVE TO present
317
+ m_lastPresentSourceImage = core::smart_refctd_ptr<IGPUImage>(presentInfo.source .image );
318
+ m_lastPresentSemaphore = core::smart_refctd_ptr<ISemaphore>(presentInfo.waitSemaphore );
319
+ m_lastPresent = presentInfo;
320
+ // in case of failure, most we can do is just not submit an invalidated commandbuffer with the triple buffer present
314
321
bool willBlit = true ;
322
+
315
323
const auto acquireCount = getAcquireCount ();
316
324
const IQueue::SSubmitInfo::SSemaphoreInfo waitSemaphores[2 ] = {
317
- presentInfo.wait ,
325
+ {
326
+ .semaphore = presentInfo.waitSemaphore ,
327
+ .value = presentInfo.waitValue ,
328
+ // If we need to Acquire Ownership of the Triple Buffer Source we need a Happens-Before between the Semaphore wait and the Acquire Ownership
329
+ // https://github.com/KhronosGroup/Vulkan-Docs/issues/2319
330
+ // else we need to know what stage starts reading from the Triple Buffer Source
331
+ .stageMask = acquireOwnership ? asset::PIPELINE_STAGE_FLAGS::ALL_COMMANDS_BITS:swapchainResources->getTripleBufferPresentStages ()
332
+ },
318
333
{
319
334
.semaphore = getAcquireSemaphore (),
320
335
.value = acquireCount,
321
- .stageMask = asset::PIPELINE_STAGE_FLAGS::NONE // presentation engine usage isn't a stage
336
+ // There 99% surely probably be a Layout Transition of the acquired image away from PRESENT_SRC,
337
+ // so need an ALL->NONE dependency between acquire and semaphore wait
338
+ // https://github.com/KhronosGroup/Vulkan-Docs/issues/2319
339
+ .stageMask = asset::PIPELINE_STAGE_FLAGS::ALL_COMMANDS_BITS
322
340
}
323
341
};
324
- m_lastPresentSourceImage = core::smart_refctd_ptr<IGPUImage>(presentInfo.source .image );
325
- m_lastPresentSemaphore = core::smart_refctd_ptr<ISemaphore>(presentInfo.wait .semaphore );
326
- m_pLastPresentSignalValue = presentInfo.pPresentSemaphoreWaitValue ;
327
- m_lastPresentSource = presentInfo.source ;
328
- m_lastPresentWait = presentInfo.wait ;
329
342
330
343
//
331
344
auto queue = getAssignedQueue ();
@@ -350,19 +363,19 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
350
363
auto cmdbuf = m_cmdbufs[cmdBufIx].get ();
351
364
352
365
willBlit &= cmdbuf->begin (IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT);
353
- // now enqueue the Triple Buffer Present
354
- const IQueue::SSubmitInfo::SSemaphoreInfo presented[1 ] = {
355
- {
356
- .semaphore = m_presentSemaphore.get (),
357
- .value = acquireCount,
358
- // don't need to predicate with `willBlit` because if `willBlit==false` cmdbuf not properly begun and validation will fail
359
- .stageMask = swapchainResources->tripleBufferPresent (cmdbuf,m_lastPresentSource,imageIx,acquireOwnership ? queue->getFamilyIndex ():IQueue::FamilyIgnored)
360
- }
361
- };
362
- willBlit &= bool (presented[1 ].stageMask .value );
366
+ willBlit &= swapchainResources->tripleBufferPresent (cmdbuf,presentInfo.source ,imageIx,acquireOwnership ? queue->getFamilyIndex ():IQueue::FamilyIgnored);
363
367
willBlit &= cmdbuf->end ();
364
368
365
369
const IQueue::SSubmitInfo::SCommandBufferInfo commandBuffers[1 ] = {{.cmdbuf =cmdbuf}};
370
+ // now enqueue the Triple Buffer Present
371
+ const IQueue::SSubmitInfo::SSemaphoreInfo presented[1 ] = {{
372
+ .semaphore = m_presentSemaphore.get (),
373
+ .value = acquireCount,
374
+ // There 99% surely probably be a Layout Transition of the acquired image to PRESENT_SRC,
375
+ // so need a NONE->ALL dependency between that and semaphore signal
376
+ // https://github.com/KhronosGroup/Vulkan-Docs/issues/2319
377
+ .stageMask = asset::PIPELINE_STAGE_FLAGS::ALL_COMMANDS_BITS
378
+ }};
366
379
const IQueue::SSubmitInfo submitInfos[1 ] = {{
367
380
.waitSemaphores = waitSemaphores,
368
381
.commandBuffers = commandBuffers,
@@ -374,7 +387,7 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
374
387
if (willBlit)
375
388
{
376
389
// let the others know we've enqueued another TBP
377
- const auto oldVal = m_pLastPresentSignalValue ->exchange (acquireCount);
390
+ const auto oldVal = m_lastPresent. pPresentSemaphoreWaitValue ->exchange (acquireCount);
378
391
assert (oldVal<acquireCount);
379
392
380
393
auto frameResources = core::make_refctd_dynamic_array<core::smart_refctd_dynamic_array<ISwapchain::void_refctd_ptr>>(2 );
@@ -403,9 +416,8 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
403
416
// Why do we delay the swapchain recreate present until the rendering of the most recent present source is done? Couldn't we present whatever latest Triple Buffer is done?
404
417
// No because there can be presents enqueued whose wait semaphores have not signalled yet, meaning there could be images presented in the future.
405
418
// 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.
406
- IQueue::SSubmitInfo::SSemaphoreInfo m_lastPresentWait = {};
407
- SPresentSource m_lastPresentSource = {};
408
- std::atomic_uint64_t * m_pLastPresentSignalValue = nullptr ;
419
+ SCachedPresentInfo m_lastPresent = {};
420
+ // extras for lifetime preservation till next immediate spontaneous triple buffer present
409
421
core::smart_refctd_ptr<ISemaphore> m_lastPresentSemaphore = {};
410
422
core::smart_refctd_ptr<IGPUImage> m_lastPresentSourceImage = {};
411
423
};
0 commit comments