@@ -8,6 +8,9 @@ namespace nbl::video
88
99class IGPUCommandBuffer ;
1010
11+ template <typename Functor,bool RefcountTheDevice>
12+ class MultiTimelineEventHandlerST ;
13+
1114class IQueue : public core ::Interface, public core::Unmovable
1215{
1316 public:
@@ -70,7 +73,7 @@ class IQueue : public core::Interface, public core::Unmovable
7073 DEVICE_LOST,
7174 OTHER_ERROR
7275 };
73- //
76+ // We actually make our Queue Abstraction keep track of Commandbuffers and Semaphores used in a submit until its done
7477 struct SSubmitInfo
7578 {
7679 struct SSemaphoreInfo
@@ -94,34 +97,78 @@ class IQueue : public core::Interface, public core::Unmovable
9497
9598 inline bool valid () const
9699 {
97- // any two being empty is wrong
98- if (commandBuffers.empty () && signalSemaphores.empty ()) // wait and do nothing
99- return false ;
100- if (waitSemaphores.empty () && signalSemaphores.empty ()) // work without sync
101- return false ;
102- if (waitSemaphores.empty () && commandBuffers.empty ()) // signal without doing work first
100+ // need at least one semaphore to keep the submit resources alive
101+ if (signalSemaphores.empty ())
103102 return false ;
104103 return true ;
105104 }
106105 };
107- virtual RESULT submit (const std::span<const SSubmitInfo> _submits);
108- //
109- virtual RESULT waitIdle () const = 0;
106+ NBL_API2 virtual RESULT submit (const std::span<const SSubmitInfo> _submits);
107+ // Wait idle also makes sure all past submits drop their resources
108+ NBL_API2 virtual RESULT waitIdle ();
109+ // You can call this to force an early check on whether past submits have completed and hasten when the refcount gets dropped.
110+ // Normally its the next call to `submit` that polls the event timeline for completions.
111+ NBL_API2 virtual uint32_t cullResources ();
110112
111113 // we cannot derive from IBackendObject because we can't derive from IReferenceCounted
112114 inline const ILogicalDevice* getOriginDevice () const {return m_originDevice;}
113115 inline bool wasCreatedBy (const ILogicalDevice* device) const {return device==m_originDevice;}
114116 // Vulkan: const VkQueue*
115117 virtual const void * getNativeHandle () const = 0;
118+
119+ // only public because MultiTimelineEventHandlerST needs to know about it
120+ class DeferredSubmitResourceDrop final
121+ {
122+ using smart_ptr = core::smart_refctd_ptr<IBackendObject>;
123+ core::smart_refctd_dynamic_array<smart_ptr> m_resources;
124+
125+ public:
126+ inline DeferredSubmitResourceDrop (const SSubmitInfo& info)
127+ {
128+ // We could actually not hold any signal semaphore because you're expected to use the signal result somewhere else.
129+ // However it's possible to you might only wait on one from the set and then drop the rest (UB)
130+ m_resources = core::make_refctd_dynamic_array<decltype (m_resources)>(info.signalSemaphores .size ()-1 +info.commandBuffers .size ()+info.waitSemaphores .size ());
131+ auto outRes = m_resources->data ();
132+ for (const auto & sema : info.waitSemaphores )
133+ *(outRes++) = smart_ptr (sema.semaphore );
134+ for (const auto & cb : info.commandBuffers )
135+ *(outRes++) = smart_ptr (cb.cmdbuf );
136+ // We don't hold the last signal semaphore, because the timeline does as an Event trigger.
137+ for (auto i=0u ; i<info.signalSemaphores .size ()-1 ; i++)
138+ *(outRes++) = smart_ptr (info.signalSemaphores [i].semaphore );
139+ }
140+ DeferredSubmitResourceDrop (const DeferredSubmitResourceDrop& other) = delete ;
141+ inline DeferredSubmitResourceDrop (DeferredSubmitResourceDrop&& other) : m_resources(nullptr )
142+ {
143+ this ->operator =(std::move (other));
144+ }
145+
146+ DeferredSubmitResourceDrop& operator =(const DeferredSubmitResourceDrop& other) = delete ;
147+ inline DeferredSubmitResourceDrop& operator =(DeferredSubmitResourceDrop&& other)
148+ {
149+ m_resources = std::move (other.m_resources );
150+ m_resources = nullptr ;
151+ return *this ;
152+ }
153+
154+ // always exhaustive poll, because we need to get rid of resources ASAP
155+ inline void operator ()()
156+ {
157+ m_resources = nullptr ;
158+ }
159+ };
116160
117161 protected:
118- // ! `flags` takes bits from E_CREATE_FLAGS
119- inline IQueue ( const ILogicalDevice* originDevice, const uint32_t _famIx, const core::bitflag<CREATE_FLAGS> _flags, const float _priority)
120- : m_originDevice(originDevice), m_familyIndex(_famIx), m_priority(_priority), m_flags(_flags) {}
162+ NBL_API2 IQueue (ILogicalDevice* originDevice, const uint32_t _famIx, const core::bitflag<CREATE_FLAGS> _flags, const float _priority);
163+ // WARNING: Due to ordering of destructors, its the Base Class that implements `waitIdle_impl` that needs to call `waitIdle`!
164+ NBL_API2 ~IQueue ();
121165
122166 friend class CThreadSafeQueueAdapter ;
123167 virtual RESULT submit_impl (const std::span<const SSubmitInfo> _submits) = 0;
168+ virtual RESULT waitIdle_impl () const = 0;
124169
170+ // Refcounts all resources used by Pending Submits, gets occasionally cleared out
171+ std::unique_ptr<MultiTimelineEventHandlerST<DeferredSubmitResourceDrop,false >> m_submittedResources;
125172 const ILogicalDevice* m_originDevice;
126173 const uint32_t m_familyIndex;
127174 const float m_priority;
0 commit comments