@@ -16,57 +16,48 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
16
16
class ICallback : public ISimpleManagedSurface ::ICallback
17
17
{
18
18
protected:
19
- friend class IResizableSurface ;
20
- // `recreator` owns the `ISurface`, which refcounts the `IWindow` which refcounts the callback, so fumb pointer to avoid cycles
21
- inline void setSwapchainRecreator (IResizableSurface* recreator) {m_recreator = recreator;}
22
-
23
19
// remember to call this when overriding it further down!
24
20
inline virtual bool onWindowResized_impl (uint32_t w, uint32_t h) override
25
21
{
26
- m_recreator->explicitRecreateSwapchain (w,h);
22
+ if (m_recreator)
23
+ m_recreator->explicitRecreateSwapchain (w,h);
27
24
return true ;
28
25
}
29
26
30
- IResizableSurface* m_recreator;
27
+ private:
28
+ friend class IResizableSurface ;
29
+ // `recreator` owns the `ISurface`, which refcounts the `IWindow` which refcounts the callback, so fumb pointer to avoid cycles
30
+ inline void setSwapchainRecreator (IResizableSurface* recreator) { m_recreator = recreator; }
31
+
32
+ IResizableSurface* m_recreator = nullptr ;
31
33
};
32
34
33
35
//
34
36
class NBL_API2 ISwapchainResources : public core::IReferenceCounted, public ISimpleManagedSurface::ISwapchainResources
35
37
{
36
38
protected:
37
- // remember to call in the derived class on all of these
38
- virtual inline void invalidate_impl () override
39
- {
40
- std::fill (cmdbufs.begin (),cmdbufs.end (),nullptr );
41
- }
42
- virtual inline bool onCreateSwapchain_impl (const uint8_t qFam) override
43
- {
44
- auto device = const_cast <ILogicalDevice*>(swapchain->getOriginDevice ());
45
-
46
- // transient commandbuffer and pool to perform the blits or copies to SC images
47
- auto pool = device->createCommandPool (qFam,IGPUCommandPool::CREATE_FLAGS::RESET_COMMAND_BUFFER_BIT);
48
- if (!pool || !pool->createCommandBuffers (IGPUCommandPool::BUFFER_LEVEL::PRIMARY,{cmdbufs.data (),swapchain->getImageCount ()}))
49
- return false ;
50
-
51
- return true ;
52
- }
53
-
54
- // The `cmdbuf` is already begun when given to the callback, and will be ended inside
55
- // Returns what stage the submit signal semaphore should signal from for the presentation to wait on
56
- virtual asset::PIPELINE_STAGE_FLAGS tripleBufferPresent (IGPUCommandBuffer* cmdbuf, IGPUImage* source) = 0;
57
-
58
- private:
59
39
friend class IResizableSurface ;
60
40
61
- std::array<core::smart_refctd_ptr<IGPUCommandBuffer>,ISwapchain::MaxImages> cmdbufs = {};
41
+ // The `cmdbuf` is already begun when given to the callback, and will be ended outside.
42
+ // Returns what stage the submit signal semaphore should signal from for the presentation to wait on.
43
+ // User is responsible for transitioning both images' layouts, acquiring ownership etc.
44
+ virtual asset::PIPELINE_STAGE_FLAGS tripleBufferPresent (IGPUCommandBuffer* cmdbuf, IGPUImage* source, const uint8_t dstIx) = 0;
62
45
};
63
46
47
+ //
48
+ inline CThreadSafeQueueAdapter* pickQueue (ILogicalDevice* device) const override final
49
+ {
50
+ const auto fam = pickQueueFamily (device);
51
+ return device->getThreadSafeQueue (fam,device->getQueueCount (fam)-1 );
52
+ }
53
+
64
54
struct SPresentInfo
65
55
{
66
56
inline operator bool () const {return source;}
67
57
68
58
IGPUImage* source = nullptr ;
69
59
// TODO: add sourceRegion
60
+ // only allow waiting for one semaphore, because there's only one source to present!
70
61
IQueue::SSubmitInfo::SSemaphoreInfo wait;
71
62
core::IReferenceCounted* frameResources;
72
63
};
@@ -101,31 +92,53 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
101
92
102
93
// The blit enqueue operations are fast enough to be done under a mutex, this is safer on some platforms
103
94
// You need to "race to present" to avoid a flicker
104
- return present ({.source =m_lastPresentSource,.wait =m_lastPresentWait,.frameResources =nullptr });
95
+ return present_impl ({.source =m_lastPresentSource,.wait =m_lastPresentWait,.frameResources =nullptr });
105
96
}
106
97
107
98
protected:
108
- inline IResizableSurface (core::smart_refctd_ptr<ISurface>&& _surface, ICallback* _cb) : ISimpleManagedSurface(std::move(_surface),_cb)
109
- {
110
- _cb->setSwapchainRecreator (this );
111
- }
99
+ using ISimpleManagedSurface::ISimpleManagedSurface;
112
100
virtual inline ~IResizableSurface ()
113
101
{
114
102
static_cast <ICallback*>(m_cb)->setSwapchainRecreator (nullptr );
115
103
}
116
104
117
105
//
118
- inline CThreadSafeQueueAdapter* pickQueue (ILogicalDevice* device) const override final
106
+ inline void deinit_impl () override final
119
107
{
120
- const auto fam = pickQueueFamily (device);
121
- return device->getThreadSafeQueue (fam,device->getQueueCount (fam)-1 );
108
+ // stop any calls into explicit resizes
109
+ std::unique_lock guard (m_swapchainResourcesMutex);
110
+ static_cast <ICallback*>(m_cb)->setSwapchainRecreator (nullptr );
111
+
112
+ m_lastPresentWait = {};
113
+ m_lastPresentSource = {};
114
+ m_lastPresentSemaphore = nullptr ;
115
+ m_lastPresentSourceImage = nullptr ;
116
+
117
+ if (m_blitSemaphore)
118
+ {
119
+ auto device = const_cast <ILogicalDevice*>(m_blitSemaphore->getOriginDevice ());
120
+ const ISemaphore::SWaitInfo info[1 ] = {{
121
+ .semaphore = m_blitSemaphore.get (), .value = getAcquireCount ()
122
+ }};
123
+ device->blockForSemaphores (info);
124
+ }
125
+
126
+ std::fill (m_cmdbufs.begin (),m_cmdbufs.end (),nullptr );
127
+ m_blitSemaphore = nullptr ;
122
128
}
123
129
124
130
//
125
- inline bool init_impl (CThreadSafeQueueAdapter* queue, const ISwapchain::SSharedCreationParams& sharedParams) override final
131
+ inline bool init_impl (const ISwapchain::SSharedCreationParams& sharedParams) override final
126
132
{
133
+ // swapchain callback already deinitialized, so no mutex needed here
134
+
135
+ auto queue = getAssignedQueue ();
127
136
auto device = const_cast <ILogicalDevice*>(queue->getOriginDevice ());
128
137
138
+ m_sharedParams = sharedParams;
139
+ if (!m_sharedParams.deduce (device->getPhysicalDevice (),getSurface ()))
140
+ return false ;
141
+
129
142
// want to keep using the same semaphore throughout the lifetime to not run into sync issues
130
143
if (!m_blitSemaphore)
131
144
{
@@ -134,11 +147,16 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
134
147
return false ;
135
148
}
136
149
137
- m_sharedParams = sharedParams;
138
- if (!m_sharedParams.deduce (device->getPhysicalDevice (),getSurface ()))
150
+ // transient commandbuffer and pool to perform the blits or copies to SC images
151
+ auto pool = device->createCommandPool (queue->getFamilyIndex (),IGPUCommandPool::CREATE_FLAGS::RESET_COMMAND_BUFFER_BIT);
152
+ if (!pool || !pool->createCommandBuffers (IGPUCommandPool::BUFFER_LEVEL::PRIMARY,{m_cmdbufs.data (),getMaxFramesInFlight ()}))
139
153
return false ;
140
154
141
- return createSwapchainResources ();
155
+ if (!createSwapchainResources ())
156
+ return false ;
157
+
158
+ static_cast <ICallback*>(m_cb)->setSwapchainRecreator (this );
159
+ return true ;
142
160
}
143
161
144
162
//
@@ -205,12 +223,17 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
205
223
//
206
224
inline bool present_impl (const SPresentInfo& presentInfo)
207
225
{
208
- if (!presentInfo)
226
+ // irrecoverable or bad input
227
+ if (!presentInfo || !getSwapchainResources ())
209
228
return false ;
210
229
211
230
// delayed init of our swapchain
212
- if (! getSwapchainResources () && !recreateSwapchain ())
231
+ if (getSwapchainResources ()-> getStatus ()!=ISwapchainResources::STATUS::USABLE && !recreateSwapchain ())
213
232
return false ;
233
+
234
+ // now pointer won't change until we get out from under the lock
235
+ auto swapchainResources = static_cast <ISwapchainResources*>(getSwapchainResources ());
236
+ assert (swapchainResources);
214
237
215
238
const uint8_t imageIx = acquireNextImage ();
216
239
if (imageIx==ISwapchain::MaxImages)
@@ -228,40 +251,40 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
228
251
}
229
252
};
230
253
m_lastPresentSourceImage = core::smart_refctd_ptr<IGPUImage>(presentInfo.source );
254
+ m_lastPresentSemaphore = core::smart_refctd_ptr<ISemaphore>(presentInfo.wait .semaphore );
231
255
m_lastPresentSource = presentInfo.source ;
232
256
m_lastPresentWait = presentInfo.wait ;
233
-
234
- // now pointer won't change until we get out from under the lock
235
- auto swapchainResources = static_cast <ISwapchainResources*>(getSwapchainResources ());
236
- assert (swapchainResources);
237
257
238
258
//
239
259
auto queue = getAssignedQueue ();
240
260
auto device = const_cast <ILogicalDevice*>(queue->getOriginDevice ());
241
261
242
262
// need to wait before resetting a commandbuffer
243
- const auto scImageCount = swapchainResources-> getSwapchain ()-> getImageCount ();
244
- if (acquireCount>scImageCount )
263
+ const auto maxFramesInFlight = getMaxFramesInFlight ();
264
+ if (acquireCount>maxFramesInFlight )
245
265
{
246
266
const ISemaphore::SWaitInfo cmdbufDonePending[1 ] = {
247
267
{
248
268
.semaphore = m_blitSemaphore.get (),
249
- .value = acquireCount-scImageCount
269
+ .value = acquireCount-maxFramesInFlight
250
270
}
251
271
};
252
272
device->blockForSemaphores (cmdbufDonePending);
253
273
}
254
274
255
- const auto cmdBufIx = acquireCount%swapchainResources->getSwapchain ()->getImageCount ();
256
- auto cmdbuf = swapchainResources->cmdbufs [cmdBufIx].get ();
275
+
276
+ // Maybe tie the cmbufs to the Managed Surface instead?
277
+ const auto cmdBufIx = acquireCount%maxFramesInFlight;
278
+ auto cmdbuf = m_cmdbufs[cmdBufIx].get ();
257
279
258
280
willBlit &= cmdbuf->begin (IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT);
259
281
// now enqueue the mini-blit
260
282
const IQueue::SSubmitInfo::SSemaphoreInfo blitted[1 ] = {
261
283
{
262
284
.semaphore = m_blitSemaphore.get (),
263
285
.value = acquireCount,
264
- .stageMask = swapchainResources->tripleBufferPresent (cmdbuf,presentInfo.source )
286
+ // don't need to predicate with `willBlit` because if `willBlit==false` cmdbuf not properly begun and validation will fail
287
+ .stageMask = swapchainResources->tripleBufferPresent (cmdbuf,presentInfo.source ,imageIx)
265
288
}
266
289
};
267
290
willBlit &= bool (blitted[1 ].stageMask .value );
@@ -274,54 +297,17 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
274
297
.signalSemaphores = blitted
275
298
}};
276
299
willBlit &= queue->submit (submitInfos)==IQueue::RESULT::SUCCESS;
277
- #if 0
278
- const IQueue::SSubmitInfo::SSemaphoreInfo readyToPresent[1] = {{
279
- .semaphore = presentSemaphore,
280
- .value = presentValue,
281
- .stageMask = raceToPresent_impl(lastSource,cmdbuf)
282
- }};
283
- const std::span<const IQueue::SSubmitInfo::SSemaphoreInfo> waitSemaphores = {&wait,1};
284
-
285
- bool willBlit = false;
286
- // successful blit command enqueue if we have to wait on something
287
- if (readyToPresent->stageMask && cmdbuf->end())
288
- {
289
- const IQueue::SSubmitInfo::SCommandBufferInfo commandBuffers[1] = {{
290
- .cmdbuf=cmdbuf
291
- }};
292
- const IQueue::SSubmitInfo submits[1] = {{
293
- .waitSemaphores = waitSemaphores,
294
- .commandBuffers = commandBuffers,
295
- .signalSemaphores = readyToPresent
296
- }};
297
- willBlit = queue->submit(submits)==IQueue::RESULT::SUCCESS;
298
- }
299
-
300
- // need to present either way
301
- if (willBlit)
302
- {
303
- switch (swapchain->present({.queue=queue,.imgIndex=acquiredIx,.waitSemaphores=readyToPresent},core::smart_refctd_ptr<core::IReferenceCounted>(cmdbuf)))
304
- {
305
- case ISwapchain::PRESENT_RESULT::SUBOPTIMAL: [[fallthrough]];
306
- case ISwapchain::PRESENT_RESULT::SUCCESS:
307
- // all resources can be dropped, the swapchain will hold onto them
308
- return true;
309
- case ISwapchain::PRESENT_RESULT::OUT_OF_DATE:
310
- invalidate();
311
- break;
312
- default:
313
- becomeIrrecoverable();
314
- break;
315
- }
316
- // now in a weird situation where we have pending blit work on the GPU but no present and refcounting to keep the submitted cmdbuf and semaphores alive
317
- const ISemaphore::SWaitInfo infos[1] = {{.semaphore=presentSemaphore,.value=presentValue}};
318
- const_cast<ILogicalDevice*>(queue->getOriginDevice())->blockForSemaphores(infos);
319
- }
320
- else
321
- swapchain->present({.queue=queue,.imgIndex=acquiredIx,.waitSemaphores=waitSemaphores});
322
- return false;
323
- #endif
324
- return ISimpleManagedSurface::present (imageIx,blitted,presentInfo.frameResources );
300
+
301
+ // handle two cases of present
302
+ if (willBlit)
303
+ {
304
+ auto frameResources = core::make_refctd_dynamic_array<core::smart_refctd_dynamic_array<ISwapchain::void_refctd_ptr>>(2 );
305
+ frameResources->front () = core::smart_refctd_ptr<core::IReferenceCounted>(presentInfo.frameResources );
306
+ frameResources->back () = core::smart_refctd_ptr<IGPUCommandBuffer>(cmdbuf);
307
+ return ISimpleManagedSurface::present (imageIx,blitted,frameResources.get ());
308
+ }
309
+ else
310
+ return ISimpleManagedSurface::present (imageIx,{waitSemaphores+1 ,1 },presentInfo.frameResources );
325
311
}
326
312
327
313
// Assume it will execute under a mutex
@@ -331,15 +317,20 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
331
317
ISwapchain::SSharedCreationParams m_sharedParams = {};
332
318
333
319
private:
320
+ // 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
334
321
core::smart_refctd_ptr<ISemaphore> m_blitSemaphore;
335
- // used to protect access to swapchain resources during acquire, present and immediateBlit
322
+ // Command Buffers for blitting/copying to
323
+ std::array<core::smart_refctd_ptr<IGPUCommandBuffer>,ISwapchain::MaxImages> m_cmdbufs = {};
324
+
325
+ // used to protect access to swapchain resources during present and recreateExplicit
336
326
std::mutex m_swapchainResourcesMutex;
337
327
// 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?
338
328
// No because there can be presents enqueued whose wait semaphores have not signalled yet, meaning there could be images presented in the future.
339
329
// 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.
330
+ IQueue::SSubmitInfo::SSemaphoreInfo m_lastPresentWait = {};
340
331
decltype (SPresentInfo::source) m_lastPresentSource = {};
332
+ core::smart_refctd_ptr<ISemaphore> m_lastPresentSemaphore = {};
341
333
core::smart_refctd_ptr<IGPUImage> m_lastPresentSourceImage = {};
342
- IQueue::SSubmitInfo::SSemaphoreInfo m_lastPresentWait = {};
343
334
};
344
335
345
336
// The use of this class is supposed to be externally synchronized
0 commit comments