Skip to content

Commit 4cf0a2b

Browse files
finish prototype implementation, fix a few bugs
1 parent 7f4da9b commit 4cf0a2b

File tree

3 files changed

+176
-133
lines changed

3 files changed

+176
-133
lines changed

include/nbl/video/ILogicalDevice.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ class NBL_API2 ILogicalDevice : public core::IReferenceCounted, public IDeviceMe
275275
IDeviceMemoryBacked::SMemoryBinding binding = {};
276276
};
277277
//! The counterpart of @see bindBufferMemory for images
278-
inline bool bindImageMemory(uint32_t count, const SBindImageMemoryInfo* pBindInfos)
278+
[[deprecated]] inline bool bindImageMemory(uint32_t count, const SBindImageMemoryInfo* pBindInfos)
279279
{
280280
for (auto i=0u; i<count; i++)
281281
{
@@ -290,6 +290,10 @@ class NBL_API2 ILogicalDevice : public core::IReferenceCounted, public IDeviceMe
290290
}
291291
return bindImageMemory_impl(count,pBindInfos);
292292
}
293+
inline bool bindImageMemory(const std::span<const SBindImageMemoryInfo> bindInfos)
294+
{
295+
return bindImageMemory(bindInfos.size(),bindInfos.data());
296+
}
293297

294298
//! Descriptor Creation
295299
// Buffer (@see ICPUBuffer)

include/nbl/video/utilities/CResizableSurface.h

Lines changed: 94 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)