Skip to content

Commit 03bb892

Browse files
move the SIntendedSubmitInfo struct out of IUtilities
1 parent 2558fd8 commit 03bb892

File tree

3 files changed

+198
-225
lines changed

3 files changed

+198
-225
lines changed

include/nbl/video/utilities/IUtilities.h

Lines changed: 1 addition & 185 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,9 @@
77
#include "nbl/asset/asset.h"
88
#include "nbl/asset/utils/ISPIRVOptimizer.h"
99

10-
#include "nbl/video/IGPUBuffer.h"
11-
#include "nbl/video/IGPUImage.h"
12-
#include "nbl/video/ILogicalDevice.h"
1310
#include "nbl/video/IPhysicalDevice.h"
1411
#include "nbl/video/alloc/StreamingTransientDataBuffer.h"
12+
#include "nbl/video/utilities/SIntendedSubmitInfo.h"
1513
#include "nbl/video/utilities/CPropertyPoolHandler.h"
1614
#include "nbl/video/utilities/CScanner.h"
1715
#include "nbl/video/utilities/CComputeBlit.h"
@@ -194,188 +192,6 @@ class NBL_API2 IUtilities : public core::IReferenceCounted
194192
));
195193
return allocationSize;
196194
}
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-
};
379195

380196

381197
//! This method lets you wrap any other function following the "submit on overflow" pattern with the final submission

0 commit comments

Comments
 (0)