From 7dc642196a78b63ff983a587dc10e40298065bb2 Mon Sep 17 00:00:00 2001 From: Bill Hollings Date: Thu, 8 Aug 2019 16:13:47 -0400 Subject: [PATCH] Add support for VkEvent. Add MVKEvent class. MVKEventNative subclass uses native MTLEvent. MVKEventEmulated subclass uses emulation using CPU blocking and MTLCommandBuffer completion handling. MVKConfiguration::synchronousQueueSubmits now disabled by default if MTLEvents are not supported. Document updated use of MVK_ALLOW_METAL_EVENTS and MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS environment variables and synchronousQueueSubmits config setting, in vk_mvk_moltenvk.h. --- Docs/Whats_New.md | 1 + MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h | 7 +- MoltenVK/MoltenVK/Commands/MVKCmdPipeline.h | 72 ++++++++++++++++ MoltenVK/MoltenVK/Commands/MVKCmdPipeline.mm | 85 +++++++++++++++++++ MoltenVK/MoltenVK/Commands/MVKCommandBuffer.h | 3 + .../MoltenVK/Commands/MVKCommandBuffer.mm | 5 ++ MoltenVK/MoltenVK/Commands/MVKCommandPool.h | 4 + MoltenVK/MoltenVK/Commands/MVKCommandPool.mm | 6 +- MoltenVK/MoltenVK/GPUObjects/MVKDevice.h | 11 +++ MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm | 24 +++++- MoltenVK/MoltenVK/GPUObjects/MVKImage.mm | 6 +- MoltenVK/MoltenVK/GPUObjects/MVKInstance.mm | 15 ++++ MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm | 6 +- MoltenVK/MoltenVK/GPUObjects/MVKSync.h | 83 +++++++++++++++++- MoltenVK/MoltenVK/GPUObjects/MVKSync.mm | 77 ++++++++++++++++- MoltenVK/MoltenVK/Utility/MVKEnvironment.h | 2 +- MoltenVK/MoltenVK/Vulkan/vulkan.mm | 37 ++++---- 17 files changed, 413 insertions(+), 31 deletions(-) diff --git a/Docs/Whats_New.md b/Docs/Whats_New.md index 7000655b..9a5e72f7 100644 --- a/Docs/Whats_New.md +++ b/Docs/Whats_New.md @@ -18,6 +18,7 @@ MoltenVK 1.0.37 Released TBD +- Add support for `VkEvent`, using either native `MTLEvent` or emulation when `MTLEvent` not available. - Revert to supporting host-coherent memory for linear images on macOS. - Ensure Vulkan loader magic number is set every time before returning any dispatchable Vulkan handle. - Fix crash when `VkDeviceCreateInfo` specifies queue families out of numerical order. diff --git a/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h b/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h index d67de99c..eef5e65b 100644 --- a/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h +++ b/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h @@ -116,7 +116,7 @@ typedef unsigned long MTLLanguageVersion; * * 4. Setting the MVK_ALLOW_METAL_EVENTS runtime environment variable or MoltenVK compile-time build * setting to 1 will cause MoltenVK to use Metal events, if they are available on the device, for - * Vulkan sychronization components such as VkSemaphore. This is disabled by default. + * for VkSemaphore sychronization behaviour. This is disabled by default. */ typedef struct { @@ -167,7 +167,10 @@ typedef struct { * The initial value or this parameter is set by the * MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS * runtime environment variable or MoltenVK compile-time build setting. - * If neither is set, the value of this parameter defaults to true. + * If neither is set, the value of this parameter defaults to true for macOS 10.14 + * and above or iOS 12 and above, and false otherwise. The reason for this distinction + * is that this feature should be disabled when emulation is required to support VkEvents + * because native support for events (MTLEvent) is not available. */ VkBool32 synchronousQueueSubmits; diff --git a/MoltenVK/MoltenVK/Commands/MVKCmdPipeline.h b/MoltenVK/MoltenVK/Commands/MVKCmdPipeline.h index 1cfd37eb..1c543aa1 100644 --- a/MoltenVK/MoltenVK/Commands/MVKCmdPipeline.h +++ b/MoltenVK/MoltenVK/Commands/MVKCmdPipeline.h @@ -189,6 +189,54 @@ private: }; +#pragma mark - +#pragma mark MVKCmdSetResetEvent + +/** Vulkan command to set or reset an event. */ +class MVKCmdSetResetEvent : public MVKCommand { + +public: + void setContent(VkEvent event, VkPipelineStageFlags stageMask, bool status); + + void encode(MVKCommandEncoder* cmdEncoder) override; + + MVKCmdSetResetEvent(MVKCommandTypePool* pool); + +private: + MVKEvent* _mvkEvent; + bool _status; + +}; + + +#pragma mark - +#pragma mark MVKCmdWaitEvents + +/** Vulkan command to wait for an event to be signaled. */ +class MVKCmdWaitEvents : public MVKCommand { + +public: + void setContent(uint32_t eventCount, + const VkEvent* pEvents, + VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + uint32_t memoryBarrierCount, + const VkMemoryBarrier* pMemoryBarriers, + uint32_t bufferMemoryBarrierCount, + const VkBufferMemoryBarrier* pBufferMemoryBarriers, + uint32_t imageMemoryBarrierCount, + const VkImageMemoryBarrier* pImageMemoryBarriers); + + void encode(MVKCommandEncoder* cmdEncoder) override; + + MVKCmdWaitEvents(MVKCommandTypePool* pool); + +private: + MVKVectorInline _mvkEvents; + +}; + + #pragma mark - #pragma mark Command creation functions @@ -241,3 +289,27 @@ void mvkCmdPushDescriptorSetWithTemplate(MVKCommandBuffer* cmdBuff, VkPipelineLayout layout, uint32_t set, const void* pData); + +/** Adds a set event command to the specified command buffer. */ +void mvkCmdSetEvent(MVKCommandBuffer* cmdBuff, + VkEvent event, + VkPipelineStageFlags stageMask); + +/** Adds a reset event command to the specified command buffer. */ +void mvkCmdResetEvent(MVKCommandBuffer* cmdBuff, + VkEvent event, + VkPipelineStageFlags stageMask); + + +/** Adds a wait events command to the specified command buffer. */ +void mvkCmdWaitEvents(MVKCommandBuffer* cmdBuff, + uint32_t eventCount, + const VkEvent* pEvents, + VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + uint32_t memoryBarrierCount, + const VkMemoryBarrier* pMemoryBarriers, + uint32_t bufferMemoryBarrierCount, + const VkBufferMemoryBarrier* pBufferMemoryBarriers, + uint32_t imageMemoryBarrierCount, + const VkImageMemoryBarrier* pImageMemoryBarriers); diff --git a/MoltenVK/MoltenVK/Commands/MVKCmdPipeline.mm b/MoltenVK/MoltenVK/Commands/MVKCmdPipeline.mm index c65adced..7cd6e09c 100644 --- a/MoltenVK/MoltenVK/Commands/MVKCmdPipeline.mm +++ b/MoltenVK/MoltenVK/Commands/MVKCmdPipeline.mm @@ -346,6 +346,54 @@ MVKCmdPushDescriptorSetWithTemplate::~MVKCmdPushDescriptorSetWithTemplate() { } +#pragma mark - +#pragma mark MVKCmdSetResetEvent + +void MVKCmdSetResetEvent::setContent(VkEvent event, VkPipelineStageFlags stageMask, bool status) { + _mvkEvent = (MVKEvent*)event; + _status = status; +} + +void MVKCmdSetResetEvent::encode(MVKCommandEncoder* cmdEncoder) { + cmdEncoder->signalEvent(_mvkEvent, _status); +} + +MVKCmdSetResetEvent::MVKCmdSetResetEvent(MVKCommandTypePool* pool) + : MVKCommand::MVKCommand((MVKCommandTypePool*)pool) {} + + +#pragma mark - +#pragma mark MVKCmdWaitEvents + +void MVKCmdWaitEvents::setContent(uint32_t eventCount, + const VkEvent* pEvents, + VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + uint32_t memoryBarrierCount, + const VkMemoryBarrier* pMemoryBarriers, + uint32_t bufferMemoryBarrierCount, + const VkBufferMemoryBarrier* pBufferMemoryBarriers, + uint32_t imageMemoryBarrierCount, + const VkImageMemoryBarrier* pImageMemoryBarriers) { + + _mvkEvents.clear(); // Clear for reuse + _mvkEvents.reserve(eventCount); + for (uint32_t i = 0; i < eventCount; i++) { + _mvkEvents.push_back((MVKEvent*)pEvents[i]); + } + +} + +void MVKCmdWaitEvents::encode(MVKCommandEncoder* cmdEncoder) { + for (MVKEvent* mvkEvt : _mvkEvents) { + mvkEvt->encodeWait(cmdEncoder->_mtlCmdBuffer); + } +} + +MVKCmdWaitEvents::MVKCmdWaitEvents(MVKCommandTypePool* pool) + : MVKCommand::MVKCommand((MVKCommandTypePool*)pool) {} + + #pragma mark - #pragma mark Command creation functions @@ -420,3 +468,40 @@ void mvkCmdPushDescriptorSetWithTemplate(MVKCommandBuffer* cmdBuff, cmd->setContent(descUpdateTemplate, layout, set, pData); cmdBuff->addCommand(cmd); } + +void mvkCmdSetEvent(MVKCommandBuffer* cmdBuff, + VkEvent event, + VkPipelineStageFlags stageMask) { + MVKCmdSetResetEvent* cmd = cmdBuff->_commandPool->_cmdSetResetEventPool.acquireObject(); + cmd->setContent(event, stageMask, true); + cmdBuff->addCommand(cmd); +} + +void mvkCmdResetEvent(MVKCommandBuffer* cmdBuff, + VkEvent event, + VkPipelineStageFlags stageMask) { + MVKCmdSetResetEvent* cmd = cmdBuff->_commandPool->_cmdSetResetEventPool.acquireObject(); + cmd->setContent(event, stageMask, false); + cmdBuff->addCommand(cmd); +} + +void mvkCmdWaitEvents(MVKCommandBuffer* cmdBuff, + uint32_t eventCount, + const VkEvent* pEvents, + VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + uint32_t memoryBarrierCount, + const VkMemoryBarrier* pMemoryBarriers, + uint32_t bufferMemoryBarrierCount, + const VkBufferMemoryBarrier* pBufferMemoryBarriers, + uint32_t imageMemoryBarrierCount, + const VkImageMemoryBarrier* pImageMemoryBarriers) { + MVKCmdWaitEvents* cmd = cmdBuff->_commandPool->_cmdWaitEventsPool.acquireObject(); + cmd->setContent(eventCount, pEvents, + srcStageMask, dstStageMask, + memoryBarrierCount, pMemoryBarriers, + bufferMemoryBarrierCount, pBufferMemoryBarriers, + imageMemoryBarrierCount, pImageMemoryBarriers); + cmdBuff->addCommand(cmd); + +} diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.h b/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.h index ddeae21f..bbff4283 100644 --- a/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.h +++ b/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.h @@ -283,6 +283,9 @@ public: /** Binds a pipeline to a bind point. */ void bindPipeline(VkPipelineBindPoint pipelineBindPoint, MVKPipeline* pipeline); + /** Encodes an operation to signal an event to a status. */ + void signalEvent(MVKEvent* mvkEvent, bool status); + /** * If a pipeline is currently bound, returns whether the current pipeline permits dynamic * setting of the specified state. If no pipeline is currently bound, returns true. diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.mm b/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.mm index f43a4954..c8fbbdd8 100644 --- a/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.mm +++ b/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.mm @@ -359,6 +359,11 @@ void MVKCommandEncoder::bindPipeline(VkPipelineBindPoint pipelineBindPoint, MVKP } } +void MVKCommandEncoder::signalEvent(MVKEvent* mvkEvent, bool status) { + endCurrentMetalEncoding(); + mvkEvent->encodeSignal(_mtlCmdBuffer, status); +} + bool MVKCommandEncoder::supportsDynamicState(VkDynamicState state) { MVKGraphicsPipeline* gpl = (MVKGraphicsPipeline*)_graphicsPipelineState.getPipeline(); return !gpl || gpl->supportsDynamicState(state); diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandPool.h b/MoltenVK/MoltenVK/Commands/MVKCommandPool.h index 45cc0a6f..e65989ec 100644 --- a/MoltenVK/MoltenVK/Commands/MVKCommandPool.h +++ b/MoltenVK/MoltenVK/Commands/MVKCommandPool.h @@ -149,6 +149,10 @@ public: MVKCommandTypePool _cmdDebugMarkerInsertPool; + MVKCommandTypePool _cmdSetResetEventPool; + + MVKCommandTypePool _cmdWaitEventsPool; + #pragma mark Command resources diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandPool.mm b/MoltenVK/MoltenVK/Commands/MVKCommandPool.mm index 668089fd..6c95e70f 100644 --- a/MoltenVK/MoltenVK/Commands/MVKCommandPool.mm +++ b/MoltenVK/MoltenVK/Commands/MVKCommandPool.mm @@ -126,6 +126,8 @@ void MVKCommandPool::trim() { _cmdDebugMarkerBeginPool.clear(); _cmdDebugMarkerEndPool.clear(); _cmdDebugMarkerInsertPool.clear(); + _cmdSetResetEventPool.clear(); + _cmdWaitEventsPool.clear(); } @@ -180,7 +182,9 @@ MVKCommandPool::MVKCommandPool(MVKDevice* device, _cmdPushSetWithTemplatePool(this), _cmdDebugMarkerBeginPool(this), _cmdDebugMarkerEndPool(this), - _cmdDebugMarkerInsertPool(this) + _cmdDebugMarkerInsertPool(this), + _cmdSetResetEventPool(this), + _cmdWaitEventsPool(this) // when extending be sure to add to trim() as well {} diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h index 3ce75903..17bdb025 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h +++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h @@ -47,6 +47,7 @@ class MVKSwapchain; class MVKDeviceMemory; class MVKFence; class MVKSemaphore; +class MVKEvent; class MVKQueryPool; class MVKShaderModule; class MVKPipelineCache; @@ -445,6 +446,11 @@ public: void destroySemaphore(MVKSemaphore* mvkSem4, const VkAllocationCallbacks* pAllocator); + MVKEvent* createEvent(const VkEventCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator); + void destroyEvent(MVKEvent* mvkEvent, + const VkAllocationCallbacks* pAllocator); + MVKQueryPool* createQueryPool(const VkQueryPoolCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator); void destroyQueryPool(MVKQueryPool* mvkQP, @@ -640,6 +646,11 @@ public: /** Performance statistics. */ MVKPerformanceStatistics _performanceStatistics; + // Indicates whether semaphores should use MTLEvents if available. + // Set by the MVK_ALLOW_METAL_EVENTS environment variable if MTLEvents are available. + // This should be a temporary fix after some repair to semaphore handling. + bool _useMTLEventsForSemaphores; + #pragma mark Construction diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm index b1930c20..6966d73d 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm +++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm @@ -128,7 +128,7 @@ void MVKPhysicalDevice::getFeatures(VkPhysicalDeviceFeatures2* features) { auto* portabilityFeatures = (VkPhysicalDevicePortabilitySubsetFeaturesEXTX*)next; portabilityFeatures->triangleFans = false; portabilityFeatures->separateStencilMaskRef = true; - portabilityFeatures->events = false; + portabilityFeatures->events = _metalFeatures.events; portabilityFeatures->standardImageViews = _mvkInstance->getMoltenVKConfiguration()->fullImageViewSwizzle; portabilityFeatures->samplerMipLodBias = false; break; @@ -781,7 +781,7 @@ void MVKPhysicalDevice::initMetalFeatures() { if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_iOS_GPUFamily1_v5] ) { _metalFeatures.mslVersionEnum = MTLLanguageVersion2_1; - MVK_SET_FROM_ENV_OR_BUILD_BOOL(_metalFeatures.events, MVK_ALLOW_METAL_EVENTS); + _metalFeatures.events = true; _metalFeatures.textureBuffers = true; } @@ -844,7 +844,7 @@ void MVKPhysicalDevice::initMetalFeatures() { if ( [_mtlDevice supportsFeatureSet: MTLFeatureSet_macOS_GPUFamily1_v4] ) { _metalFeatures.mslVersionEnum = MTLLanguageVersion2_1; _metalFeatures.multisampleArrayTextures = true; - MVK_SET_FROM_ENV_OR_BUILD_BOOL(_metalFeatures.events, MVK_ALLOW_METAL_EVENTS); + _metalFeatures.events = true; _metalFeatures.memoryBarriers = true; _metalFeatures.textureBuffers = true; } @@ -1899,6 +1899,19 @@ void MVKDevice::destroySemaphore(MVKSemaphore* mvkSem4, mvkSem4->destroy(); } +MVKEvent* MVKDevice::createEvent(const VkEventCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator) { + if (_pMetalFeatures->events) { + return new MVKEventNative(this, pCreateInfo); + } else { + return new MVKEventEmulated(this, pCreateInfo); + } +} + +void MVKDevice::destroyEvent(MVKEvent* mvkEvent, const VkAllocationCallbacks* pAllocator) { + mvkEvent->destroy(); +} + MVKQueryPool* MVKDevice::createQueryPool(const VkQueryPoolCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator) { switch (pCreateInfo->queryType) { @@ -2301,6 +2314,11 @@ void MVKDevice::initPhysicalDevice(MVKPhysicalDevice* physicalDevice, const VkDe _pProperties = &_physicalDevice->_properties; _pMemoryProperties = &_physicalDevice->_memoryProperties; + _useMTLEventsForSemaphores = MVK_ALLOW_METAL_EVENTS; + if (_pMetalFeatures->events) { + MVK_SET_FROM_ENV_OR_BUILD_BOOL(_useMTLEventsForSemaphores, MVK_ALLOW_METAL_EVENTS); + } + #if MVK_MACOS // If we have selected a high-power GPU and want to force the window system // to use it, force the window system to use a high-power GPU by calling the diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm index 4f2683e2..972309f7 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm +++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm @@ -1286,7 +1286,7 @@ void MVKSwapchainImage::signalWhenAvailable(MVKSemaphore* semaphore, MVKFence* f if (_availability.isAvailable) { _availability.isAvailable = false; signal(signaler); - if (_device->_pMetalFeatures->events) { + if (_device->_useMTLEventsForSemaphores) { // Unfortunately, we can't assume we have an MTLSharedEvent here. // This means we need to execute a command on the device to signal // the semaphore. Alternatively, we could always use an MTLSharedEvent, @@ -1310,7 +1310,7 @@ void MVKSwapchainImage::signalWhenAvailable(MVKSemaphore* semaphore, MVKFence* f // Signal either or both of the semaphore and fence in the specified tracker pair. void MVKSwapchainImage::signal(MVKSwapchainSignaler& signaler) { - if (signaler.first && !_device->_pMetalFeatures->events) { signaler.first->signal(); } + if (signaler.first && !_device->_useMTLEventsForSemaphores) { signaler.first->signal(); } if (signaler.second) { signaler.second->signal(); } } @@ -1367,7 +1367,7 @@ void MVKSwapchainImage::presentCAMetalDrawable(id mtlCmdBuff) if (scName) { [mtlCmdBuff popDebugGroup]; } resetMetalSurface(); - if (_device->_pMetalFeatures->events && !_availabilitySignalers.empty()) { + if (_device->_useMTLEventsForSemaphores && !_availabilitySignalers.empty()) { // Signal the semaphore device-side. _availabilitySignalers.front().first->encodeSignal(mtlCmdBuff); } diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKInstance.mm b/MoltenVK/MoltenVK/GPUObjects/MVKInstance.mm index 0bb9bef6..31558bb4 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKInstance.mm +++ b/MoltenVK/MoltenVK/GPUObjects/MVKInstance.mm @@ -644,6 +644,21 @@ void MVKInstance::logVersions() { } void MVKInstance::initConfig() { + +// The default value for MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS actually depends on whether +// MTLEvents are supported, becuase if MTLEvents are not supported, then synchronous queues +// should be turned off by default to ensure , whereas if MTLEvents are supported, we want +// sychronous queues for better behaviour. The app can of course still override this default +// behaviour by setting the env var, or the config directly. +#undef MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS +#define MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS syncQueueSubmits +#if MVK_MACOS + bool syncQueueSubmits = mvkOSVersion() >= 10.14; // Support for MTLEvents +#endif +#if MVK_IOS + bool syncQueueSubmits = mvkOSVersion() >= 12.0; // Support for MTLEvents +#endif + MVK_SET_FROM_ENV_OR_BUILD_BOOL( _mvkConfig.debugMode, MVK_DEBUG); MVK_SET_FROM_ENV_OR_BUILD_BOOL( _mvkConfig.shaderConversionFlipVertexY, MVK_CONFIG_SHADER_CONVERSION_FLIP_VERTEX_Y); MVK_SET_FROM_ENV_OR_BUILD_BOOL( _mvkConfig.synchronousQueueSubmits, MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS); diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm b/MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm index f0cc7f8b..b0b4dc60 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm +++ b/MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm @@ -224,7 +224,7 @@ void MVKQueueCommandBufferSubmission::execute() { MVKDevice* mvkDev = _queue->getDevice(); // If the device supports it, wait for any semaphores on the device. - if (mvkDev->_pMetalFeatures->events && _isAwaitingSemaphores) { + if (mvkDev->_useMTLEventsForSemaphores && _isAwaitingSemaphores) { _isAwaitingSemaphores = false; for (auto* ws : _waitSemaphores) { ws->encodeWait(getActiveMTLCommandBuffer()); @@ -239,7 +239,7 @@ void MVKQueueCommandBufferSubmission::execute() { if (_fence || _isSignalingSemaphores) { getActiveMTLCommandBuffer(); } // If the device supports it, signal all semaphores on the device. - if (mvkDev->_pMetalFeatures->events && _isSignalingSemaphores) { + if (mvkDev->_useMTLEventsForSemaphores && _isSignalingSemaphores) { _isSignalingSemaphores = false; for (auto* ss : _signalSemaphores) { ss->encodeSignal(getActiveMTLCommandBuffer()); @@ -354,7 +354,7 @@ void MVKQueuePresentSurfaceSubmission::execute() { // If there are semaphores and this device supports MTLEvent, we must present // with a command buffer in order to synchronize with the semaphores. MVKDevice* mvkDev = _queue->getDevice(); - if (mvkDev->_pMetalFeatures->events && !_waitSemaphores.empty()) { + if (mvkDev->_useMTLEventsForSemaphores && !_waitSemaphores.empty()) { // Create a command buffer, have it wait for the semaphores, then present // surfaces via the command buffer. id mtlCmdBuff = getMTLCommandBuffer(); diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSync.h b/MoltenVK/MoltenVK/GPUObjects/MVKSync.h index 24ac84fd..98870023 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKSync.h +++ b/MoltenVK/MoltenVK/GPUObjects/MVKSync.h @@ -60,6 +60,9 @@ public: */ 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. @@ -81,7 +84,7 @@ public: * * 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 = true). + * 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. */ @@ -246,6 +249,84 @@ private: }; +#pragma mark - +#pragma mark MVKEvent + +/** Represents a Vulkan semaphore. */ +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 mtlCmdBuff, bool status) = 0; + + /** Encodes an operation to block command buffer operation until this event is signaled. */ + virtual void encodeWait(id mtlCmdBuff) = 0; + + +#pragma mark Construction + + MVKEvent(MVKDevice* device, const VkEventCreateInfo* pCreateInfo) : MVKVulkanAPIDeviceObject(device) {} + +protected: + void propogateDebugName() 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 mtlCmdBuff, bool status) override; + void encodeWait(id mtlCmdBuff) override; + + MVKEventNative(MVKDevice* device, const VkEventCreateInfo* pCreateInfo); + + ~MVKEventNative() override; + +protected: + id _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 mtlCmdBuff, bool status) override; + void encodeWait(id mtlCmdBuff) override; + + MVKEventEmulated(MVKDevice* device, const VkEventCreateInfo* pCreateInfo); + +protected: + MVKSemaphoreImpl _blocker; + bool _inlineSignalStatus; +}; + + #pragma mark - #pragma mark Support functions diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSync.mm b/MoltenVK/MoltenVK/GPUObjects/MVKSync.mm index f0a1b1cf..9fb29bec 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKSync.mm +++ b/MoltenVK/MoltenVK/GPUObjects/MVKSync.mm @@ -46,6 +46,11 @@ void MVKSemaphoreImpl::reserve() { _reservationCount++; } +bool MVKSemaphoreImpl::isReserved() { + lock_guard lock(_lock); + return !isClear(); +} + bool MVKSemaphoreImpl::wait(uint64_t timeout, bool reserveAgain) { unique_lock lock(_lock); @@ -97,7 +102,7 @@ void MVKSemaphore::encodeSignal(id cmdBuff) { MVKSemaphore::MVKSemaphore(MVKDevice* device, const VkSemaphoreCreateInfo* pCreateInfo) : MVKVulkanAPIDeviceObject(device), _blocker(false, 1), _mtlEvent(nil), _mtlEventValue(1) { - if (device->_pMetalFeatures->events) { + if (device->_useMTLEventsForSemaphores) { _mtlEvent = [device->getMTLDevice() newEvent]; } } @@ -155,6 +160,76 @@ bool MVKFence::getIsSignaled() { } +#pragma mark - +#pragma mark MVKEventNative + +// Odd == set / Even == reset. +bool MVKEventNative::isSet() { return _mtlEvent.signaledValue & 1; } + +void MVKEventNative::signal(bool status) { + if (isSet() != status) { + _mtlEvent.signaledValue += 1; + } +} + +void MVKEventNative::encodeSignal(id mtlCmdBuff, bool status) { + if (isSet() != status) { + [mtlCmdBuff encodeSignalEvent: _mtlEvent value: _mtlEvent.signaledValue + 1]; + } +} + +void MVKEventNative::encodeWait(id mtlCmdBuff) { + if ( !isSet() ) { + [mtlCmdBuff encodeWaitForEvent: _mtlEvent value: _mtlEvent.signaledValue + 1]; + } +} + +MVKEventNative::MVKEventNative(MVKDevice* device, const VkEventCreateInfo* pCreateInfo) : MVKEvent(device, pCreateInfo) { + _mtlEvent = [_device->getMTLDevice() newSharedEvent]; // retained +} + +MVKEventNative::~MVKEventNative() { + [_mtlEvent release]; +} + + +#pragma mark - +#pragma mark MVKEventEmulated + +bool MVKEventEmulated::isSet() { return !_blocker.isReserved(); } + +void MVKEventEmulated::signal(bool status) { + if (status) { + _blocker.release(); + } else { + _blocker.reserve(); + } +} + +void MVKEventEmulated::encodeSignal(id mtlCmdBuff, bool status) { + if (status) { + [mtlCmdBuff addCompletedHandler: ^(id mcb) { _blocker.release(); }]; + } else { + _blocker.reserve(); + } + + // An encoded signal followed by an encoded wait should cause the wait to be skipped. + // However, because encoding a signal will not release the blocker until the command buffer + // is finished executing (so the CPU can tell when it really is done) it is possible that + // the encoded wait will block when it shouldn't. To avoid that, we keep track of whether + // the most recent encoded signal was set or reset, so the next encoded wait knows whether + // to really wait or not. + _inlineSignalStatus = status; +} + +void MVKEventEmulated::encodeWait(id mtlCmdBuff) { + if ( !_inlineSignalStatus ) { _blocker.wait(); } +} + +MVKEventEmulated::MVKEventEmulated(MVKDevice* device, const VkEventCreateInfo* pCreateInfo) : + MVKEvent(device, pCreateInfo), _blocker(false, 1), _inlineSignalStatus(false) {} + + #pragma mark - #pragma mark Support functions diff --git a/MoltenVK/MoltenVK/Utility/MVKEnvironment.h b/MoltenVK/MoltenVK/Utility/MVKEnvironment.h index dbbba16b..56471ad5 100644 --- a/MoltenVK/MoltenVK/Utility/MVKEnvironment.h +++ b/MoltenVK/MoltenVK/Utility/MVKEnvironment.h @@ -146,7 +146,7 @@ # define MVK_CONFIG_FORCE_LOW_POWER_GPU 0 #endif -/** Allow the use of Metal events for Vulkan synchronizations such as VkSemaphores. Disabled by default. */ +/** Allow the use of Metal events for VkSemaphore synchronization behaviour. Disabled by default. */ #ifndef MVK_ALLOW_METAL_EVENTS # define MVK_ALLOW_METAL_EVENTS 0 #endif diff --git a/MoltenVK/MoltenVK/Vulkan/vulkan.mm b/MoltenVK/MoltenVK/Vulkan/vulkan.mm index 3522f87d..1604d241 100644 --- a/MoltenVK/MoltenVK/Vulkan/vulkan.mm +++ b/MoltenVK/MoltenVK/Vulkan/vulkan.mm @@ -651,9 +651,10 @@ MVK_PUBLIC_SYMBOL VkResult vkCreateEvent( VkEvent* pEvent) { MVKTraceVulkanCallStart(); - //VK_DEBUG_REPORT_OBJECT_TYPE_EVENT_EXT MVKDevice* mvkDev = MVKDevice::getMVKDevice(device); - VkResult rslt = mvkDev->reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateEvent(): Vukan events are not supported."); + MVKEvent* mvkEvent = mvkDev->createEvent(pCreateInfo, pAllocator); + *pEvent = (VkEvent)mvkEvent; + VkResult rslt = mvkEvent->getConfigurationResult(); MVKTraceVulkanCallEnd(); return rslt; } @@ -666,7 +667,7 @@ MVK_PUBLIC_SYMBOL void vkDestroyEvent( MVKTraceVulkanCallStart(); if ( !event ) { return; } MVKDevice* mvkDev = MVKDevice::getMVKDevice(device); - mvkDev->reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkDestroyEvent(): Vukan events are not supported."); + mvkDev->destroyEvent((MVKEvent*)event, pAllocator); MVKTraceVulkanCallEnd(); } @@ -675,8 +676,8 @@ MVK_PUBLIC_SYMBOL VkResult vkGetEventStatus( VkEvent event) { MVKTraceVulkanCallStart(); - MVKDevice* mvkDev = MVKDevice::getMVKDevice(device); - VkResult rslt = mvkDev->reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkGetEventStatus(): Vukan events are not supported."); + MVKEvent* mvkEvent = (MVKEvent*)event; + VkResult rslt = mvkEvent->isSet() ? VK_EVENT_SET : VK_EVENT_RESET; MVKTraceVulkanCallEnd(); return rslt; } @@ -686,10 +687,10 @@ MVK_PUBLIC_SYMBOL VkResult vkSetEvent( VkEvent event) { MVKTraceVulkanCallStart(); - MVKDevice* mvkDev = MVKDevice::getMVKDevice(device); - VkResult rslt = mvkDev->reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkSetEvent(): Vukan events are not supported."); + MVKEvent* mvkEvent = (MVKEvent*)event; + mvkEvent->signal(true); MVKTraceVulkanCallEnd(); - return rslt; + return VK_SUCCESS; } MVK_PUBLIC_SYMBOL VkResult vkResetEvent( @@ -697,10 +698,10 @@ MVK_PUBLIC_SYMBOL VkResult vkResetEvent( VkEvent event) { MVKTraceVulkanCallStart(); - MVKDevice* mvkDev = MVKDevice::getMVKDevice(device); - VkResult rslt = mvkDev->reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkResetEvent(): Vukan events are not supported."); + MVKEvent* mvkEvent = (MVKEvent*)event; + mvkEvent->signal(false); MVKTraceVulkanCallEnd(); - return rslt; + return VK_SUCCESS; } MVK_PUBLIC_SYMBOL VkResult vkCreateQueryPool( @@ -1709,7 +1710,7 @@ MVK_PUBLIC_SYMBOL void vkCmdSetEvent( MVKTraceVulkanCallStart(); MVKCommandBuffer* cmdBuff = MVKCommandBuffer::getMVKCommandBuffer(commandBuffer); - cmdBuff->reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdSetEvent(): Vukan events are not supported."); + mvkCmdSetEvent(cmdBuff, event, stageMask); MVKTraceVulkanCallEnd(); } @@ -1720,7 +1721,7 @@ MVK_PUBLIC_SYMBOL void vkCmdResetEvent( MVKTraceVulkanCallStart(); MVKCommandBuffer* cmdBuff = MVKCommandBuffer::getMVKCommandBuffer(commandBuffer); - cmdBuff->reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdResetEvent(): Vukan events are not supported."); + mvkCmdResetEvent(cmdBuff, event, stageMask); MVKTraceVulkanCallEnd(); } @@ -1739,7 +1740,11 @@ MVK_PUBLIC_SYMBOL void vkCmdWaitEvents( MVKTraceVulkanCallStart(); MVKCommandBuffer* cmdBuff = MVKCommandBuffer::getMVKCommandBuffer(commandBuffer); - cmdBuff->reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdWaitEvents(): Vukan events are not supported."); + mvkCmdWaitEvents(cmdBuff, eventCount, pEvents, + srcStageMask, dstStageMask, + memoryBarrierCount, pMemoryBarriers, + bufferMemoryBarrierCount, pBufferMemoryBarriers, + imageMemoryBarrierCount, pImageMemoryBarriers); MVKTraceVulkanCallEnd(); } @@ -1757,8 +1762,8 @@ MVK_PUBLIC_SYMBOL void vkCmdPipelineBarrier( MVKTraceVulkanCallStart(); MVKCommandBuffer* cmdBuff = MVKCommandBuffer::getMVKCommandBuffer(commandBuffer); - mvkCmdPipelineBarrier(cmdBuff, srcStageMask, dstStageMask, - dependencyFlags, memoryBarrierCount, pMemoryBarriers, + mvkCmdPipelineBarrier(cmdBuff, srcStageMask, dstStageMask, dependencyFlags, + memoryBarrierCount, pMemoryBarriers, bufferMemoryBarrierCount, pBufferMemoryBarriers, imageMemoryBarrierCount, pImageMemoryBarriers); MVKTraceVulkanCallEnd();