Skip to content

Commit 00d3a1a

Browse files
use a Threadsafe Queue for present, make the ISimpleManagedSurface::ISwapchainResources hold swapchain images, and prototype ISimpleManagedSurface::immediateBlit
1 parent 8f0b645 commit 00d3a1a

File tree

4 files changed

+212
-8
lines changed

4 files changed

+212
-8
lines changed

include/nbl/video/utilities/ISimpleManagedSurface.h

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
namespace nbl::video
1010
{
1111

12+
// The use of this class is supposed to be externally synchronized
1213
class NBL_API2 ISimpleManagedSurface : public core::IReferenceCounted
1314
{
1415
public:
@@ -46,7 +47,7 @@ class NBL_API2 ISimpleManagedSurface : public core::IReferenceCounted
4647
}
4748

4849
// Just pick the first queue within the first compatible family
49-
inline IQueue* pickQueue(ILogicalDevice* device) const
50+
inline CThreadSafeQueueAdapter* pickQueue(ILogicalDevice* device) const
5051
{
5152
return device->getThreadSafeQueue(pickQueueFamily(device),0);
5253
}
@@ -72,6 +73,14 @@ class NBL_API2 ISimpleManagedSurface : public core::IReferenceCounted
7273
// If status is `NOT_READY` this might either return nullptr or the old stale swapchain that should be retired
7374
inline ISwapchain* getSwapchain() const {return swapchain.get();}
7475

76+
//
77+
inline IGPUImage* getImage(const uint8_t index) const
78+
{
79+
if (index<images.size())
80+
return images[index].get();
81+
return nullptr;
82+
}
83+
7584
protected:
7685
virtual ~ISwapchainResources()
7786
{
@@ -85,6 +94,7 @@ class NBL_API2 ISimpleManagedSurface : public core::IReferenceCounted
8594
if (status==STATUS::NOT_READY)
8695
return;
8796
invalidate_impl();
97+
std::fill(images.begin(),images.end(),nullptr);
8898
status = STATUS::NOT_READY;
8999
}
90100
// mark the surface & swapchain as hopeless and ready for deletion due to errors
@@ -110,18 +120,38 @@ class NBL_API2 ISimpleManagedSurface : public core::IReferenceCounted
110120
// here you drop your own resources of the base class
111121
virtual void invalidate_impl() = 0;
112122

113-
// this will notify you of the swapchain being created and when you can start creating per-swapchain and per-image resources
114-
virtual bool onCreateSwapchain() = 0;
123+
// This will notify you of the swapchain being created and when you can start creating per-swapchain and per-image resources
124+
// NOTE: Current class doesn't trigger it because it knows nothing about how and when to create or recreate a swapchain.
125+
inline bool onCreateSwapchain()
126+
{
127+
auto device = const_cast<ILogicalDevice*>(swapchain->getOriginDevice());
128+
// create images
129+
for (auto i=0u; i<swapchain->getImageCount(); i++)
130+
{
131+
images[i] = swapchain->createImage(i);
132+
if (!images[i])
133+
{
134+
std::fill_n(images.begin(),i,nullptr);
135+
return false;
136+
}
137+
}
138+
139+
return onCreateSwapchain_impl();
140+
}
141+
// extra things you might need
142+
virtual bool onCreateSwapchain_impl() = 0;
115143

116144
// As per the above, the swapchain might not be possible to create or recreate right away, so this might be
117145
// either nullptr before the first successful acquire or the old to-be-retired swapchain.
118146
core::smart_refctd_ptr<ISwapchain> swapchain = {};
147+
// Useful for everyone
148+
std::array<core::smart_refctd_ptr<IGPUImage>,ISwapchain::MaxImages> images = {};
119149
// We start in not-ready, instead of irrecoverable, because we haven't tried to create a swapchain yet
120150
STATUS status = STATUS::NOT_READY;
121151
};
122152

123153
// We need to defer the swapchain creation till the Physical Device is chosen and Queues are created together with the Logical Device
124-
inline bool init(IQueue* queue, const ISwapchain::SSharedCreationParams& sharedParams={})
154+
inline bool init(CThreadSafeQueueAdapter* queue, const ISwapchain::SSharedCreationParams& sharedParams={})
125155
{
126156
getSwapchainResources().becomeIrrecoverable();
127157
if (!queue)
@@ -144,7 +174,7 @@ class NBL_API2 ISimpleManagedSurface : public core::IReferenceCounted
144174
}
145175

146176
//
147-
inline IQueue* getAssignedQueue() const {return m_queue;}
177+
inline CThreadSafeQueueAdapter* getAssignedQueue() const {return m_queue;}
148178

149179
// An interesting solution to the "Frames In Flight", our tiny wrapper class will have its own Timeline Semaphore incrementing with each acquire, and thats it.
150180
inline uint64_t getAcquireCount() {return m_acquireCount;}
@@ -154,6 +184,10 @@ class NBL_API2 ISimpleManagedSurface : public core::IReferenceCounted
154184
inline int8_t acquireNextImage()
155185
{
156186
// Only check upon an acquire, previously acquired images MUST be presented
187+
// Window/Surface got closed, but won't actually disappear UNTIL the swapchain gets dropped,
188+
// which is outside of our control here as there is a nice chain of lifetimes of:
189+
// `ExternalCmdBuf -via usage of-> Swapchain Image -memory provider-> Swapchain -created from-> Window/Surface`
190+
// Only when the last user of the swapchain image drops it, will the window die.
157191
if (m_cb->isWindowOpen())
158192
{
159193
using status_t = ISwapchainResources::STATUS;
@@ -230,6 +264,10 @@ class NBL_API2 ISimpleManagedSurface : public core::IReferenceCounted
230264
return false;
231265
}
232266

267+
// Utility function for more complex Managed Surfaces, it does not increase the `m_acquireCount` but does acquire and present immediately
268+
using image_barrier_t = IGPUCommandBuffer::SImageMemoryBarrier<IGPUCommandBuffer::SOwnershipTransferBarrier>;
269+
bool immediateBlit(const image_barrier_t& contents, IQueue* blitQueue=nullptr);
270+
233271
protected:
234272
inline ISimpleManagedSurface(core::smart_refctd_ptr<ISurface>&& _surface, ICallback* _cb) : m_surface(std::move(_surface)), m_cb(_cb) {}
235273
virtual inline ~ISimpleManagedSurface() = default;
@@ -247,8 +285,8 @@ class NBL_API2 ISimpleManagedSurface : public core::IReferenceCounted
247285
// persistent and constant for whole lifetime of the object
248286
const core::smart_refctd_ptr<ISurface> m_surface;
249287
ICallback* const m_cb = nullptr;
250-
// might get re-assigned
251-
IQueue* m_queue = nullptr;
288+
// Use a Threadsafe queue to make sure we can do smooth resize in derived class, might get re-assigned
289+
CThreadSafeQueueAdapter* m_queue = nullptr;
252290
// created and persistent after first initialization
253291
core::smart_refctd_ptr<ISemaphore> m_acquireSemaphore;
254292
// You don't want to use `m_swapchainResources.swapchain->getAcquireCount()` because it resets when swapchain gets recreated

src/nbl/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ set(NBL_VIDEO_SOURCES
264264
${NBL_ROOT_PATH}/src/nbl/video/utilities/CPropertyPoolHandler.cpp
265265
${NBL_ROOT_PATH}/src/nbl/video/utilities/CScanner.cpp
266266
${NBL_ROOT_PATH}/src/nbl/video/utilities/CComputeBlit.cpp
267+
${NBL_ROOT_PATH}/src/nbl/video/utilities/ISimpleManagedSurface.cpp
267268

268269
# Interfaces
269270
${NBL_ROOT_PATH}/src/nbl/video/IAPIConnection.cpp
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
#include "nbl/video/utilities/ISimpleManagedSurface.h"
2+
3+
using namespace nbl;
4+
using namespace video;
5+
6+
7+
bool ISimpleManagedSurface::immediateBlit(const image_barrier_t& contents, IQueue* blitQueue)
8+
{
9+
if (!contents.image || !m_queue)
10+
return false;
11+
12+
auto device = const_cast<ILogicalDevice*>(m_queue->getOriginDevice());
13+
const auto qFamProps = device->getPhysicalDevice()->getQueueFamilyProperties();
14+
if (!blitQueue)
15+
{
16+
// default to using the presentation queue if it can blit
17+
if (qFamProps[m_queue->getFamilyIndex()].queueFlags.hasFlags(IQueue::FAMILY_FLAGS::GRAPHICS_BIT))
18+
blitQueue = m_queue;
19+
else // just pick first compatible
20+
for (uint8_t qFam=0; qFam<ILogicalDevice::MaxQueueFamilies; qFam++)
21+
if (device->getQueueCount(qFam) && qFamProps[qFam].queueFlags.hasFlags(IQueue::FAMILY_FLAGS::GRAPHICS_BIT))
22+
{
23+
blitQueue = device->getThreadSafeQueue(qFam,0);
24+
break;
25+
}
26+
}
27+
28+
if (!blitQueue || qFamProps[blitQueue->getFamilyIndex()].queueFlags.hasFlags(IQueue::FAMILY_FLAGS::GRAPHICS_BIT))
29+
return false;
30+
31+
// create a different semaphore so we don't increase the acquire counter in `this`
32+
auto semaphore = device->createSemaphore(0);
33+
if (!semaphore)
34+
return false;
35+
36+
// transient commandbuffer and pool to perform the blit
37+
core::smart_refctd_ptr<IGPUCommandBuffer> cmdbuf;
38+
{
39+
auto pool = device->createCommandPool(blitQueue->getFamilyIndex(),IGPUCommandPool::CREATE_FLAGS::TRANSIENT_BIT);
40+
if (!pool || !pool->createCommandBuffers(IGPUCommandPool::BUFFER_LEVEL::PRIMARY,{&cmdbuf,1}) || !cmdbuf)
41+
return false;
42+
}
43+
44+
const IQueue::SSubmitInfo::SSemaphoreInfo acquired[1] = {
45+
{
46+
.semaphore=semaphore.get(),
47+
.value=1
48+
}
49+
};
50+
// acquire
51+
;
52+
53+
// now record the blit commands
54+
auto acquiredImage = getSwapchainResources().getImage(0xffu);
55+
{
56+
if (!cmdbuf->begin(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT))
57+
return false;
58+
59+
const auto blitSrcLayout = IGPUImage::LAYOUT::TRANSFER_SRC_OPTIMAL;
60+
const auto blitDstLayout = IGPUImage::LAYOUT::TRANSFER_DST_OPTIMAL;
61+
IGPUCommandBuffer::SPipelineBarrierDependencyInfo depInfo = {};
62+
63+
// barrier before
64+
const image_barrier_t preBarriers[2] = {
65+
{
66+
.barrier = {
67+
.dep = {
68+
.srcStageMask = asset::PIPELINE_STAGE_FLAGS::NONE, // acquire isn't a stage
69+
.srcAccessMask = asset::ACCESS_FLAGS::NONE, // performs no accesses
70+
.dstStageMask = asset::PIPELINE_STAGE_FLAGS::BLIT_BIT,
71+
.dstAccessMask = asset::ACCESS_FLAGS::TRANSFER_WRITE_BIT
72+
}
73+
},
74+
.image = acquiredImage,
75+
.subresourceRange = {
76+
.aspectMask = IGPUImage::EAF_COLOR_BIT,
77+
.baseMipLevel = 0,
78+
.levelCount = 1,
79+
.baseArrayLayer = 0,
80+
.layerCount = 1
81+
},
82+
.oldLayout = IGPUImage::LAYOUT::UNDEFINED, // I do not care about previous contents
83+
.newLayout = blitDstLayout
84+
},
85+
{
86+
.barrier = {
87+
.dep = {
88+
.srcStageMask = contents.barrier.dep.srcStageMask,
89+
.srcAccessMask = contents.barrier.dep.srcAccessMask,
90+
.dstStageMask = asset::PIPELINE_STAGE_FLAGS::BLIT_BIT,
91+
.dstAccessMask = asset::ACCESS_FLAGS::TRANSFER_READ_BIT
92+
},
93+
.ownershipOp = IGPUCommandBuffer::SOwnershipTransferBarrier::OWNERSHIP_OP::ACQUIRE,
94+
.otherQueueFamilyIndex = contents.barrier.otherQueueFamilyIndex
95+
},
96+
.image = contents.image,
97+
.subresourceRange = contents.subresourceRange,
98+
.oldLayout = contents.oldLayout,
99+
.newLayout = blitSrcLayout
100+
}
101+
};
102+
depInfo.imgBarriers = preBarriers;
103+
if (!cmdbuf->pipelineBarrier(asset::EDF_NONE,depInfo))
104+
return false;
105+
106+
// do the blit
107+
{
108+
const IGPUCommandBuffer::SImageBlit region = {
109+
// .srcSubresource = contents.subresourceRange,
110+
// .srcOffsets[2] = ,
111+
// .dstSubresource = ,
112+
// .dstOffsets =,
113+
};
114+
if (!cmdbuf->blitImage(contents.image,blitSrcLayout,acquiredImage,blitDstLayout,1,&region,IGPUSampler::ETF_LINEAR))
115+
return false;
116+
}
117+
118+
// barrier after
119+
const image_barrier_t postBarriers[2] = {
120+
{
121+
.barrier = {
122+
// When transitioning the image to VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR or VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, there is no need to delay subsequent processing,
123+
// or perform any visibility operations (as vkQueuePresentKHR performs automatic visibility operations).
124+
// To achieve this, the dstAccessMask member of the VkImageMemoryBarrier should be set to 0, and the dstStageMask parameter should be set to VK_PIPELINE_STAGE_2_NONE
125+
.dep = {
126+
.srcStageMask = preBarriers[0].barrier.dep.dstStageMask,
127+
.srcAccessMask = preBarriers[0].barrier.dep.dstAccessMask,
128+
.dstStageMask = asset::PIPELINE_STAGE_FLAGS::NONE, // Present isn't a stage
129+
.dstAccessMask = asset::ACCESS_FLAGS::NONE // it doesn't perform accesses known to VK
130+
}
131+
},
132+
.image = preBarriers[0].image,
133+
.subresourceRange = preBarriers[0].subresourceRange,
134+
.oldLayout = preBarriers[0].newLayout,
135+
.newLayout = IGPUImage::LAYOUT::PRESENT_SRC
136+
},
137+
{
138+
.barrier = {
139+
.dep = {
140+
.srcStageMask = preBarriers[1].barrier.dep.dstStageMask,
141+
.srcAccessMask = preBarriers[1].barrier.dep.dstAccessMask,
142+
.dstStageMask = contents.barrier.dep.dstStageMask,
143+
.dstAccessMask = contents.barrier.dep.dstAccessMask
144+
},
145+
.ownershipOp = IGPUCommandBuffer::SOwnershipTransferBarrier::OWNERSHIP_OP::RELEASE,
146+
.otherQueueFamilyIndex = contents.barrier.otherQueueFamilyIndex
147+
},
148+
.image = preBarriers[1].image,
149+
.subresourceRange = preBarriers[1].subresourceRange,
150+
.oldLayout = preBarriers[1].newLayout,
151+
.newLayout = contents.newLayout
152+
}
153+
};
154+
depInfo.imgBarriers = postBarriers;
155+
if (!cmdbuf->pipelineBarrier(asset::EDF_NONE,depInfo))
156+
return false;
157+
}
158+
159+
// submit
160+
161+
// present
162+
;
163+
164+
return true;
165+
}

0 commit comments

Comments
 (0)