|
7 | 7 | #include "nbl/asset/asset.h"
|
8 | 8 | #include "nbl/asset/utils/ISPIRVOptimizer.h"
|
9 | 9 |
|
10 |
| -#include "nbl/video/IGPUBuffer.h" |
11 |
| -#include "nbl/video/IGPUImage.h" |
12 |
| -#include "nbl/video/ILogicalDevice.h" |
13 | 10 | #include "nbl/video/IPhysicalDevice.h"
|
14 | 11 | #include "nbl/video/alloc/StreamingTransientDataBuffer.h"
|
| 12 | +#include "nbl/video/utilities/SIntendedSubmitInfo.h" |
15 | 13 | #include "nbl/video/utilities/CPropertyPoolHandler.h"
|
16 | 14 | #include "nbl/video/utilities/CScanner.h"
|
17 | 15 | #include "nbl/video/utilities/CComputeBlit.h"
|
@@ -194,188 +192,6 @@ class NBL_API2 IUtilities : public core::IReferenceCounted
|
194 | 192 | ));
|
195 | 193 | return allocationSize;
|
196 | 194 | }
|
197 |
| - |
198 |
| - //! Struct meant to be used with any Utility (not just `IUtilities`) which exhibits "submit on overflow" behaviour. |
199 |
| - //! Such functions are non-blocking (unless overflow) and take `SIntendedSubmitInfo` by reference and patch it accordingly. |
200 |
| - //! MAKE SURE to do a submit to `queue` by yourself with a submit info obtained by casting `this` to `IQueue::SSubmitInfo` ! |
201 |
| - //! for example: in the case the `frontHalf.waitSemaphores` were already waited upon, the struct will be modified to have it's `waitSemaphores` emptied. |
202 |
| - struct SIntendedSubmitInfo final |
203 |
| - { |
204 |
| - public: |
205 |
| - inline bool valid() const |
206 |
| - { |
207 |
| - if (!frontHalf.valid() || frontHalf.commandBuffers.empty() || signalSemaphores.empty()) |
208 |
| - return false; |
209 |
| - const auto* scratch = frontHalf.getScratchCommandBuffer(); |
210 |
| - // Must be resettable so we can end, submit, wait, reset and continue recording commands into it as-if nothing happened |
211 |
| - if (!scratch->isResettable()) |
212 |
| - return false; |
213 |
| - // It makes no sense to reuse the same commands for a second submission. |
214 |
| - // Moreover its dangerous because the utilities record their own internal commands which might use subresources for which |
215 |
| - // frees have already been latched on the scratch semaphore you must signal anyway. |
216 |
| - if (!scratch->getRecordingFlags().hasFlags(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT)) |
217 |
| - return false; |
218 |
| - if (scratch->getState()!=IGPUCommandBuffer::STATE::INITIAL) |
219 |
| - return false; |
220 |
| - return true; |
221 |
| - } |
222 |
| - |
223 |
| - inline ISemaphore::SWaitInfo getScratchSemaphoreNextWait() const {return {signalSemaphores.front().semaphore,signalSemaphores.front().value};} |
224 |
| - |
225 |
| - inline operator IQueue::SSubmitInfo() const |
226 |
| - { |
227 |
| - return { |
228 |
| - .waitSemaphores = frontHalf.waitSemaphores, |
229 |
| - .commandBuffers = frontHalf.commandBuffers, |
230 |
| - .signalSemaphores = signalSemaphores |
231 |
| - }; |
232 |
| - } |
233 |
| - |
234 |
| - // One thing you might notice is that this results in a few implicit Memory and Execution Dependencies |
235 |
| - // So there's a little bit of non-deterministic behaviour we won't fight (will not insert a barrier every time you "could-have" overflown) |
236 |
| - inline void overflowSubmit() |
237 |
| - { |
238 |
| - auto cmdbuf = frontHalf.getScratchCommandBuffer(); |
239 |
| - auto& scratchSemaphore = signalSemaphores.front(); |
240 |
| - // but first sumbit the already buffered up copies |
241 |
| - cmdbuf->end(); |
242 |
| - IQueue::SSubmitInfo submit = *this; |
243 |
| - // we only signal the last semaphore which is used as scratch |
244 |
| - submit.signalSemaphores = {&scratchSemaphore,1}; |
245 |
| - assert(submit.isValid()); |
246 |
| - frontHalf.queue->submit({&submit,1}); |
247 |
| - // We wait (stall) on the immediately preceeding submission timeline semaphore signal value and increase it for the next signaller |
248 |
| - { |
249 |
| - const ISemaphore::SWaitInfo info = {scratchSemaphore.semaphore,scratchSemaphore.value++}; |
250 |
| - const_cast<ILogicalDevice*>(cmdbuf->getOriginDevice())->blockForSemaphores({&info,1}); |
251 |
| - } |
252 |
| - // we've already waited on the Host for the semaphores, no use waiting twice |
253 |
| - frontHalf.waitSemaphores = {}; |
254 |
| - // since all the commandbuffers have submitted already we only reuse the last one |
255 |
| - frontHalf.commandBuffers = {&frontHalf.commandBuffers.back(),1}; |
256 |
| - // we will still signal the same set in the future |
257 |
| - cmdbuf->reset(IGPUCommandBuffer::RESET_FLAGS::RELEASE_RESOURCES_BIT); |
258 |
| - cmdbuf->begin(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT); |
259 |
| - } |
260 |
| - |
261 |
| - |
262 |
| - //! The last CommandBuffer will be used to record the copy commands |
263 |
| - struct SFrontHalf final |
264 |
| - { |
265 |
| - //! Need a valid queue and all the command buffers except the last one should be in `EXECUTABLE` state. |
266 |
| - inline bool valid() const |
267 |
| - { |
268 |
| - if (!queue) |
269 |
| - return false; |
270 |
| - if (!commandBuffers.empty()) |
271 |
| - for (size_t i=0; i<commandBuffers.size()-1; i++) |
272 |
| - if (commandBuffers[i].cmdbuf->getState()==IGPUCommandBuffer::STATE::EXECUTABLE) |
273 |
| - return false; |
274 |
| - return true; |
275 |
| - } |
276 |
| - |
277 |
| - //! Little class to hold the storage for the modified commandbuffer span until submission time. |
278 |
| - class CRAIISpanPatch final : core::Uncopyable |
279 |
| - { |
280 |
| - public: |
281 |
| - inline ~CRAIISpanPatch() |
282 |
| - { |
283 |
| - toNullify->commandBuffers = {}; |
284 |
| - } |
285 |
| - inline CRAIISpanPatch(CRAIISpanPatch&& other) : CRAIISpanPatch() {operator=(std::move(other));} |
286 |
| - inline CRAIISpanPatch& operator=(CRAIISpanPatch&& rhs) |
287 |
| - { |
288 |
| - commandBuffersStorage = std::move(rhs.commandBuffersStorage); |
289 |
| - return *this; |
290 |
| - } |
291 |
| - |
292 |
| - inline operator bool() const {return m_recordingCommandBuffer.get();} |
293 |
| - |
294 |
| - private: |
295 |
| - friend SFrontHalf; |
296 |
| - inline CRAIISpanPatch() = default; |
297 |
| - inline CRAIISpanPatch(SFrontHalf* _toNull) : commandBuffersStorage(_toNull->commandBuffers.size()+1), toNullify(_toNull) {} |
298 |
| - |
299 |
| - core::vector<IQueue::SSubmitInfo::SCommandBufferInfo> commandBuffersStorage; |
300 |
| - // If we made a new commandbuffer we need to nullify the span so it doesn't point at stale mem |
301 |
| - SFrontHalf* toNullify = nullptr; |
302 |
| - // If new one made, then need to hold reference to it, else its just an extra ref, but whatever |
303 |
| - core::smart_refctd_ptr<IGPUCommandBuffer> m_recordingCommandBuffer; |
304 |
| - }; |
305 |
| - //! Patches the `commandBuffers` and then makes sure the last command buffer is resettable, in recording state begun with ONE_TIME_SUBMIT |
306 |
| - //! If we can't make the last cmdbuffer that way, we make a new one and add it onto the end (hence the name "patching") |
307 |
| - //! If `commandBuffers.empty()`, it will create an implicit command buffer to use for recording commands, |
308 |
| - //! else if the last command buffer is not feasible to use as scratch for whatever reason, |
309 |
| - //! it will add another temporary command buffer to end of `commandBuffers` and use it for recording. |
310 |
| - //! WARNING: If patching occurs: |
311 |
| - //! - a submission must occur before the return value goes out of scope! |
312 |
| - //! - if `!commandBuffers.empty()`, the last CommandBuffer won't be in the same state as it was before entering the function, |
313 |
| - //! because it needs to be `end()`ed before the submission |
314 |
| - //! - the destructor of the return value will clear `commandBuffers` span |
315 |
| - //! For more info on command buffer states See `ICommandBuffer::E_STATE` comments. |
316 |
| - [[nodiscard("The RAII object returned by `patch()` provides lifetimes to your spans!")]] |
317 |
| - inline CRAIISpanPatch patch() |
318 |
| - { |
319 |
| - if (auto* candidateScratch = getScratchCommandBuffer(); candidateScratch && candidateScratch->isResettable()) |
320 |
| - switch(candidateScratch->getState()) |
321 |
| - { |
322 |
| - case IGPUCommandBuffer::STATE::INITIAL: |
323 |
| - if (!candidateScratch->begin(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT)) |
324 |
| - break; |
325 |
| - [[fallthrough]]; |
326 |
| - case IGPUCommandBuffer::STATE::RECORDING: |
327 |
| - if (!candidateScratch->getRecordingFlags().hasFlags(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT)) |
328 |
| - break; |
329 |
| - { |
330 |
| - CRAIISpanPatch retval; |
331 |
| - retval.m_recordingCommandBuffer = core::smart_refctd_ptr<IGPUCommandBuffer>(candidateScratch); |
332 |
| - return retval; |
333 |
| - } |
334 |
| - break; |
335 |
| - default: |
336 |
| - break; |
337 |
| - } |
338 |
| - |
339 |
| - CRAIISpanPatch retval(this); |
340 |
| - std::copy(commandBuffers.begin(),commandBuffers.end(),retval.commandBuffersStorage.begin()); |
341 |
| - { |
342 |
| - auto pool = const_cast<ILogicalDevice*>(queue->getOriginDevice())->createCommandPool(queue->getFamilyIndex(),IGPUCommandPool::CREATE_FLAGS::RESET_COMMAND_BUFFER_BIT); |
343 |
| - if (!pool || !pool->createCommandBuffers(IGPUCommandPool::BUFFER_LEVEL::PRIMARY,{&retval.m_recordingCommandBuffer,1})) |
344 |
| - return {}; |
345 |
| - if (!retval.m_recordingCommandBuffer->begin(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT)) |
346 |
| - return {}; |
347 |
| - retval.commandBuffersStorage.back().cmdbuf = retval.m_recordingCommandBuffer.get(); |
348 |
| - } |
349 |
| - commandBuffers = retval.commandBuffersStorage; |
350 |
| - return retval; |
351 |
| - } |
352 |
| - |
353 |
| - // Use the last command buffer in intendedNextSubmit, it should be in recording state |
354 |
| - inline IGPUCommandBuffer* getScratchCommandBuffer() {return commandBuffers.empty() ? nullptr:commandBuffers.back().cmdbuf;} |
355 |
| - inline const IGPUCommandBuffer* getScratchCommandBuffer() const {return commandBuffers.empty() ? nullptr:commandBuffers.back().cmdbuf;} |
356 |
| - |
357 |
| - // This parameter is required but may be unused if there is no need to submit |
358 |
| - IQueue* queue = {}; |
359 |
| - // Use this parameter to wait for previous operations to finish before whatever commands the Utility you're using records |
360 |
| - std::span<const IQueue::SSubmitInfo::SSemaphoreInfo> waitSemaphores = {}; |
361 |
| - // Fill the commandbuffers you want to run before the first command the Utility records to run in the same submit, |
362 |
| - // for example baked command buffers with pipeline barrier commands. |
363 |
| - // Also remember that even though the last CommandBuffer is scratch, it you can record commands into it as well. |
364 |
| - std::span<IQueue::SSubmitInfo::SCommandBufferInfo> commandBuffers = {}; |
365 |
| - } frontHalf = {}; |
366 |
| - //! The first Semaphore will be used as a scratch, so don't choose the values for waits and signals yourself as we can advance the counter an arbitrary amount! |
367 |
| - //! You can actually examine the change in `signalSemaphore.front().value` to figure out how many overflows occurred. |
368 |
| - //! This semaphore is needed to "stitch together" additional submits if they occur so they occur before and after the original intended waits and signals. |
369 |
| - //! We use the first semaphore to keep the intended order of original semaphore signal and waits unchanged no matter how many overflows occur. |
370 |
| - //! You do however, NEED TO KEEP IT in the signal set of the last submit you're supposed to do manually, this allows freeing any resources used |
371 |
| - //! after the submit is done, indicating that your streaming routine is done. |
372 |
| - //! * Also use this parameter to signal new semaphores so that other submits know your Utility method is done. |
373 |
| - std::span<IQueue::SSubmitInfo::SSemaphoreInfo> signalSemaphores = {}; |
374 |
| - |
375 |
| - private: |
376 |
| - friend class IUtilities; |
377 |
| - static const char* ErrorText; |
378 |
| - }; |
379 | 195 |
|
380 | 196 |
|
381 | 197 | //! This method lets you wrap any other function following the "submit on overflow" pattern with the final submission
|
|
0 commit comments