1
+ #ifndef _NBL_VIDEO_I_SIMPLE_MANAGED_SURFACE_H_INCLUDED_
2
+ #define _NBL_VIDEO_I_SIMPLE_MANAGED_SURFACE_H_INCLUDED_
3
+
4
+
5
+ #include " nbl/video/ISwapchain.h"
6
+ #include " nbl/video/ILogicalDevice.h"
7
+
8
+
9
+ namespace nbl ::video
10
+ {
11
+
12
+ class NBL_API2 ISimpleManagedSurface : public core::IReferenceCounted
13
+ {
14
+ public:
15
+ // Simple callback to facilitate detection of window being closed
16
+ class ICallback : public ui ::IWindow::IEventCallback
17
+ {
18
+ public:
19
+ inline ICallback () : m_windowNotClosed(true ) {}
20
+
21
+ // unless you create a separate callback per window, both will "trip" this condition
22
+ inline bool isWindowOpen () const {return m_windowNotClosed;}
23
+
24
+ private:
25
+ inline virtual bool onWindowClosed_impl () override
26
+ {
27
+ m_windowNotClosed = false ;
28
+ return true ;
29
+ }
30
+
31
+ bool m_windowNotClosed;
32
+ };
33
+
34
+ //
35
+ inline ISurface* getSurface () {return m_surface.get ();}
36
+ inline const ISurface* getSurface () const {return m_surface.get ();}
37
+
38
+ // A small utility for the boilerplate
39
+ inline uint8_t pickQueueFamily (ILogicalDevice* device) const
40
+ {
41
+ uint8_t qFam = 0u ;
42
+ for (; qFam<ILogicalDevice::MaxQueueFamilies; qFam++)
43
+ if (device->getQueueCount (qFam) && m_surface->isSupportedForPhysicalDevice (device->getPhysicalDevice (),qFam))
44
+ break ;
45
+ return qFam;
46
+ }
47
+
48
+ // Just pick the first queue within the first compatible family
49
+ inline IQueue* pickQueue (ILogicalDevice* device) const
50
+ {
51
+ return device->getThreadSafeQueue (pickQueueFamily (device),0 );
52
+ }
53
+
54
+ // A class to hold resources that can die/invalidate spontaneously when a swapchain gets re-created.
55
+ // Due to the inheritance and down-casting, you should make your own const `getSwapchainResources()` in the derived class thats not an override
56
+ class NBL_API2 ISwapchainResources : core::InterfaceUnmovable
57
+ {
58
+ friend class ISimpleManagedSurface ;
59
+
60
+ public:
61
+ // If window gets minimized on some platforms or more rarely if it gets resized weirdly, the render area becomes 0 so its impossible to recreate a swapchain.
62
+ // So we need to defer the swapchain re-creation until we can resize to a valid extent.
63
+ enum class STATUS : int8_t
64
+ {
65
+ IRRECOVERABLE = -1 ,
66
+ USABLE,
67
+ NOT_READY = 1
68
+ };
69
+ // If `getStatus()==STATUS::IRRECOVERABLE` the Managed Surface Object is useless and can't be recovered into a functioning state.
70
+ inline STATUS getStatus () const {return status;}
71
+
72
+ // If status is `NOT_READY` this might either return nullptr or the old stale swapchain that should be retired
73
+ inline ISwapchain* getSwapchain () const {return swapchain.get ();}
74
+
75
+ protected:
76
+ virtual ~ISwapchainResources ()
77
+ {
78
+ // just to avoid deadlocks due to circular refcounting
79
+ becomeIrrecoverable ();
80
+ }
81
+
82
+ // just drop the per-swapchain resources, e.g. Framebuffers with each of the swapchain images, or even pre-recorded commandbuffers
83
+ inline void invalidate ()
84
+ {
85
+ if (status==STATUS::NOT_READY)
86
+ return ;
87
+ invalidate_impl ();
88
+ status = STATUS::NOT_READY;
89
+ }
90
+ // mark the surface & swapchain as hopeless and ready for deletion due to errors
91
+ inline void becomeIrrecoverable ()
92
+ {
93
+ if (status==STATUS::IRRECOVERABLE)
94
+ return ;
95
+
96
+ // Want to nullify things in an order that leads to fastest drops (if possible) and shallowest callstacks when refcounting
97
+ invalidate ();
98
+
99
+ // We need to call this method manually to make sure resources latched on swapchain images are dropped and cycles broken, otherwise its
100
+ // EXTERMELY LIKELY (if you don't reset CommandBuffers) that you'll end up with a circular reference, for example:
101
+ // `CommandBuffer -> SC Image[i] -> Swapchain -> FrameResource[i] -> CommandBuffer`
102
+ // and a memory leak of: Swapchain and its Images, CommandBuffer and its pool CommandPool, and any resource used by the CommandBuffer.
103
+ if (swapchain)
104
+ while (swapchain->acquiredImagesAwaitingPresent ()) {}
105
+ swapchain = nullptr ;
106
+
107
+ status = STATUS::IRRECOVERABLE;
108
+ }
109
+
110
+ // here you drop your own resources of the base class
111
+ virtual void invalidate_impl () = 0;
112
+
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;
115
+
116
+ // As per the above, the swapchain might not be possible to create or recreate right away, so this might be
117
+ // either nullptr before the first successful acquire or the old to-be-retired swapchain.
118
+ core::smart_refctd_ptr<ISwapchain> swapchain = {};
119
+ // We start in not-ready, instead of irrecoverable, because we haven't tried to create a swapchain yet
120
+ STATUS status = STATUS::NOT_READY;
121
+ };
122
+
123
+ // 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={})
125
+ {
126
+ getSwapchainResources ().becomeIrrecoverable ();
127
+ if (!queue)
128
+ return false ;
129
+
130
+ auto device = const_cast <ILogicalDevice*>(queue->getOriginDevice ());
131
+ // want to keep using the same semaphore throughout the lifetime to not run into sync issues
132
+ if (!m_acquireSemaphore)
133
+ {
134
+ m_acquireSemaphore = device->createSemaphore (0u );
135
+ if (!m_acquireSemaphore)
136
+ return false ;
137
+ }
138
+
139
+ if (!init_impl (queue,sharedParams))
140
+ return false ;
141
+
142
+ m_queue = queue;
143
+ return true ;
144
+ }
145
+
146
+ //
147
+ inline IQueue* getAssignedQueue () const {return m_queue;}
148
+
149
+ // 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.
150
+ inline uint64_t getAcquireCount () {return m_acquireCount;}
151
+ inline ISemaphore* getAcquireSemaphore () {return m_acquireSemaphore.get ();}
152
+
153
+ // RETURNS: Negative on failure, otherwise its the acquired image's index.
154
+ inline int8_t acquireNextImage ()
155
+ {
156
+ // Only check upon an acquire, previously acquired images MUST be presented
157
+ if (m_cb->isWindowOpen ())
158
+ {
159
+ using status_t = ISwapchainResources::STATUS;
160
+ switch (getSwapchainResources ().getStatus ())
161
+ {
162
+ case status_t ::NOT_READY:
163
+ if (handleNotReady ())
164
+ break ;
165
+ [[fallthrough]];
166
+ case status_t ::IRRECOVERABLE:
167
+ return -1 ;
168
+ default :
169
+ break ;
170
+ }
171
+
172
+ const IQueue::SSubmitInfo::SSemaphoreInfo signalInfos[1 ] = {
173
+ {
174
+ .semaphore =m_acquireSemaphore.get (),
175
+ .value =m_acquireCount+1
176
+ }
177
+ };
178
+
179
+ uint32_t imageIndex;
180
+ // We don't support resizing (swapchain recreation) in this example, so a failure to acquire is a failure to keep running
181
+ switch (getSwapchainResources ().swapchain ->acquireNextImage ({.queue =m_queue,.signalSemaphores =signalInfos},&imageIndex))
182
+ {
183
+ case ISwapchain::ACQUIRE_IMAGE_RESULT::SUBOPTIMAL: [[fallthrough]];
184
+ case ISwapchain::ACQUIRE_IMAGE_RESULT::SUCCESS:
185
+ // the semaphore will only get signalled upon a successful acquire
186
+ m_acquireCount++;
187
+ return static_cast <int8_t >(imageIndex);
188
+ case ISwapchain::ACQUIRE_IMAGE_RESULT::TIMEOUT: [[fallthrough]];
189
+ case ISwapchain::ACQUIRE_IMAGE_RESULT::NOT_READY: // don't throw our swapchain away just because of a timeout XD
190
+ assert (false ); // shouldn't happen though cause we use uint64_t::max() as the timeout
191
+ break ;
192
+ case ISwapchain::ACQUIRE_IMAGE_RESULT::OUT_OF_DATE:
193
+ // try again, will re-create swapchain
194
+ {
195
+ const int8_t retval = handleOutOfDate ();
196
+ if (retval>=0 )
197
+ return retval;
198
+ }
199
+ default :
200
+ break ;
201
+ }
202
+ }
203
+ getSwapchainResources ().becomeIrrecoverable ();
204
+ return -1 ;
205
+ }
206
+
207
+ // Frame Resources are not optional, shouldn't be null!
208
+ inline bool present (const uint8_t imageIndex, const std::span<const IQueue::SSubmitInfo::SSemaphoreInfo> waitSemaphores, core::smart_refctd_ptr<core::IReferenceCounted>&& frameResources)
209
+ {
210
+ if (getSwapchainResources ().getStatus ()!=ISwapchainResources::STATUS::USABLE || waitSemaphores.empty () || !frameResources)
211
+ return false ;
212
+
213
+ const ISwapchain::SPresentInfo info = {
214
+ .queue = m_queue,
215
+ .imgIndex = imageIndex,
216
+ .waitSemaphores = waitSemaphores
217
+ };
218
+ switch (getSwapchainResources ().getSwapchain ()->present (info,std::move (frameResources)))
219
+ {
220
+ case ISwapchain::PRESENT_RESULT::SUBOPTIMAL: [[fallthrough]];
221
+ case ISwapchain::PRESENT_RESULT::SUCCESS:
222
+ return true ;
223
+ case ISwapchain::PRESENT_RESULT::OUT_OF_DATE:
224
+ getSwapchainResources ().invalidate ();
225
+ break ;
226
+ default :
227
+ getSwapchainResources ().becomeIrrecoverable ();
228
+ break ;
229
+ }
230
+ return false ;
231
+ }
232
+
233
+ protected:
234
+ inline ISimpleManagedSurface (core::smart_refctd_ptr<ISurface>&& _surface, ICallback* _cb) : m_surface(std::move(_surface)), m_cb(_cb) {}
235
+ virtual inline ~ISimpleManagedSurface () = default ;
236
+
237
+ virtual ISwapchainResources& getSwapchainResources () = 0;
238
+
239
+ // generally used to check that per-swapchain resources can be created (including the swapchain itself)
240
+ virtual bool init_impl (IQueue* queue, const ISwapchain::SSharedCreationParams& sharedParams) = 0;
241
+
242
+ // handlers for acquisition exceptions
243
+ virtual bool handleNotReady () = 0;
244
+ virtual int8_t handleOutOfDate () = 0;
245
+
246
+ private:
247
+ // persistent and constant for whole lifetime of the object
248
+ const core::smart_refctd_ptr<ISurface> m_surface;
249
+ ICallback* const m_cb = nullptr ;
250
+ // might get re-assigned
251
+ IQueue* m_queue = nullptr ;
252
+ // created and persistent after first initialization
253
+ core::smart_refctd_ptr<ISemaphore> m_acquireSemaphore;
254
+ // You don't want to use `m_swapchainResources.swapchain->getAcquireCount()` because it resets when swapchain gets recreated
255
+ uint64_t m_acquireCount = 0 ;
256
+ };
257
+
258
+ }
259
+ #endif
0 commit comments