Skip to content

Commit 8a39eb1

Browse files
committed
Merge branch 'asset-conversion-v3' into cad_large_texture_streaming
2 parents 3d28442 + fb7f033 commit 8a39eb1

File tree

1 file changed

+85
-75
lines changed

1 file changed

+85
-75
lines changed

include/nbl/video/utilities/SIntendedSubmitInfo.h

Lines changed: 85 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -17,101 +17,111 @@ namespace nbl::video
1717
//! The "current recording commandBuffer" is returned by `getCurrentRecordingCommandBufferInfo` or updated via the pointer ref in beginNextCommandBuffer or overflowSubmit)
1818
struct SIntendedSubmitInfo final : core::Uncopyable
1919
{
20-
public:
21-
// This parameter is required but may be unused if there is no need (no overflow) to do submit
22-
IQueue* queue = nullptr;
23-
// Use this parameter to wait for previous operations to finish before whatever commands the Utility you're using records
24-
std::span<const IQueue::SSubmitInfo::SSemaphoreInfo> waitSemaphores = {};
25-
// Fill the commandbuffers you want to run before the first command the Utility records to run in the same submit,
26-
// for example baked command buffers with pipeline barrier commands.
27-
std::span<const IQueue::SSubmitInfo::SCommandBufferInfo> prevCommandBuffers = {};
28-
// A set of command buffers the Utility can round robin its transient commands. All must be individually resettable.
29-
// Command buffers are cycled through for use and submission in a simple modular arithmetic fashion.
30-
// EXACTLY ONE commandbuffer must be in recording state! This is the one the utility will use immediately to record commands.
31-
// But remember that even though its scratch, you can record some of your own preceeding commands into it as well.
32-
std::span<IQueue::SSubmitInfo::SCommandBufferInfo> scratchCommandBuffers = {};
33-
// This semaphore is needed to ensure correct and valid usage of the command buffer used to record the next submit by ensuring they are not currently pending.
34-
// The initial `scratchSemaphore.value` gets incremented and signalled on each submit, can start at 0 because an overflow will signal `value+1`.
35-
// `[Multi]TimelineEventHandler` to latch cleanups on `scratchSemaphore` where this value, so make sure you're submitting by yourself manually and use the functions provided here.
36-
// is a value that they expect to actually get signalled in the future.
37-
// NOTE: To ensure deterministic behavior, do not attempt to signal this semaphore yourself.
38-
// You can actually examine the change in `scratchSemaphore.value` to figure out how many submits occurred.
39-
IQueue::SSubmitInfo::SSemaphoreInfo scratchSemaphore = {};
40-
// Optional: If you had a semaphore whose highest pending signal is 45 but gave the scratch a value of 68 (causing 69 to get signalled in a popped submit),
41-
// but only used 4 scratch command buffers, we'd wait for the semaphore to reach 66 before resetting the next scratch command buffer.
42-
// That is obviously suboptimal if the next scratch command buffer wasn't pending with a signal of 67 at all (you'd wait until 70 gets signalled).
43-
// Therefore you would need to override this behaviour somehow and be able to tell to only wait for the semaphore at values higher than 68.
44-
size_t initialScratchValue = 0;
45-
// Optional: Callback to perform some other CPU work while blocking for one of the submitted scratch command buffers to complete execution.
46-
// Can get called repeatedly! The argument is the scratch semaphore (so it can poll itself to know when to finish work - prevent priority inversion)
47-
std::function<void(const ISemaphore::SWaitInfo&)> overflowCallback = {};
48-
49-
//
50-
inline ISemaphore::SWaitInfo getFutureScratchSemaphore() const {return {scratchSemaphore.semaphore,scratchSemaphore.value+1};}
51-
52-
// Returns the command buffer to use for recording if valid, nullptr otherwise
53-
inline const IQueue::SSubmitInfo::SCommandBufferInfo* getCurrentRecordingCommandBufferInfo() const
20+
// All commandbuffers must be compatible with the queue we're about to submit to
21+
bool cmdbufNotSubmittableToQueue(const IGPUCommandBuffer* cmdbuf) const
5422
{
55-
if (scratchCommandBuffers.empty())
56-
return nullptr;
23+
return !cmdbuf || cmdbuf->getPool()->getQueueFamilyIndex()!=queue->getFamilyIndex();
24+
}
5725

58-
// All commandbuffers must be compatible with the queue we're about to submit to
59-
auto cmdbufNotSubmittableToQueue = [this](const IGPUCommandBuffer* cmdbuf)->bool
26+
// Returns the scratch to use if valid, nullptr otherwise
27+
template<bool AllChecks>
28+
inline const IQueue::SSubmitInfo::SCommandBufferInfo* valid_impl() const
29+
{
30+
if (!queue || scratchCommandBuffers.empty() || !scratchSemaphore.semaphore)
31+
return nullptr;
32+
// the found scratch
33+
const IQueue::SSubmitInfo::SCommandBufferInfo* scratch = nullptr;
34+
// skip expensive stuff
35+
std::conditional_t<AllChecks,core::unordered_set<const IGPUCommandBuffer*>,const void*> uniqueCmdBufs;
36+
if constexpr (AllChecks)
6037
{
61-
return !cmdbuf || cmdbuf->getPool()->getQueueFamilyIndex()!=queue->getFamilyIndex();
62-
};
63-
64-
// finding command buffer toRecord
65-
const IQueue::SSubmitInfo::SCommandBufferInfo* toRecord = nullptr;
66-
core::unordered_set<const IGPUCommandBuffer*> uniqueCmdBufs;
67-
uniqueCmdBufs.reserve(scratchCommandBuffers.size());
38+
// All commandbuffers before the scratch must be executable (ready to be submitted)
39+
for (const auto& info : prevCommandBuffers)
40+
if (cmdbufNotSubmittableToQueue(info.cmdbuf) || info.cmdbuf->getState()!=IGPUCommandBuffer::STATE::EXECUTABLE)
41+
return nullptr;
42+
//
43+
uniqueCmdBufs.reserve(scratchCommandBuffers.size());
44+
}
6845
for (auto& info : scratchCommandBuffers)
6946
{
70-
// Must be resettable so we can end, submit, wait, reset and continue recording commands into it as-if nothing happened
71-
if (cmdbufNotSubmittableToQueue(info.cmdbuf) || !info.cmdbuf->isResettable())
72-
return nullptr;
73-
uniqueCmdBufs.insert(info.cmdbuf);
74-
// not our toRecord
47+
if constexpr (AllChecks)
48+
{
49+
// Must be resettable so we can end, submit, wait, reset and continue recording commands into it as-if nothing happened
50+
if (cmdbufNotSubmittableToQueue(info.cmdbuf) || !info.cmdbuf->isResettable())
51+
return nullptr;
52+
uniqueCmdBufs.insert(info.cmdbuf);
53+
}
54+
// not our scratch
7555
if (info.cmdbuf->getState()!=IGPUCommandBuffer::STATE::RECORDING)
7656
continue;
77-
// there can only be one toRecord!
78-
if (toRecord)
57+
// there can only be one scratch!
58+
if (scratch)
7959
return nullptr;
80-
toRecord = &info;
60+
scratch = &info;
8161
}
8262
// a commandbuffer repeats itself
63+
if constexpr (AllChecks)
8364
if (uniqueCmdBufs.size()!=scratchCommandBuffers.size())
8465
return nullptr;
85-
// there is no toRecord cmdbuf at all!
86-
if (!toRecord)
66+
// there is no scratch cmdbuf at all!
67+
if (!scratch)
8768
return nullptr;
8869
// It makes no sense to reuse the same commands for a second submission.
8970
// Moreover its dangerous because the utilities record their own internal commands which might use subresources for which
9071
// frees have already been latched on the scratch semaphore you must signal anyway.
91-
if (!toRecord->cmdbuf->getRecordingFlags().hasFlags(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT))
72+
if (!scratch->cmdbuf->getRecordingFlags().hasFlags(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT))
9273
return nullptr;
93-
return toRecord;
74+
return scratch;
9475
}
95-
96-
// Returns the command buffer to use for recording if valid, nullptr otherwise
97-
inline const IQueue::SSubmitInfo::SCommandBufferInfo* valid() const
98-
{
99-
if (!queue || !scratchSemaphore.semaphore)
100-
return nullptr;
10176

102-
// All commandbuffers must be compatible with the queue we're about to submit to
103-
auto cmdbufNotSubmittableToQueue = [this](const IGPUCommandBuffer* cmdbuf)->bool
104-
{
105-
return !cmdbuf || cmdbuf->getPool()->getQueueFamilyIndex()!=queue->getFamilyIndex();
106-
};
77+
public:
78+
// This parameter is required but may be unused if there is no need (no overflow) to perform submit operations (no call to `overflowSubmit` or `submit`)
79+
IQueue* queue = nullptr;
10780

108-
// All prevCommandBuffers must be executable (ready to be submitted)
109-
for (const auto& info : prevCommandBuffers)
110-
if (cmdbufNotSubmittableToQueue(info.cmdbuf) || info.cmdbuf->getState()!=IGPUCommandBuffer::STATE::EXECUTABLE)
111-
return nullptr;
81+
// Use this parameter to wait for previous operations to finish before whatever commands the Utility you're using records.
82+
std::span<const IQueue::SSubmitInfo::SSemaphoreInfo> waitSemaphores = {};
11283

113-
return getCurrentRecordingCommandBufferInfo();
114-
}
84+
// Fill the commandbuffers you want to run before the first command the Utility records to run in the same submit,
85+
// for example, baked command buffers with pipeline barrier commands.
86+
std::span<const IQueue::SSubmitInfo::SCommandBufferInfo> prevCommandBuffers = {};
87+
88+
// A set of command buffers the Utility can use in a round-robin manner for its transient commands. All command buffers must be individually resettable.
89+
// Command buffers are cycled through for use and submission using a simple modular arithmetic fashion.
90+
// EXACTLY ONE commandbuffer must be in recording state! This is the one the utilities will use immediately to record commands.
91+
// However, even though it's scratch, you can record some of your own preceding commands into it as well.
92+
std::span<IQueue::SSubmitInfo::SCommandBufferInfo> scratchCommandBuffers = {};
93+
94+
// This semaphore is needed to indicate when each sub-submit is complete and resources can be reclaimed.
95+
// It also ensures safe usage of the scratch command buffers by blocking the CPU if the next scratch command buffer to use is in a PENDING state and not safe to begin.
96+
// The initial `scratchSemaphore.value` gets incremented and signaled on each submit. It can start at 0 because an overflow will signal `value+1`.
97+
// NOTE: You should signal this semaphore when doing your last/tail submit manually OR when not submitting the recorded scratch at all
98+
// (in which case, signal from Host or another submit). Why? The utilities that deal with `SIntendedSubmitInfo` might use the
99+
// `[Multi]TimelineEventHandler` to latch cleanups/deallocations on `scratchSemaphore`, and `getFutureScratchSemaphore()`
100+
// is the value they expect to get signaled in the future.
101+
// NOTE: Each overflow submit bumps the value to wait and signal by +1. A utility may overflow an arbitrary number of times.
102+
// Therefore, DO NOT choose the values for waits and signals manually, and do not modify the `scratchSemaphore` field after initialization or first use.
103+
// You can examine the change in `scratchSemaphore.value` to observe how many overflows or submits have occurred.
104+
IQueue::SSubmitInfo::SSemaphoreInfo scratchSemaphore = {};
105+
106+
// Optional: If you had a semaphore whose highest pending signal is 45 but gave the scratch a value of 68 (causing 69 to get signaled in a popped submit),
107+
// but only used 4 scratch command buffers, we'd wait for the semaphore to reach 66 before resetting the next scratch command buffer.
108+
// This is obviously suboptimal if the next scratch command buffer wasn't pending with a signal of 67 at all (you'd be waiting until 70 gets signaled).
109+
// Therefore, you would need to override this behavior to only wait for semaphore values higher than 68.
110+
size_t initialScratchValue = 0;
111+
112+
// Optional: Callback to perform some other CPU work while blocking for one of the submitted scratch command buffers to complete execution.
113+
// This callback may be called repeatedly! The argument provided is the scratch semaphore, allowing it to poll itself to determine when it can finish its work,
114+
// preventing priority inversion.
115+
std::function<void(const ISemaphore::SWaitInfo&)> overflowCallback = {};
116+
117+
//
118+
inline ISemaphore::SWaitInfo getFutureScratchSemaphore() const {return {scratchSemaphore.semaphore,scratchSemaphore.value+1};}
119+
120+
//
121+
inline const auto* getCommandBufferForRecording() const {return valid_impl<false>();}
122+
123+
//
124+
inline const auto* valid() const {return valid_impl<true>();}
115125

116126
//! xxxx
117127
class CSubmitStorage final : core::Uncopyable
@@ -164,7 +174,7 @@ struct SIntendedSubmitInfo final : core::Uncopyable
164174
// - clear the `waitSemaphores` which we'll use in the future because they will already be awaited on this `queue`
165175
inline CSubmitStorage popSubmit(IGPUCommandBuffer* recordingCmdBuf, const std::span<const IQueue::SSubmitInfo::SSemaphoreInfo> signalSemaphores)
166176
{
167-
assert(scratch);
177+
assert(recordingCmdBuf);
168178
CSubmitStorage retval(*this,recordingCmdBuf,signalSemaphores);
169179

170180
// If you want to wait for the result of this popped submit, you need to wait for this new value

0 commit comments

Comments
 (0)