@@ -16,57 +16,48 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
1616 class ICallback : public ISimpleManagedSurface ::ICallback
1717 {
1818 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-
2319 // remember to call this when overriding it further down!
2420 inline virtual bool onWindowResized_impl (uint32_t w, uint32_t h) override
2521 {
26- m_recreator->explicitRecreateSwapchain (w,h);
22+ if (m_recreator)
23+ m_recreator->explicitRecreateSwapchain (w,h);
2724 return true ;
2825 }
2926
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 ;
3133 };
3234
3335 //
3436 class NBL_API2 ISwapchainResources : public core::IReferenceCounted, public ISimpleManagedSurface::ISwapchainResources
3537 {
3638 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:
5939 friend class IResizableSurface ;
6040
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;
6245 };
6346
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+
6454 struct SPresentInfo
6555 {
6656 inline operator bool () const {return source;}
6757
6858 IGPUImage* source = nullptr ;
6959 // TODO: add sourceRegion
60+ // only allow waiting for one semaphore, because there's only one source to present!
7061 IQueue::SSubmitInfo::SSemaphoreInfo wait;
7162 core::IReferenceCounted* frameResources;
7263 };
@@ -101,31 +92,53 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
10192
10293 // The blit enqueue operations are fast enough to be done under a mutex, this is safer on some platforms
10394 // 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 });
10596 }
10697
10798 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;
112100 virtual inline ~IResizableSurface ()
113101 {
114102 static_cast <ICallback*>(m_cb)->setSwapchainRecreator (nullptr );
115103 }
116104
117105 //
118- inline CThreadSafeQueueAdapter* pickQueue (ILogicalDevice* device) const override final
106+ inline void deinit_impl () override final
119107 {
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 ;
122128 }
123129
124130 //
125- inline bool init_impl (CThreadSafeQueueAdapter* queue, const ISwapchain::SSharedCreationParams& sharedParams) override final
131+ inline bool init_impl (const ISwapchain::SSharedCreationParams& sharedParams) override final
126132 {
133+ // swapchain callback already deinitialized, so no mutex needed here
134+
135+ auto queue = getAssignedQueue ();
127136 auto device = const_cast <ILogicalDevice*>(queue->getOriginDevice ());
128137
138+ m_sharedParams = sharedParams;
139+ if (!m_sharedParams.deduce (device->getPhysicalDevice (),getSurface ()))
140+ return false ;
141+
129142 // want to keep using the same semaphore throughout the lifetime to not run into sync issues
130143 if (!m_blitSemaphore)
131144 {
@@ -134,11 +147,16 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
134147 return false ;
135148 }
136149
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 ()}))
139153 return false ;
140154
141- return createSwapchainResources ();
155+ if (!createSwapchainResources ())
156+ return false ;
157+
158+ static_cast <ICallback*>(m_cb)->setSwapchainRecreator (this );
159+ return true ;
142160 }
143161
144162 //
@@ -205,12 +223,17 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
205223 //
206224 inline bool present_impl (const SPresentInfo& presentInfo)
207225 {
208- if (!presentInfo)
226+ // irrecoverable or bad input
227+ if (!presentInfo || !getSwapchainResources ())
209228 return false ;
210229
211230 // delayed init of our swapchain
212- if (! getSwapchainResources () && !recreateSwapchain ())
231+ if (getSwapchainResources ()-> getStatus ()!=ISwapchainResources::STATUS::USABLE && !recreateSwapchain ())
213232 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);
214237
215238 const uint8_t imageIx = acquireNextImage ();
216239 if (imageIx==ISwapchain::MaxImages)
@@ -228,40 +251,40 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
228251 }
229252 };
230253 m_lastPresentSourceImage = core::smart_refctd_ptr<IGPUImage>(presentInfo.source );
254+ m_lastPresentSemaphore = core::smart_refctd_ptr<ISemaphore>(presentInfo.wait .semaphore );
231255 m_lastPresentSource = presentInfo.source ;
232256 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);
237257
238258 //
239259 auto queue = getAssignedQueue ();
240260 auto device = const_cast <ILogicalDevice*>(queue->getOriginDevice ());
241261
242262 // 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 )
245265 {
246266 const ISemaphore::SWaitInfo cmdbufDonePending[1 ] = {
247267 {
248268 .semaphore = m_blitSemaphore.get (),
249- .value = acquireCount-scImageCount
269+ .value = acquireCount-maxFramesInFlight
250270 }
251271 };
252272 device->blockForSemaphores (cmdbufDonePending);
253273 }
254274
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 ();
257279
258280 willBlit &= cmdbuf->begin (IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT);
259281 // now enqueue the mini-blit
260282 const IQueue::SSubmitInfo::SSemaphoreInfo blitted[1 ] = {
261283 {
262284 .semaphore = m_blitSemaphore.get (),
263285 .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)
265288 }
266289 };
267290 willBlit &= bool (blitted[1 ].stageMask .value );
@@ -274,54 +297,17 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
274297 .signalSemaphores = blitted
275298 }};
276299 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 );
325311 }
326312
327313 // Assume it will execute under a mutex
@@ -331,15 +317,20 @@ class NBL_API2 IResizableSurface : public ISimpleManagedSurface
331317 ISwapchain::SSharedCreationParams m_sharedParams = {};
332318
333319 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
334321 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
336326 std::mutex m_swapchainResourcesMutex;
337327 // 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?
338328 // No because there can be presents enqueued whose wait semaphores have not signalled yet, meaning there could be images presented in the future.
339329 // 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 = {};
340331 decltype (SPresentInfo::source) m_lastPresentSource = {};
332+ core::smart_refctd_ptr<ISemaphore> m_lastPresentSemaphore = {};
341333 core::smart_refctd_ptr<IGPUImage> m_lastPresentSourceImage = {};
342- IQueue::SSubmitInfo::SSemaphoreInfo m_lastPresentWait = {};
343334};
344335
345336// The use of this class is supposed to be externally synchronized
0 commit comments