Simplify and improve performance of MTLCommandBuffer completion handling.
Manage MTLCommandBuffer in MVKQueueCommandBufferSubmission instead of MVKQueue. Remove MVKMTLCommandBufferCountdown and its use in MVKQueue.
This commit is contained in:
parent
f2cd295b18
commit
26058daba3
@ -438,64 +438,6 @@ protected:
|
||||
};
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark MVKMTLCommandBufferCountdown
|
||||
|
||||
/**
|
||||
* Abstract class that can be initialized with the number of active MTLCommandBuffers and the
|
||||
* ID of the MTLCommandBuffer after those tracked by this countdown, counts down as each earlier
|
||||
* active MTLCommandBuffer completes, and takes action when the countdown reaches zero.
|
||||
*
|
||||
* Subclasses must override the finish() member function to perform the action
|
||||
* that is to be taken upon completion of the countdown.
|
||||
*
|
||||
* This class is not thread-safe. When using this class with multiple threads,
|
||||
* you must ensure that operations that change the count value are guarded.
|
||||
*/
|
||||
class MVKMTLCommandBufferCountdown : public MVKBaseObject {
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Sets the number of active MTLCommandBuffers and the ID of the next MTLCommandBuffer
|
||||
* after those tracked by this countdown. This countdown is interested in MTLCommandBuffers
|
||||
* whose ID's are less than the specified ID.
|
||||
*
|
||||
* If the count is zero, the finish() member function is called.
|
||||
*
|
||||
* Returns whether the count is zero. If this function returns true, it is possible
|
||||
* that this intance has completed and has been destroyed. No further references should be
|
||||
* made to this instance.
|
||||
*/
|
||||
bool setActiveMTLCommandBufferCount(uint32_t count, MVKMTLCommandBufferID mtlCmdBuffID);
|
||||
|
||||
/**
|
||||
* Called when the MTLCommandBuffer with the specified ID has completed. If the specified
|
||||
* ID is less than the ID registered via the setActiveMTLCommandBufferCount() function,
|
||||
* the count of active MTLCommandBuffers is decremented. If the count is zero, the finish()
|
||||
* member function is called.
|
||||
*
|
||||
* Returns whether the count is now at zero. If this function returns true, it is possible
|
||||
* that this intance has completed and has been destroyed. No further references should be
|
||||
* made to this instance.
|
||||
*/
|
||||
bool mtlCommandBufferHasCompleted(MVKMTLCommandBufferID mtlCmdBuffID);
|
||||
|
||||
/** Returns the current count value. */
|
||||
uint32_t getCount();
|
||||
|
||||
protected:
|
||||
|
||||
/** Performs the action to take when the count has reached zero. */
|
||||
virtual void finish() = 0;
|
||||
|
||||
bool checkFinished();
|
||||
|
||||
uint32_t _activeMTLCommandBufferCount;
|
||||
MVKMTLCommandBufferID _maxMTLCmdBuffID;
|
||||
};
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Support functions
|
||||
|
||||
|
@ -526,35 +526,6 @@ MVKCommandEncoder::MVKCommandEncoder(MVKCommandBuffer* cmdBuffer,
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark MVKMTLCommandBufferCountdown
|
||||
|
||||
bool MVKMTLCommandBufferCountdown::setActiveMTLCommandBufferCount(uint32_t count,
|
||||
MVKMTLCommandBufferID mtlCmdBuffID) {
|
||||
_activeMTLCommandBufferCount = count;
|
||||
_maxMTLCmdBuffID = mtlCmdBuffID;
|
||||
|
||||
return checkFinished();
|
||||
}
|
||||
|
||||
bool MVKMTLCommandBufferCountdown::mtlCommandBufferHasCompleted(MVKMTLCommandBufferID mtlCmdBuffID) {
|
||||
if ( (_activeMTLCommandBufferCount > 0) && (mtlCmdBuffID < _maxMTLCmdBuffID) ) {
|
||||
_activeMTLCommandBufferCount--;
|
||||
}
|
||||
return checkFinished();
|
||||
}
|
||||
|
||||
// If the count of active MTLCommandBuffers is zero, calls the finish() member function.
|
||||
// Returns whether the count is now at zero.
|
||||
bool MVKMTLCommandBufferCountdown::checkFinished() {
|
||||
bool isDone = (_activeMTLCommandBufferCount == 0);
|
||||
if (isDone) { finish(); }
|
||||
return isDone;
|
||||
}
|
||||
|
||||
uint32_t MVKMTLCommandBufferCountdown::getCount() { return _activeMTLCommandBufferCount; }
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Support functions
|
||||
|
||||
|
@ -87,29 +87,6 @@ public:
|
||||
/** Block the current thread until this queue is idle. */
|
||||
VkResult waitIdle(MVKCommandUse cmdBuffUse);
|
||||
|
||||
/**
|
||||
* Retrieves a MTLCommandBuffer instance from the contained MTLCommandQueue, adds a
|
||||
* completion handler to it so that the mtlCommandBufferHasCompleted() function will
|
||||
* be called when the MTLCommandBuffer completes, and returns the MTLCommandBuffer.
|
||||
*/
|
||||
id<MTLCommandBuffer> makeMTLCommandBuffer(NSString* mtlCmdBuffLabel);
|
||||
|
||||
/** Called automatically when the specified MTLCommandBuffer with the specified ID has completed. */
|
||||
void mtlCommandBufferHasCompleted(id<MTLCommandBuffer> mtlCmdBuff, MVKMTLCommandBufferID mtlCmdBuffID);
|
||||
|
||||
/**
|
||||
* Registers the specified countdown object. This function sets the count value
|
||||
* of the countdown object to the current number of incomplete MTLCommandBuffers,
|
||||
* and marks the countdown object with the ID of the most recently registered
|
||||
* MTLCommandBuffer. The countdown object will be decremented each time any
|
||||
* MTLCommandBuffer with an ID less than the ID of the most recent MTLCommandBuffer
|
||||
* at the time the countdown object was registered.
|
||||
*
|
||||
* If the current number of incomplete MTLCommandBuffers is zero, the countdown
|
||||
* object will indicate that it is already completed, and will not be registered.
|
||||
*/
|
||||
void registerMTLCommandBufferCountdown(MVKMTLCommandBufferCountdown* countdown);
|
||||
|
||||
/** Returns the command encoding pool. */
|
||||
inline MVKCommandEncodingPool* getCommandEncodingPool() { return &_commandEncodingPool; }
|
||||
|
||||
@ -161,9 +138,6 @@ protected:
|
||||
dispatch_queue_t _execQueue;
|
||||
id<MTLCommandQueue> _mtlQueue;
|
||||
std::string _name;
|
||||
std::vector<MVKMTLCommandBufferCountdown*> _completionCountdowns;
|
||||
std::mutex _completionLock;
|
||||
uint32_t _activeMTLCommandBufferCount;
|
||||
MVKMTLCommandBufferID _nextMTLCmdBuffID;
|
||||
MVKCommandEncodingPool _commandEncodingPool;
|
||||
MVKGPUCaptureScope* _submissionCaptureScope;
|
||||
@ -171,26 +145,6 @@ protected:
|
||||
};
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark MVKQueueCommandBufferSubmissionCountdown
|
||||
|
||||
/** Counts down MTLCommandBuffers on behalf of an MVKQueueCommandBufferSubmission instance. */
|
||||
class MVKQueueCommandBufferSubmissionCountdown : public MVKMTLCommandBufferCountdown {
|
||||
|
||||
public:
|
||||
|
||||
/** Constructs an instance. */
|
||||
MVKQueueCommandBufferSubmissionCountdown(MVKQueueCommandBufferSubmission* qSub);
|
||||
|
||||
protected:
|
||||
|
||||
/** Performs the action to take when the count has reached zero. */
|
||||
virtual void finish();
|
||||
|
||||
MVKQueueCommandBufferSubmission* _qSub;
|
||||
};
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark MVKQueueSubmission
|
||||
|
||||
@ -247,7 +201,7 @@ public:
|
||||
id<MTLCommandBuffer> getActiveMTLCommandBuffer();
|
||||
|
||||
/** Commits and releases the currently active MTLCommandBuffer. */
|
||||
void commitActiveMTLCommandBuffer();
|
||||
void commitActiveMTLCommandBuffer(bool signalCompletion = false);
|
||||
|
||||
/**
|
||||
* Constructs an instance for the device and queue.
|
||||
@ -265,9 +219,6 @@ public:
|
||||
protected:
|
||||
friend MVKCommandEncoder;
|
||||
|
||||
NSString* getMTLCommandBufferName();
|
||||
|
||||
MVKQueueCommandBufferSubmissionCountdown _cmdBuffCountdown;
|
||||
std::vector<MVKCommandBuffer*> _cmdBuffers;
|
||||
std::vector<MVKSemaphore*> _signalSemaphores;
|
||||
MVKFence* _fence;
|
||||
|
@ -126,62 +126,6 @@ VkResult MVKQueue::waitIdle(MVKCommandUse cmdBuffUse) {
|
||||
return mvkWaitForFences(1, &fence, false);
|
||||
}
|
||||
|
||||
// This function is guarded against conflict with the mtlCommandBufferHasCompleted()
|
||||
// function, but is not threadsafe against calls to this function itself, or to the
|
||||
// registerMTLCommandBufferCountdown() function from multiple threads. It is assumed
|
||||
// that this function and the registerMTLCommandBufferCountdown() function are called
|
||||
// from a single thread.
|
||||
id<MTLCommandBuffer> MVKQueue::makeMTLCommandBuffer(NSString* mtlCmdBuffLabel) {
|
||||
|
||||
// Retrieve a MTLCommandBuffer from the MTLCommandQueue.
|
||||
id<MTLCommandBuffer> mtlCmdBuffer = [_mtlQueue commandBufferWithUnretainedReferences];
|
||||
mtlCmdBuffer.label = mtlCmdBuffLabel;
|
||||
|
||||
// Assign a unique ID to the MTLCommandBuffer, and track when it completes.
|
||||
MVKMTLCommandBufferID mtlCmdBuffID = _nextMTLCmdBuffID++;
|
||||
[mtlCmdBuffer addCompletedHandler: ^(id<MTLCommandBuffer> mtlCmdBuff) {
|
||||
this->mtlCommandBufferHasCompleted(mtlCmdBuff, mtlCmdBuffID);
|
||||
}];
|
||||
|
||||
// Keep a running count of the active MTLCommandBuffers.
|
||||
// This needs to be guarded against a race condition with a MTLCommandBuffer completing.
|
||||
lock_guard<mutex> lock(_completionLock);
|
||||
_activeMTLCommandBufferCount++;
|
||||
|
||||
return mtlCmdBuffer;
|
||||
}
|
||||
|
||||
// This function must be called after all corresponding calls to makeMTLCommandBuffer() and from the same thead.
|
||||
void MVKQueue::registerMTLCommandBufferCountdown(MVKMTLCommandBufferCountdown* countdown) {
|
||||
lock_guard<mutex> lock(_completionLock);
|
||||
|
||||
if ( !countdown->setActiveMTLCommandBufferCount(_activeMTLCommandBufferCount, _nextMTLCmdBuffID) ) {
|
||||
_completionCountdowns.push_back(countdown);
|
||||
}
|
||||
}
|
||||
|
||||
void MVKQueue::mtlCommandBufferHasCompleted(id<MTLCommandBuffer> mtlCmdBuff, MVKMTLCommandBufferID mtlCmdBuffID) {
|
||||
lock_guard<mutex> lock(_completionLock);
|
||||
|
||||
_activeMTLCommandBufferCount--;
|
||||
|
||||
// Iterate through the countdowns, letting them know about the completion, and
|
||||
// remove any countdowns that have completed by eliding them out of the array.
|
||||
uint32_t ccCnt = (uint32_t)_completionCountdowns.size();
|
||||
uint32_t currCCIdx = 0;
|
||||
for (uint32_t ccIdx = 0; ccIdx < ccCnt; ccIdx++) {
|
||||
MVKMTLCommandBufferCountdown* mvkCD = _completionCountdowns[ccIdx];
|
||||
if ( !mvkCD->mtlCommandBufferHasCompleted(mtlCmdBuffID) ) {
|
||||
// Only retain the countdown if it has not just completed.
|
||||
// Move it forward in the array if any preceding countdowns have been removed.
|
||||
if (currCCIdx != ccIdx) { _completionCountdowns[currCCIdx] = mvkCD; }
|
||||
currCCIdx++;
|
||||
}
|
||||
}
|
||||
// If any countdowns were removed, clear out the extras at the end
|
||||
if (currCCIdx < ccCnt) { _completionCountdowns.resize(currCCIdx); }
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Construction
|
||||
|
||||
@ -193,7 +137,6 @@ MVKQueue::MVKQueue(MVKDevice* device, MVKQueueFamily* queueFamily, uint32_t inde
|
||||
_queueFamily = queueFamily;
|
||||
_index = index;
|
||||
_priority = priority;
|
||||
_activeMTLCommandBufferCount = 0;
|
||||
_nextMTLCmdBuffID = 1;
|
||||
|
||||
initName();
|
||||
@ -238,15 +181,6 @@ void MVKQueue::initGPUCaptureScopes() {
|
||||
}
|
||||
|
||||
MVKQueue::~MVKQueue() {
|
||||
// Delay destroying this queue until registerMTLCommandBufferCountdown() is done.
|
||||
// registerMTLCommandBufferCountdown() can trigger a queue submission to finish(),
|
||||
// which may trigger semaphores that control a queue waitIdle(). If that waitIdle()
|
||||
// is being called by the app just prior to device and queue destruction, a rare race
|
||||
// condition exists if registerMTLCommandBufferCountdown() does not complete before
|
||||
// this queue is destroyed. If _completionLock is destroyed along with this queue,
|
||||
// before registerMTLCommandBufferCountdown() completes, a SIGABRT crash will arise
|
||||
// in the destructor of the lock created in registerMTLCommandBufferCountdown().
|
||||
lock_guard<mutex> lock(_completionLock);
|
||||
destroyExecQueue();
|
||||
_submissionCaptureScope->destroy();
|
||||
_presentationCaptureScope->destroy();
|
||||
@ -261,16 +195,6 @@ void MVKQueue::destroyExecQueue() {
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark MVKQueueCommandBufferSubmissionCountdown
|
||||
|
||||
MVKQueueCommandBufferSubmissionCountdown::MVKQueueCommandBufferSubmissionCountdown(MVKQueueCommandBufferSubmission* qSub) {
|
||||
_qSub = qSub;
|
||||
}
|
||||
|
||||
void MVKQueueCommandBufferSubmissionCountdown::finish() { _qSub->finish(); }
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark MVKQueueSubmission
|
||||
|
||||
@ -298,14 +222,11 @@ void MVKQueueSubmission::recordResult(VkResult vkResult) {
|
||||
#pragma mark -
|
||||
#pragma mark MVKQueueCommandBufferSubmission
|
||||
|
||||
std::atomic<uint32_t> _subCount;
|
||||
|
||||
void MVKQueueCommandBufferSubmission::execute() {
|
||||
|
||||
// MVKLogDebug("Executing submission %p.", this);
|
||||
|
||||
auto cs = _queue->_submissionCaptureScope;
|
||||
cs->beginScope();
|
||||
_queue->_submissionCaptureScope->beginScope();
|
||||
|
||||
// Execute each command buffer, or if no command buffers, but a fence or semaphores,
|
||||
// create an empty MTLCommandBuffer to trigger the semaphores and fence.
|
||||
@ -321,23 +242,20 @@ void MVKQueueCommandBufferSubmission::execute() {
|
||||
}
|
||||
}
|
||||
|
||||
commitActiveMTLCommandBuffer();
|
||||
|
||||
cs->endScope();
|
||||
|
||||
// Register for callback when MTLCommandBuffers have completed
|
||||
_queue->registerMTLCommandBufferCountdown(&_cmdBuffCountdown);
|
||||
// Nothing after this because callback might destroy this instance before this function ends.
|
||||
commitActiveMTLCommandBuffer(true);
|
||||
}
|
||||
|
||||
id<MTLCommandBuffer> MVKQueueCommandBufferSubmission::getActiveMTLCommandBuffer() {
|
||||
if ( !_activeMTLCommandBuffer ) {
|
||||
_activeMTLCommandBuffer = _queue->makeMTLCommandBuffer(getMTLCommandBufferName());
|
||||
_activeMTLCommandBuffer = [_queue->_mtlQueue commandBufferWithUnretainedReferences];
|
||||
_activeMTLCommandBuffer.label = mvkMTLCommandBufferLabel(_cmdBuffUse);
|
||||
[_activeMTLCommandBuffer enqueue];
|
||||
}
|
||||
return _activeMTLCommandBuffer;
|
||||
}
|
||||
|
||||
void MVKQueueCommandBufferSubmission::commitActiveMTLCommandBuffer() {
|
||||
void MVKQueueCommandBufferSubmission::commitActiveMTLCommandBuffer(bool signalCompletion) {
|
||||
|
||||
// Wait on each wait semaphore in turn. It doesn't matter which order they are signalled.
|
||||
// We have delayed this as long as possible to allow as much filling of the MTLCommandBuffer
|
||||
@ -347,24 +265,26 @@ void MVKQueueCommandBufferSubmission::commitActiveMTLCommandBuffer() {
|
||||
for (auto& ws : _waitSemaphores) { ws->wait(); }
|
||||
}
|
||||
|
||||
[_activeMTLCommandBuffer commit];
|
||||
_activeMTLCommandBuffer = nil; // not retained
|
||||
}
|
||||
if (signalCompletion) {
|
||||
[_activeMTLCommandBuffer addCompletedHandler: ^(id<MTLCommandBuffer> mtlCmdBuff) {
|
||||
this->finish();
|
||||
}];
|
||||
}
|
||||
|
||||
// Returns an NSString suitable for use as a label
|
||||
NSString* MVKQueueCommandBufferSubmission::getMTLCommandBufferName() {
|
||||
switch (_cmdBuffUse) {
|
||||
case kMVKCommandUseQueueSubmit:
|
||||
return [NSString stringWithFormat: @"%@ (virtual for sync)", mvkMTLCommandBufferLabel(_cmdBuffUse)];
|
||||
default:
|
||||
return mvkMTLCommandBufferLabel(_cmdBuffUse);
|
||||
}
|
||||
// Use temp var because callback may destroy this instance before this function ends.
|
||||
id<MTLCommandBuffer> mtlCmdBuff = _activeMTLCommandBuffer;
|
||||
_activeMTLCommandBuffer = nil; // not retained
|
||||
[mtlCmdBuff commit];
|
||||
}
|
||||
|
||||
void MVKQueueCommandBufferSubmission::finish() {
|
||||
|
||||
// MVKLogDebug("Finishing submission %p. Submission count %u.", this, _subCount--);
|
||||
|
||||
// Performed here instead of as part of execute() for rare case where app destroys queue
|
||||
// immediately after a waitIdle() is cleared by fence below, taking the capture scope with it.
|
||||
_queue->_submissionCaptureScope->endScope();
|
||||
|
||||
// Signal each of the signal semaphores.
|
||||
for (auto& ss : _signalSemaphores) { ss->signal(); }
|
||||
|
||||
@ -382,7 +302,7 @@ MVKQueueCommandBufferSubmission::MVKQueueCommandBufferSubmission(MVKDevice* devi
|
||||
: MVKQueueSubmission(device,
|
||||
queue,
|
||||
(pSubmit ? pSubmit->waitSemaphoreCount : 0),
|
||||
(pSubmit ? pSubmit->pWaitSemaphores : nullptr)), _cmdBuffCountdown(this) {
|
||||
(pSubmit ? pSubmit->pWaitSemaphores : nullptr)) {
|
||||
|
||||
// pSubmit can be null if just tracking the fence alone
|
||||
if (pSubmit) {
|
||||
@ -405,6 +325,7 @@ MVKQueueCommandBufferSubmission::MVKQueueCommandBufferSubmission(MVKDevice* devi
|
||||
_cmdBuffUse= cmdBuffUse;
|
||||
_activeMTLCommandBuffer = nil;
|
||||
|
||||
// static std::atomic<uint32_t> _subCount;
|
||||
// MVKLogDebug("Creating submission %p. Submission count %u.", this, ++_subCount);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user