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:
Bill Hollings 2018-09-11 10:54:21 -04:00
parent f2cd295b18
commit 26058daba3
4 changed files with 22 additions and 237 deletions

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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
}
// 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);
if (signalCompletion) {
[_activeMTLCommandBuffer addCompletedHandler: ^(id<MTLCommandBuffer> mtlCmdBuff) {
this->finish();
}];
}
// 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);
}