blob: 356236941332ad02ca5c5985e6771a991fe7d1aa [file] [log] [blame]
/*
* MVKSync.h
*
* Copyright (c) 2015-2022 The Brenwill Workshop Ltd. (http://www.brenwill.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "MVKDevice.h"
#include <atomic>
#include <mutex>
#include <condition_variable>
#include <unordered_set>
class MVKFenceSitter;
#pragma mark -
#pragma mark MVKSemaphoreImpl
/**
* A general utility semaphore object. Reservations can be made with an instance,
* and it will block waiting threads until reservations have been released.
*
* An instance can be configured so that each call to the reserve() function must be
* matched with a separate call to the release() function before waiting threads are
* unblocked, or it can be configured so that a single call to the release() function
* will release all outstanding reservations and unblock all threads immediately.
*/
class MVKSemaphoreImpl : public MVKBaseObject {
public:
/** Returns nil as this object has no need to track the Vulkan object. */
MVKVulkanAPIObject* getVulkanAPIObject() override { return nullptr; };
/**
* Adds a reservation to this semaphore, incrementing the reservation count.
* Subsequent calls to a wait() function will block until a corresponding call
* is made to the release() function.
*/
void reserve();
/**
* Depending on configuration, releases one or all reservations. When all reservations
* have been released, unblocks all waiting threads to continue processing.
* Returns true if the last reservation was released.
*/
bool release();
/** Returns whether this instance is in a reserved state. */
bool isReserved();
/**
* Blocks processing on the current thread until any or all (depending on configuration) outstanding
* reservations have been released, or until the specified timeout interval in nanoseconds expires.
*
* If timeout is the special value UINT64_MAX the timeout is treated as infinite.
*
* If reserveAgain is set to true, a single reservation will be added once this wait is finished.
*
* Returns true if all reservations were cleared, or false if the timeout interval expired.
*/
bool wait(uint64_t timeout = UINT64_MAX, bool reserveAgain = false);
#pragma mark Construction
/**
* Constructs an instance with the specified number of initial reservations.
* This value defaults to zero, starting the semaphore in an unblocking state.
*
* The waitAll parameter indicates whether a call to the release() function is required
* for each call to the reserve() function (waitAll = true), or whether a single call
* to the release() function will release all outstanding reservations (waitAll = false).
* This value defaults to true, indicating that each call to the reserve() function will
* require a separate call to the release() function to cause the semaphore to stop blocking.
*/
MVKSemaphoreImpl(bool waitAll = true, uint32_t reservationCount = 0)
: _shouldWaitAll(waitAll), _reservationCount(reservationCount) {}
/** Destructor. */
~MVKSemaphoreImpl();
private:
bool operator()();
inline bool isClear() { return _reservationCount == 0; } // Not thread-safe
std::mutex _lock;
std::condition_variable _blocker;
bool _shouldWaitAll;
uint32_t _reservationCount;
};
#pragma mark -
#pragma mark MVKSemaphore
/** Abstract class that represents a Vulkan semaphore. */
class MVKSemaphore : public MVKVulkanAPIDeviceObject {
public:
/** Returns the Vulkan type of this object. */
VkObjectType getVkObjectType() override { return VK_OBJECT_TYPE_SEMAPHORE; }
/** Returns the debug report object type of this object. */
VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_SEMAPHORE_EXT; }
/** Returns the type of this semaphore. */
virtual VkSemaphoreType getSemaphoreType() { return VK_SEMAPHORE_TYPE_BINARY; }
/**
* Wait for this semaphore to be signaled.
*
* If the subclass uses command encoding AND the mtlCmdBuff is not nil, a wait
* is encoded on the mtlCmdBuff, and this call returns immediately. Otherwise, if the
* subclass does NOT use command encoding, AND the mtlCmdBuff is nil, this call blocks
* indefinitely until this semaphore is signaled. Other combinations do nothing.
*
* This design allows this function to be blindly called twice, from different points in the
* code path, once with a mtlCmdBuff to support encoding a wait on the command buffer if the
* subclass supports command encoding, and once without a mtlCmdBuff, at the point in the
* code path where the code should block if the subclass does not support command encoding.
*
* 'value' only applies if this semaphore is a timeline semaphore. It is the value to wait for the
* semaphore to have.
*/
virtual void encodeWait(id<MTLCommandBuffer> mtlCmdBuff, uint64_t value) = 0;
/**
* Signals this semaphore.
*
* If the subclass uses command encoding AND the mtlCmdBuff is not nil, a signal is
* encoded on the mtlCmdBuff. Otherwise, if the subclass does NOT use command encoding,
* AND the mtlCmdBuff is nil, this call immediately signals any waiting calls.
* Either way, this call returns immediately. Other combinations do nothing.
*
* This design allows this function to be blindly called twice, from different points in the
* code path, once with a mtlCmdBuff to support encoding a wait on the command buffer if the
* subclass supports command encoding, and once without a mtlCmdBuff, at the point in the
* code path where the code should block if the subclass does not support command encoding.
*
* 'value' only applies if this semaphore is a timeline semaphore. It is the value to assign the semaphore
* upon completion.
*/
virtual void encodeSignal(id<MTLCommandBuffer> mtlCmdBuff, uint64_t value) = 0;
/**
* Begin a deferred signal operation.
*
* A deferred signal acts like a normal signal operation, except that the signal op itself
* is not actually executed. A token is returned, which must be passed to encodeDeferredSignal()
* to complete the signal operation.
*
* This is intended to be used by MVKSwapchain, in order to properly implement semaphores
* for image availability, particularly with MTLEvent-based semaphores, to ensure the correct
* value is used in signal/wait operations.
*/
virtual uint64_t deferSignal() = 0;
/**
* Complete a deferred signal operation.
*
* This is the other half of the deferSignal() method. The token that was returned
* from deferSignal() must be passed here.
*/
virtual void encodeDeferredSignal(id<MTLCommandBuffer> mtlCmdBuff, uint64_t deferToken) = 0;
/** Returns whether this semaphore uses command encoding. */
virtual bool isUsingCommandEncoding() = 0;
/**
* Returns the MTLSharedEvent underlying this Vulkan semaphore,
* or nil if this semaphore is not underpinned by a MTLSharedEvent.
*/
virtual id<MTLSharedEvent> getMTLSharedEvent() { return nil; };
#pragma mark Construction
MVKSemaphore(MVKDevice* device, const VkSemaphoreCreateInfo* pCreateInfo) : MVKVulkanAPIDeviceObject(device) {}
protected:
void propagateDebugName() override {}
};
#pragma mark -
#pragma mark MVKSemaphoreMTLFence
/** An MVKSemaphore that uses MTLFence to provide synchronization. */
class MVKSemaphoreMTLFence : public MVKSemaphore {
public:
void encodeWait(id<MTLCommandBuffer> mtlCmdBuff, uint64_t) override;
void encodeSignal(id<MTLCommandBuffer> mtlCmdBuff, uint64_t) override;
uint64_t deferSignal() override;
void encodeDeferredSignal(id<MTLCommandBuffer> mtlCmdBuff, uint64_t) override;
bool isUsingCommandEncoding() override { return true; }
MVKSemaphoreMTLFence(MVKDevice* device,
const VkSemaphoreCreateInfo* pCreateInfo,
const VkExportMetalObjectCreateInfoEXT* pExportInfo,
const VkImportMetalSharedEventInfoEXT* pImportInfo);
~MVKSemaphoreMTLFence() override;
protected:
id<MTLFence> _mtlFence;
};
#pragma mark -
#pragma mark MVKSemaphoreMTLEvent
/** An MVKSemaphore that uses MTLEvent to provide synchronization. */
class MVKSemaphoreMTLEvent : public MVKSemaphore {
public:
void encodeWait(id<MTLCommandBuffer> mtlCmdBuff, uint64_t value) override;
void encodeSignal(id<MTLCommandBuffer> mtlCmdBuff, uint64_t value) override;
uint64_t deferSignal() override;
void encodeDeferredSignal(id<MTLCommandBuffer> mtlCmdBuff, uint64_t deferToken) override;
bool isUsingCommandEncoding() override { return true; }
MVKSemaphoreMTLEvent(MVKDevice* device,
const VkSemaphoreCreateInfo* pCreateInfo,
const VkExportMetalObjectCreateInfoEXT* pExportInfo,
const VkImportMetalSharedEventInfoEXT* pImportInfo);
~MVKSemaphoreMTLEvent() override;
protected:
id<MTLEvent> _mtlEvent;
std::atomic<uint64_t> _mtlEventValue;
};
#pragma mark -
#pragma mark MVKSemaphoreEmulated
/** An MVKSemaphore that uses CPU synchronization to provide synchronization functionality. */
class MVKSemaphoreEmulated : public MVKSemaphore {
public:
void encodeWait(id<MTLCommandBuffer> mtlCmdBuff, uint64_t) override;
void encodeSignal(id<MTLCommandBuffer> mtlCmdBuff, uint64_t) override;
uint64_t deferSignal() override;
void encodeDeferredSignal(id<MTLCommandBuffer> mtlCmdBuff, uint64_t) override;
bool isUsingCommandEncoding() override { return false; }
MVKSemaphoreEmulated(MVKDevice* device,
const VkSemaphoreCreateInfo* pCreateInfo,
const VkExportMetalObjectCreateInfoEXT* pExportInfo,
const VkImportMetalSharedEventInfoEXT* pImportInfo);
protected:
MVKSemaphoreImpl _blocker;
};
#pragma mark -
#pragma mark MVKTimelineSemaphore
/** Abstract class that represents a Vulkan timeline semaphore. */
class MVKTimelineSemaphore : public MVKSemaphore {
public:
VkSemaphoreType getSemaphoreType() override { return VK_SEMAPHORE_TYPE_TIMELINE; }
uint64_t deferSignal() override { return 0; }
// Timeline semaphores cannot yet be used for signaling swapchain availability, because
// no interaction is yet defined. When it is, though, it's likely that a value must
// be supplied, just like when using them with command buffers.
void encodeDeferredSignal(id<MTLCommandBuffer> mtlCmdBuff, uint64_t deferToken) override {
return encodeSignal(mtlCmdBuff, deferToken);
}
/** Returns the current value of the semaphore counter. */
virtual uint64_t getCounterValue() = 0;
/** Signals this semaphore on the host. */
virtual void signal(const VkSemaphoreSignalInfo* pSignalInfo) = 0;
/** Registers a wait for this semaphore on the host. Returns true if the semaphore is already signaled. */
virtual bool registerWait(MVKFenceSitter* sitter, const VkSemaphoreWaitInfo* pWaitInfo, uint32_t index) = 0;
/** Stops waiting for this semaphore. */
virtual void unregisterWait(MVKFenceSitter* sitter) = 0;
#pragma mark Construction
MVKTimelineSemaphore(MVKDevice* device, const VkSemaphoreCreateInfo* pCreateInfo) : MVKSemaphore(device, pCreateInfo) {}
};
#pragma mark -
#pragma mark MVKTimelineSemaphoreMTLEvent
/** An MVKTimelineSemaphore that uses MTLSharedEvent to provide synchronization. */
class MVKTimelineSemaphoreMTLEvent : public MVKTimelineSemaphore {
public:
void encodeWait(id<MTLCommandBuffer> mtlCmdBuff, uint64_t value) override;
void encodeSignal(id<MTLCommandBuffer> mtlCmdBuff, uint64_t value) override;
bool isUsingCommandEncoding() override { return true; }
id<MTLSharedEvent> getMTLSharedEvent() override { return _mtlEvent; };
uint64_t getCounterValue() override { return _mtlEvent.signaledValue; }
void signal(const VkSemaphoreSignalInfo* pSignalInfo) override;
bool registerWait(MVKFenceSitter* sitter, const VkSemaphoreWaitInfo* pWaitInfo, uint32_t index) override;
void unregisterWait(MVKFenceSitter* sitter) override;
MVKTimelineSemaphoreMTLEvent(MVKDevice* device,
const VkSemaphoreCreateInfo* pCreateInfo,
const VkSemaphoreTypeCreateInfo* pTypeCreateInfo,
const VkExportMetalObjectCreateInfoEXT* pExportInfo,
const VkImportMetalSharedEventInfoEXT* pImportInfo);
~MVKTimelineSemaphoreMTLEvent() override;
protected:
id<MTLSharedEvent> _mtlEvent = nil;
std::mutex _lock;
std::unordered_set<MVKFenceSitter*> _sitters;
};
#pragma mark -
#pragma mark MVKTimelineSemaphoreEmulated
/** An MVKTimelineSemaphore that uses CPU synchronization to provide synchronization functionality. */
class MVKTimelineSemaphoreEmulated : public MVKTimelineSemaphore {
public:
void encodeWait(id<MTLCommandBuffer> mtlCmdBuff, uint64_t value) override;
void encodeSignal(id<MTLCommandBuffer> mtlCmdBuff, uint64_t value) override;
bool isUsingCommandEncoding() override { return false; }
uint64_t getCounterValue() override { return _value; }
void signal(const VkSemaphoreSignalInfo* pSignalInfo) override;
bool registerWait(MVKFenceSitter* sitter, const VkSemaphoreWaitInfo* pWaitInfo, uint32_t index) override;
void unregisterWait(MVKFenceSitter* sitter) override;
MVKTimelineSemaphoreEmulated(MVKDevice* device,
const VkSemaphoreCreateInfo* pCreateInfo,
const VkSemaphoreTypeCreateInfo* pTypeCreateInfo,
const VkExportMetalObjectCreateInfoEXT* pExportInfo,
const VkImportMetalSharedEventInfoEXT* pImportInfo);
protected:
void signalImpl(uint64_t value);
std::atomic<uint64_t> _value;
std::mutex _lock;
std::condition_variable _blocker;
std::unordered_map<uint64_t, std::unordered_set<MVKFenceSitter*>> _sitters;
};
#pragma mark -
#pragma mark MVKFence
/** Represents a Vulkan fence. */
class MVKFence : public MVKVulkanAPIDeviceObject {
public:
/** Returns the Vulkan type of this object. */
VkObjectType getVkObjectType() override { return VK_OBJECT_TYPE_FENCE; }
/** Returns the debug report object type of this object. */
VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_FENCE_EXT; }
/**
* If this fence has not been signaled yet, adds the specified fence sitter to the
* internal list of fence sitters that will be notified when this fence is signaled,
* and then calls await() on the fence sitter so it is aware that it will be signaled.
*
* Does nothing if this fence has already been signaled, and does not call
* await() on the fence sitter.
*
* Each fence sitter should only listen once for each fence. Adding the same fence sitter
* more than once in between each fence reset and signal results in undefined behaviour.
*/
void addSitter(MVKFenceSitter* fenceSitter);
/** Removes the specified fence sitter. */
void removeSitter(MVKFenceSitter* fenceSitter);
/** Signals this fence. Notifies all waiting fence sitters. */
void signal();
/** Rremoves all fence sitters and resets this fence back to unsignaled state again. */
void reset();
/** Returns whether this fence has been signaled and not reset. */
bool getIsSignaled();
#pragma mark Construction
MVKFence(MVKDevice* device, const VkFenceCreateInfo* pCreateInfo) :
MVKVulkanAPIDeviceObject(device), _isSignaled(mvkAreAllFlagsEnabled(pCreateInfo->flags, VK_FENCE_CREATE_SIGNALED_BIT)) {}
protected:
void propagateDebugName() override {}
void notifySitters();
std::mutex _lock;
std::unordered_set<MVKFenceSitter*> _fenceSitters;
bool _isSignaled;
};
#pragma mark -
#pragma mark MVKFenceSitter
/** An object that responds to signals from MVKFences and MVKTimelineSemaphores. */
class MVKFenceSitter : public MVKBaseObject {
public:
/** This is a temporarily instantiated helper class. */
MVKVulkanAPIObject* getVulkanAPIObject() override { return nullptr; }
/**
* If this instance has been configured to wait for fences, blocks processing on the
* current thread until any or all of the fences that this instance is waiting for are
* signaled, or until the specified timeout in nanoseconds expires. If this instance
* has not been configured to wait for fences, this function immediately returns true.
*
* If timeout is the special value UINT64_MAX the timeout is treated as infinite.
*
* Returns true if the required fences were triggered, or false if the timeout interval expired.
*/
bool wait(uint64_t timeout = UINT64_MAX) { return _blocker.wait(timeout); }
#pragma mark Construction
MVKFenceSitter(bool waitAll) : _blocker(waitAll, 0) {}
private:
friend class MVKFence;
friend class MVKTimelineSemaphoreMTLEvent;
friend class MVKTimelineSemaphoreEmulated;
MTLSharedEventListener* getMTLSharedEventListener();
void await() { _blocker.reserve(); }
void signaled() { _blocker.release(); }
MVKSemaphoreImpl _blocker;
MTLSharedEventListener* _listener = nil;
};
#pragma mark -
#pragma mark MVKEvent
/** Abstract class that represents a Vulkan event. */
class MVKEvent : public MVKVulkanAPIDeviceObject {
public:
/** Returns the Vulkan type of this object. */
VkObjectType getVkObjectType() override { return VK_OBJECT_TYPE_EVENT; }
/** Returns the debug report object type of this object. */
VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_EVENT_EXT; }
/** Returns whether this event is set. */
virtual bool isSet() = 0;
/** Sets the signal status. */
virtual void signal(bool status) = 0;
/** Encodes an operation to signal the event with a status. */
virtual void encodeSignal(id<MTLCommandBuffer> mtlCmdBuff, bool status) = 0;
/** Encodes an operation to block command buffer operation until this event is signaled. */
virtual void encodeWait(id<MTLCommandBuffer> mtlCmdBuff) = 0;
/** Returns the MTLSharedEvent underlying this Vulkan event. */
virtual id<MTLSharedEvent> getMTLSharedEvent() = 0;
#pragma mark Construction
MVKEvent(MVKDevice* device,
const VkEventCreateInfo* pCreateInfo,
const VkExportMetalObjectCreateInfoEXT* pExportInfo,
const VkImportMetalSharedEventInfoEXT* pImportInfo) : MVKVulkanAPIDeviceObject(device) {}
protected:
void propagateDebugName() override {}
};
#pragma mark -
#pragma mark MVKEventNative
/** An MVKEvent that uses native MTLSharedEvent to provide VkEvent functionality. */
class MVKEventNative : public MVKEvent {
public:
bool isSet() override;
void signal(bool status) override;
void encodeSignal(id<MTLCommandBuffer> mtlCmdBuff, bool status) override;
void encodeWait(id<MTLCommandBuffer> mtlCmdBuff) override;
id<MTLSharedEvent> getMTLSharedEvent() override { return _mtlEvent; };
MVKEventNative(MVKDevice* device,
const VkEventCreateInfo* pCreateInfo,
const VkExportMetalObjectCreateInfoEXT* pExportInfo,
const VkImportMetalSharedEventInfoEXT* pImportInfo);
~MVKEventNative() override;
protected:
id<MTLSharedEvent> _mtlEvent;
};
#pragma mark -
#pragma mark MVKEventEmulated
/** An MVKEvent that uses CPU synchronization to provide VkEvent functionality. */
class MVKEventEmulated : public MVKEvent {
public:
bool isSet() override;
void signal(bool status) override;
void encodeSignal(id<MTLCommandBuffer> mtlCmdBuff, bool status) override;
void encodeWait(id<MTLCommandBuffer> mtlCmdBuff) override;
id<MTLSharedEvent> getMTLSharedEvent() override { return nil; };
MVKEventEmulated(MVKDevice* device,
const VkEventCreateInfo* pCreateInfo,
const VkExportMetalObjectCreateInfoEXT* pExportInfo,
const VkImportMetalSharedEventInfoEXT* pImportInfo);
protected:
MVKSemaphoreImpl _blocker;
bool _inlineSignalStatus;
};
#pragma mark -
#pragma mark Support functions
/** Resets the specified fences. */
VkResult mvkResetFences(uint32_t fenceCount, const VkFence* pFences);
/**
* Blocks the current thread until any or all of the specified
* fences have been signaled, or the specified timeout occurs.
*/
VkResult mvkWaitForFences(MVKDevice* device,
uint32_t fenceCount,
const VkFence* pFences,
VkBool32 waitAll,
uint64_t timeout = UINT64_MAX);
/**
* Blocks the current thread until any or all of the specified
* semaphores have been signaled at the specified values, or the
* specified timeout occurs.
*/
VkResult mvkWaitSemaphores(MVKDevice* device,
const VkSemaphoreWaitInfo* pWaitInfo,
uint64_t timeout = UINT64_MAX);
#pragma mark -
#pragma mark MVKMetalCompiler
/**
* Abstract class that creates Metal objects that require compilation, such as
* MTLShaderLibrary, MTLFunction, MTLRenderPipelineState and MTLComputePipelineState.
*
* Instances of this class are one-shot, and can only be used for a single compilation.
*/
class MVKMetalCompiler : public MVKBaseObject {
public:
/** Returns the Vulkan API opaque object controlling this object. */
MVKVulkanAPIObject* getVulkanAPIObject() override { return _owner->getVulkanAPIObject(); };
/** If this object is waiting for compilation to complete, deletion will be deferred until then. */
void destroy() override;
#pragma mark Construction
MVKMetalCompiler(MVKVulkanAPIDeviceObject* owner) : _owner(owner) {}
~MVKMetalCompiler() override;
protected:
void compile(std::unique_lock<std::mutex>& lock, dispatch_block_t block);
virtual void handleError();
bool endCompile(NSError* compileError);
bool markDestroyed();
MVKVulkanAPIDeviceObject* _owner;
NSError* _compileError = nil;
uint64_t _startTime = 0;
bool _isCompileDone = false;
bool _isDestroyed = false;
std::mutex _completionLock;
std::condition_variable _blocker;
std::string _compilerType = "Unknown";
MVKPerformanceTracker* _pPerformanceTracker = nullptr;
};