Skip to content

Commit f114c95

Browse files
design clearing up
1 parent 6c48e48 commit f114c95

File tree

1 file changed

+81
-74
lines changed

1 file changed

+81
-74
lines changed

include/nbl/video/utilities/IUtilities.h

Lines changed: 81 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -195,20 +195,7 @@ class NBL_API2 IUtilities : public core::IReferenceCounted
195195
return allocationSize;
196196
}
197197

198-
struct SFrontHalfSubmitInfo final
199-
{
200-
inline bool valid() const {return queue;}
201-
202-
// Use the last command buffer in intendedNextSubmit, it should be in recording state
203-
inline IGPUCommandBuffer* getScratchCommandBuffer() {return commandBuffers.empty() ? nullptr:commandBuffers.back().cmdbuf;}
204-
inline const IGPUCommandBuffer* getScratchCommandBuffer() const {return commandBuffers.empty() ? nullptr:commandBuffers.back().cmdbuf;}
205-
206-
207-
IQueue* queue = {};
208-
std::span<const IQueue::SSubmitInfo::SSemaphoreInfo> waitSemaphores = {};
209-
std::span<IQueue::SSubmitInfo::SCommandBufferInfo> commandBuffers = {};
210-
};
211-
//! Struct meant to be used with any utility (not just `IUtilities`) which exhibits "submit on overflow" behaviour.
198+
//! Struct meant to be used with any Utility (not just `IUtilities`) which exhibits "submit on overflow" behaviour.
212199
//! Such functions are non-blocking (unless overflow) and take `SIntendedSubmitInfo` by reference and patch it accordingly.
213200
//! MAKE SURE to do a submit to `queue` by yourself with a submit info obtained by casting `this` to `IQueue::SSubmitInfo` !
214201
//! for example: in the case the `frontHalf.waitSemaphores` were already waited upon, the struct will be modified to have it's `waitSemaphores` emptied.
@@ -219,8 +206,12 @@ class NBL_API2 IUtilities : public core::IReferenceCounted
219206
{
220207
if (!frontHalf.valid() || frontHalf.commandBuffers.empty() || signalSemaphores.empty())
221208
return false;
209+
// Must be resettable so we can end, submit, wait, reset and continue recording commands into it as-if nothing happened
222210
if (!frontHalf.getScratchCommandBuffer()->isResettable())
223211
return false;
212+
// It makes no sense to reuse the same commands for a second submission.
213+
// Moreover its dangerous because the utilities record their own internal commands which might use subresources for which
214+
// frees have already been latched on the scratch semaphore you must signal anyway.
224215
if (!frontHalf.getScratchCommandBuffer()->getRecordingFlags().hasFlags(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT))
225216
return false;
226217
return true;
@@ -263,9 +254,32 @@ class NBL_API2 IUtilities : public core::IReferenceCounted
263254
}
264255

265256

266-
//! The last CommandBuffer will be used to record the copy commands
267-
SFrontHalfSubmitInfo frontHalf = {};
268-
//! The first Semaphore will be used as a scratch, so don't use it yourself as we can advance the counter an arbitrary amount!
257+
//! The last CommandBuffer will be used to record the copy commands
258+
struct SFrontHalf final
259+
{
260+
//! We can't check it, but all (if any) all the command buffers except the last one should be in `EXECUTABLE` state.
261+
inline bool valid() const {return queue;}
262+
263+
// Use the last command buffer in intendedNextSubmit, it should be in recording state
264+
inline IGPUCommandBuffer* getScratchCommandBuffer() {return commandBuffers.empty() ? nullptr:commandBuffers.back().cmdbuf;}
265+
inline const IGPUCommandBuffer* getScratchCommandBuffer() const {return commandBuffers.empty() ? nullptr:commandBuffers.back().cmdbuf;}
266+
267+
// This parameter is required but may be unused if there is no need to submit
268+
IQueue* queue = {};
269+
// Use this parameter to wait for previous operations to finish before whatever commands the Utility you're using records
270+
std::span<const IQueue::SSubmitInfo::SSemaphoreInfo> waitSemaphores = {};
271+
// Fill the commandbuffers you want to run before the first command the Utility records to run in the same submit,
272+
// for example baked command buffers with pipeline barrier commands.
273+
// ....
274+
std::span<IQueue::SSubmitInfo::SCommandBufferInfo> commandBuffers = {};
275+
} frontHalf = {};
276+
//! 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!
277+
//! You can actually examine the change in `signalSemaphore.front().value` to figure out how many overflows occurred.
278+
//! This semaphore is needed to "stitch together" additional submits if they occur so they occur before and after the original intended waits and signals.
279+
//! We use the first semaphore to keep the intended order of original semaphore signal and waits unchanged no matter how many overflows occur.
280+
//! 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
281+
//! after the submit is done, indicating that your streaming routine is done.
282+
//! * Also use this parameter to signal new semaphores so that other submits know your Utility method is done.
269283
std::span<IQueue::SSubmitInfo::SSemaphoreInfo> signalSemaphores = {};
270284

271285
private:
@@ -280,55 +294,34 @@ class NBL_API2 IUtilities : public core::IReferenceCounted
280294
//! Copies `data` to stagingBuffer and Records the commands needed to copy the data from stagingBuffer to `bufferRange.buffer`
281295
//! If the allocation from staging memory fails due to large buffer size or fragmentation then This function may need to submit the command buffer via the `submissionQueue`.
282296
//! Returns:
283-
//! the number of times we overflown and had to submit, <0 [negative] on failure
297+
//! True on successful recording of copy commands and handling of overflows, false on failure for any reason.
284298
//! Parameters:
285299
//! - nextSubmit:
286300
//! Is the SubmitInfo you intended to submit your command buffers with, it will be patched if overflow occurred @see SIntendedSubmitInfo
287301
//! - bufferRange: contains offset + size into bufferRange::buffer that will be copied from `data` (offset doesn't affect how `data` is accessed)
288302
//! - data: raw pointer to data that will be copied to bufferRange::buffer
289-
//! - submissionQueue: IQueue used to submit, when needed.
290-
//! Note: This parameter is required but may not be used if there is no need to submit
291-
//! - scratchSemaphore:
292-
//! - since you've already decided on the semaphores you'll wait and signal in the `intendedNextSubmit`, we need an extra semaphore to "stich together" the submit if we split it
293-
294-
295-
//! - This is the fence you will use to submit the copies to, this allows freeing up space in stagingBuffer when the fence is signalled, indicating that the copy has finished.
296-
//! - This fence will be in `UNSIGNALED` state after exiting the function. (It will reset after each implicit submit)
297-
//! - This fence may be used for CommandBuffer submissions using `submissionQueue` inside the function.
298-
//! ** NOTE: This fence will be signalled everytime there is a submission inside this function, which may be more than one until the job is finished.
299303
//! Valid Usage:
304+
//! * nextSubmit must be valid (see `SIntendedSubmitInfo::valid()`)
305+
//! * bufferRange must be valid (see `SBufferRange::isValid()`)
300306
//! * data must not be nullptr
301-
//! * bufferRange should be valid (see SBufferRange::isValid())
302-
//! * intendedNextSubmit::commandBufferCount must be > 0
303-
//! * The commandBuffers should have been allocated from a CommandPool with the same queueFamilyIndex as `submissionQueue`
304-
//! * The last command buffer should be in `RECORDING` state.
305-
//! * The last command buffer should be must've called "begin()" with `IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT` flag
306-
//! The reason is the commands recorded into the command buffer would not be valid for a second submission and the stagingBuffer memory wouldv'e been freed/changed.
307-
//! * The last command buffer should be "resettable". See `ICommandBuffer::E_STATE` comments
308-
//! * To ensure correct execution order, (if any) all the command buffers except the last one should be in `EXECUTABLE` state.
309-
//! * submissionQueue must point to a valid IQueue
310-
//! * submissionFence must point to a valid IGPUFence
311-
//! * submissionFence must be in `UNSIGNALED` state
312-
//! ** IUtility::getDefaultUpStreamingBuffer()->cull_frees() should be called before reseting the submissionFence and after fence is signaled.
313-
inline int64_t updateBufferRangeViaStagingBuffer(SIntendedSubmitInfo& nextSubmit, const asset::SBufferRange<IGPUBuffer>& bufferRange, const void* data)
307+
inline bool updateBufferRangeViaStagingBuffer(SIntendedSubmitInfo& nextSubmit, const asset::SBufferRange<IGPUBuffer>& bufferRange, const void* data)
314308
{
315309
if (!bufferRange.isValid() || !bufferRange.buffer->getCreationParams().usage.hasFlags(asset::IBuffer::EUF_TRANSFER_DST_BIT))
316310
{
317311
m_logger.log("Invalid `bufferRange` or buffer has no `EUF_TRANSFER_DST_BIT` usage flag, cannot `updateBufferRangeViaStagingBuffer`!", system::ILogger::ELL_ERROR);
318-
return -1;
312+
return false;
319313
}
320314

321315
if (!nextSubmit.valid())
322316
{
323317
m_logger.log(nextSubmit.ErrorText,system::ILogger::ELL_ERROR);
324-
return -1;
318+
return false;
325319
}
326320

327321
const auto& limits = m_device->getPhysicalDevice()->getLimits();
328322
const uint32_t optimalTransferAtom = limits.maxResidentInvocations * sizeof(uint32_t);
329323

330324
auto cmdbuf = nextSubmit.frontHalf.getScratchCommandBuffer();
331-
int64_t overflowCounter = 0;
332325
// no pipeline barriers necessary because write and optional flush happens before submit, and memory allocation is reclaimed after fence signal
333326
for (size_t uploadedSize=0ull; uploadedSize<bufferRange.size;)
334327
{
@@ -353,7 +346,6 @@ class NBL_API2 IUtilities : public core::IReferenceCounted
353346
if (localOffset == StreamingTransientDataBufferMT<>::invalid_value)
354347
{
355348
nextSubmit.overflowSubmit();
356-
overflowCounter++;
357349
continue;
358350
}
359351
// some platforms expose non-coherent host-visible GPU memory, so writes need to be flushed explicitly
@@ -372,18 +364,51 @@ class NBL_API2 IUtilities : public core::IReferenceCounted
372364
m_defaultUploadBuffer.get()->multi_deallocate(1u,&localOffset,&allocationSize,nextSubmit.getScratchSemaphoreNextWait(),&cmdbuf);
373365
uploadedSize += subSize;
374366
}
375-
return overflowCounter;
367+
return true;
376368
}
377369

378-
//! This function is an specialization of the `updateBufferRangeViaStagingBuffer` function above.
379-
//! Submission of the commandBuffer to submissionQueue happens automatically, no need for the user to handle submit
370+
//! This method lets you wrap any other function following the "submit on overflow" pattern with the final submission
371+
//! to `intendedSubmit.queue` happening automatically, no need for the user to handle the submit at the end.
380372
//! WARNING: Don't use this function in hot loops or to do batch updates, its merely a convenience for one-off uploads
373+
//! of the `updateBufferRangeViaStagingBufferAutoSubmit` function above.
381374
//! Parameters:
382-
//! - `submitInfo`: IQueue::SSubmitInfo used to submit the copy operations.
383-
//! * Use this parameter to wait for previous operations to finish using submitInfo::waitSemaphores or signal new semaphores using submitInfo::signalSemaphores
384-
//! * Fill submitInfo::commandBuffers with the commandbuffers you want to be submitted before the copy in this struct as well, for example pipeline barrier commands.
385-
//! * Empty by default: waits for no semaphore and signals no semaphores.
386-
//! Patches the submitInfo::commandBuffers
375+
//! - `intendedSubmit`: more lax than regular `SIntendedSubmitInfo::valid()`, only needs a valid queue and at least one semaphore to signal (how else will you know you're done?)
376+
//! since the submit must and will happen, there's no point updating the semaphore and commandbuffer info spans in the intendedSubmit
377+
inline bool autoSubmit(const SIntendedSubmitInfo& intendedSubmit, const std::function<bool(SIntendedSubmitInfo&)>& what)
378+
{
379+
if (!intendedSubmit.frontHalf.valid() || intendedSubmit.signalSemaphores.empty())
380+
{
381+
// TODO: log error
382+
return false;
383+
}
384+
385+
SIntendedSubmitInfo patchedSubmit = intendedSubmit;
386+
if (!what(patchedSubmit))
387+
return false;
388+
const IQueue::SSubmitInfo submit = patchedSubmit;
389+
return intendedSubmit.frontHalf.queue->submit({&submit,1});
390+
}
391+
392+
//! This function is an specialization of the `autoSubmit` function above, it will additionally wait on the Host (CPU) for the final submit to finish.
393+
//! WARNING: This function blocks CPU and stalls the GPU!
394+
inline bool autoSubmitAndBlock(const SIntendedSubmitInfo::SFrontHalf& submit, const std::function<bool(SIntendedSubmitInfo&)>& what)
395+
{
396+
auto semaphore = m_device->createSemaphore(0);
397+
// so we begin latching everything on the value of 1, but if we overflow it increases
398+
IQueue::SSubmitInfo::SSemaphoreInfo info = {semaphore.get(),1};
399+
400+
SIntendedSubmitInfo intendedSubmit = {.frontHalf=submit,.signalSemaphores={&info,1}};
401+
if (!autoSubmit(intendedSubmit,what))
402+
return false;
403+
404+
// Watch carefully and note that we might not be waiting on the value of `1` for why @see `SIntendedSubmitInfo::signalSemaphores`
405+
const ISemaphore::SWaitInfo waitInfo = {info.semaphore,info.value};
406+
m_device->blockForSemaphores({&waitInfo,1});
407+
return true;
408+
}
409+
410+
#if 0
411+
//! Patches the intendedSubmit::frontHalf::commandBuffers
387412
//! If submitInfo::commandBufferCount == 0, it will create an implicit command buffer to use for recording copy commands
388413
//! If submitInfo::commandBufferCount > 0 the last command buffer is in `EXECUTABLE` state, It will add another temporary command buffer to end of the array and use it for recording and submission
389414
//! If submitInfo::commandBufferCount > 0 the last command buffer is in `RECORDING` or `INITIAL` state, It won't add another command buffer and uses the last command buffer for the copy commands.
@@ -408,34 +433,16 @@ class NBL_API2 IUtilities : public core::IReferenceCounted
408433
submissionQueue->submit(1u,&submitInfo,submissionFence);
409434
return true;
410435
}
411-
412-
//! This function is an specialization of the `updateBufferRangeViaStagingBufferAutoSubmit` function above.
413-
//! Additionally waits for the upload right away
414-
//! WARNING: This function blocks CPU and stalls the GPU!
415-
inline bool updateBufferRangeViaStagingBufferAutoSubmit(const SFrontHalfSubmitInfo& submit, const asset::SBufferRange<IGPUBuffer>& bufferRange, const void* data)
416-
{
417-
if(!submit.valid())
418-
{
419-
// TODO: log error
420-
return false;
421-
}
422-
423-
auto semaphore = m_device->createSemaphore(0);
424-
if (!updateBufferRangeViaStagingBufferAutoSubmit(,bufferRange,data))
425-
return false;
426-
const ISemaphore::SWaitInfo info = {semaphore.get(),1};
427-
m_device->blockForSemaphores({&info,1});
428-
return true;
429-
}
436+
#endif
430437

431438
//! WARNING: This function blocks the CPU and stalls the GPU!
432-
inline core::smart_refctd_ptr<IGPUBuffer> createFilledDeviceLocalBufferOnDedMem(const SFrontHalfSubmitInfo& submit, IGPUBuffer::SCreationParams&& params, const void* data)
439+
inline core::smart_refctd_ptr<IGPUBuffer> createFilledDeviceLocalBufferOnDedMem(const SIntendedSubmitInfo::SFrontHalf& submit, IGPUBuffer::SCreationParams&& params, const void* data)
433440
{
434441
auto buffer = m_device->createBuffer(std::move(params));
435442
auto mreqs = buffer->getMemoryReqs();
436443
mreqs.memoryTypeBits &= m_device->getPhysicalDevice()->getDeviceLocalMemoryTypeBits();
437444
auto mem = m_device->allocate(mreqs,buffer.get());
438-
if (!updateBufferRangeViaStagingBufferAutoSubmit(submit,asset::SBufferRange<IGPUBuffer>{0u,params.size,core::smart_refctd_ptr(buffer)},data))
445+
if (!autoSubmitAndBlock(submit,[&](auto& info){return updateBufferRangeViaStagingBuffer(info,asset::SBufferRange<IGPUBuffer>{0u,params.size,core::smart_refctd_ptr(buffer)},data);}))
439446
return nullptr;
440447
return buffer;
441448
}

0 commit comments

Comments
 (0)