Skip to content

Commit 8f0b645

Browse files
Add a ISimpleManagedSurface.h, also remove graphical applications
1 parent e2b7852 commit 8f0b645

File tree

5 files changed

+263
-33
lines changed

5 files changed

+263
-33
lines changed

include/nbl/ui/IGraphicalApplicationFramework.h

Lines changed: 0 additions & 31 deletions
This file was deleted.

include/nbl/video/declarations.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include "nbl/video/utilities/IUtilities.h"
3636
#include "nbl/video/utilities/IGPUObjectFromAssetConverter.h"
3737
#include "nbl/video/utilities/SPhysicalDeviceFilter.h"
38+
#include "nbl/video/utilities/ISimpleManagedSurface.h"
3839

3940
//VT
4041
//#include "nbl/video/CGPUMeshPackerV2.h"
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
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

src/nbl/ui/CGraphicalApplicationAndroid.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
#include "nbl/system/CApplicationAndroid.h"
55
#include "nbl/system/CSystemAndroid.h"
66

7-
#include "nbl/ui/IGraphicalApplicationFramework.h"
87
#include "nbl/ui/CWindowManagerAndroid.h"
98

109
#ifdef _NBL_PLATFORM_ANDROID_
10+
#include "nbl/ui/IGraphicalApplicationFramework.h"
11+
1112
#include <jni.h>
1213
#include <fstream>
1314
namespace nbl::ui

0 commit comments

Comments
 (0)