diff --git a/src/dawn/native/vulkan/CommandRecordingContext.h b/src/dawn/native/vulkan/CommandRecordingContext.h index b5ced101eb..ab43ae2d71 100644 --- a/src/dawn/native/vulkan/CommandRecordingContext.h +++ b/src/dawn/native/vulkan/CommandRecordingContext.h @@ -14,23 +14,30 @@ #ifndef SRC_DAWN_NATIVE_VULKAN_COMMANDRECORDINGCONTEXT_H_ #define SRC_DAWN_NATIVE_VULKAN_COMMANDRECORDINGCONTEXT_H_ +#include #include #include "dawn/common/vulkan_platform.h" #include "dawn/native/vulkan/BufferVk.h" namespace dawn::native::vulkan { + +class Texture; + // Used to track operations that are handled after recording. // Currently only tracks semaphores, but may be used to do barrier coalescing in the future. struct CommandRecordingContext { VkCommandBuffer commandBuffer = VK_NULL_HANDLE; std::vector waitSemaphores = {}; - std::vector signalSemaphores = {}; // The internal buffers used in the workaround of texture-to-texture copies with compressed // formats. std::vector> tempBuffers; + // External textures that will be eagerly transitioned just before VkSubmit. The textures are + // kept alive by the CommandBuffer so they don't need to be Ref-ed. + std::set externalTexturesForEagerTransition; + // For Device state tracking only. VkCommandPool commandPool = VK_NULL_HANDLE; bool used = false; diff --git a/src/dawn/native/vulkan/DeviceVk.cpp b/src/dawn/native/vulkan/DeviceVk.cpp index 05f3a00b19..662fa5a1cd 100644 --- a/src/dawn/native/vulkan/DeviceVk.cpp +++ b/src/dawn/native/vulkan/DeviceVk.cpp @@ -15,6 +15,7 @@ #include "dawn/native/vulkan/DeviceVk.h" #include "dawn/common/Log.h" +#include "dawn/common/NonCopyable.h" #include "dawn/common/Platform.h" #include "dawn/native/BackendConnection.h" #include "dawn/native/ChainUtils_autogen.h" @@ -47,6 +48,35 @@ namespace dawn::native::vulkan { +namespace { + +// Destroy the semaphore when out of scope. +class ScopedSignalSemaphore : public NonMovable { + public: + ScopedSignalSemaphore(Device* device, VkSemaphore semaphore) + : mDevice(device), mSemaphore(semaphore) {} + ~ScopedSignalSemaphore() { + if (mSemaphore != VK_NULL_HANDLE) { + ASSERT(mDevice); + mDevice->fn.DestroySemaphore(mDevice->GetVkDevice(), mSemaphore, nullptr); + } + } + + VkSemaphore Get() { return mSemaphore; } + VkSemaphore Detach() { + VkSemaphore semaphore = mSemaphore; + mSemaphore = VK_NULL_HANDLE; + return semaphore; + } + VkSemaphore* InitializeInto() { return &mSemaphore; } + + private: + Device* mDevice = nullptr; + VkSemaphore mSemaphore = VK_NULL_HANDLE; +}; + +} // namespace + // static ResultOrError> Device::Create(Adapter* adapter, const DeviceDescriptor* descriptor) { Ref device = AcquireRef(new Device(adapter, descriptor)); @@ -245,6 +275,10 @@ ResourceMemoryAllocator* Device::GetResourceMemoryAllocator() const { return mResourceMemoryAllocator.get(); } +external_semaphore::Service* Device::GetExternalSemaphoreService() const { + return mExternalSemaphoreService.get(); +} + void Device::EnqueueDeferredDeallocation(DescriptorSetAllocator* allocator) { mDescriptorAllocatorsPendingDeallocation.Enqueue(allocator, GetPendingCommandSerial()); } @@ -260,6 +294,19 @@ MaybeError Device::SubmitPendingCommands() { return {}; } + ScopedSignalSemaphore scopedSignalSemaphore(this, VK_NULL_HANDLE); + if (mRecordingContext.externalTexturesForEagerTransition.size() > 0) { + // Create an external semaphore for all external textures that have been used in the pending + // submit. + DAWN_TRY_ASSIGN(*scopedSignalSemaphore.InitializeInto(), + mExternalSemaphoreService->CreateExportableSemaphore()); + } + + // Transition eagerly all used external textures for export. + for (auto* texture : mRecordingContext.externalTexturesForEagerTransition) { + texture->TransitionEagerlyForExport(&mRecordingContext); + } + DAWN_TRY( CheckVkSuccess(fn.EndCommandBuffer(mRecordingContext.commandBuffer), "vkEndCommandBuffer")); @@ -274,9 +321,8 @@ MaybeError Device::SubmitPendingCommands() { submitInfo.pWaitDstStageMask = dstStageMasks.data(); submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &mRecordingContext.commandBuffer; - submitInfo.signalSemaphoreCount = - static_cast(mRecordingContext.signalSemaphores.size()); - submitInfo.pSignalSemaphores = AsVkArray(mRecordingContext.signalSemaphores.data()); + submitInfo.signalSemaphoreCount = (scopedSignalSemaphore.Get() == VK_NULL_HANDLE ? 0 : 1); + submitInfo.pSignalSemaphores = AsVkArray(scopedSignalSemaphore.InitializeInto()); VkFence fence = VK_NULL_HANDLE; DAWN_TRY_ASSIGN(fence, GetUnusedFence()); @@ -293,10 +339,6 @@ MaybeError Device::SubmitPendingCommands() { for (VkSemaphore semaphore : mRecordingContext.waitSemaphores) { mDeleter->DeleteWhenUnused(semaphore); } - for (VkSemaphore semaphore : mRecordingContext.signalSemaphores) { - mDeleter->DeleteWhenUnused(semaphore); - } - IncrementLastSubmittedCommandSerial(); ExecutionSerial lastSubmittedSerial = GetLastSubmittedCommandSerial(); mFencesInFlight.emplace(fence, lastSubmittedSerial); @@ -304,6 +346,26 @@ MaybeError Device::SubmitPendingCommands() { CommandPoolAndBuffer submittedCommands = {mRecordingContext.commandPool, mRecordingContext.commandBuffer}; mCommandsInFlight.Enqueue(submittedCommands, lastSubmittedSerial); + + if (mRecordingContext.externalTexturesForEagerTransition.size() > 0) { + // Export the signal semaphore. + ExternalSemaphoreHandle semaphoreHandle; + DAWN_TRY_ASSIGN(semaphoreHandle, + mExternalSemaphoreService->ExportSemaphore(scopedSignalSemaphore.Get())); + // The ownership of signal semaphore has been transferred, we no longer need to track it. + scopedSignalSemaphore.Detach(); + // Update all external textures, eagerly transitioned in the submit, with the exported + // handle, and the duplicated handles. + bool first = true; + for (auto* texture : mRecordingContext.externalTexturesForEagerTransition) { + ExternalSemaphoreHandle handle = + (first ? semaphoreHandle + : mExternalSemaphoreService->DuplicateHandle(semaphoreHandle)); + first = false; + texture->UpdateExternalSemaphoreHandle(handle); + } + } + mRecordingContext = CommandRecordingContext(); DAWN_TRY(PrepareRecordingContext()); @@ -770,7 +832,6 @@ MaybeError Device::ImportExternalImage(const ExternalImageDescriptorVk* descript ExternalMemoryHandle memoryHandle, VkImage image, const std::vector& waitHandles, - VkSemaphore* outSignalSemaphore, VkDeviceMemory* outAllocation, std::vector* outWaitSemaphores) { const TextureDescriptor* textureDescriptor = FromAPI(descriptor->cTextureDescriptor); @@ -794,9 +855,6 @@ MaybeError Device::ImportExternalImage(const ExternalImageDescriptorVk* descript VK_IMAGE_CREATE_ALIAS_BIT_KHR), "External memory usage not supported"); - // Create an external semaphore to signal when the texture is done being used - DAWN_TRY_ASSIGN(*outSignalSemaphore, mExternalSemaphoreService->CreateExportableSemaphore()); - // Import the external image's memory external_memory::MemoryImportParams importParams; DAWN_TRY_ASSIGN(importParams, mExternalMemoryService->GetMemoryImportParams(descriptor, image)); @@ -821,15 +879,12 @@ bool Device::SignalAndExportExternalTexture( return !ConsumedError([&]() -> MaybeError { DAWN_TRY(ValidateObject(texture)); - VkSemaphore signalSemaphore; + ExternalSemaphoreHandle semaphoreHandle; VkImageLayout releasedOldLayout; VkImageLayout releasedNewLayout; - DAWN_TRY(texture->ExportExternalTexture(desiredLayout, &signalSemaphore, &releasedOldLayout, + DAWN_TRY(texture->ExportExternalTexture(desiredLayout, &semaphoreHandle, &releasedOldLayout, &releasedNewLayout)); - ExternalSemaphoreHandle semaphoreHandle; - DAWN_TRY_ASSIGN(semaphoreHandle, - mExternalSemaphoreService->ExportSemaphore(signalSemaphore)); semaphoreHandles->push_back(semaphoreHandle); info->releasedOldLayout = releasedOldLayout; info->releasedNewLayout = releasedNewLayout; @@ -856,7 +911,6 @@ TextureBase* Device::CreateTextureWrappingVulkanImage( return nullptr; } - VkSemaphore signalSemaphore = VK_NULL_HANDLE; VkDeviceMemory allocation = VK_NULL_HANDLE; std::vector waitSemaphores; waitSemaphores.reserve(waitHandles.size()); @@ -869,18 +923,13 @@ TextureBase* Device::CreateTextureWrappingVulkanImage( mExternalMemoryService.get()), &result) || ConsumedError(ImportExternalImage(descriptor, memoryHandle, result->GetHandle(), - waitHandles, &signalSemaphore, &allocation, - &waitSemaphores)) || - ConsumedError( - result->BindExternalMemory(descriptor, signalSemaphore, allocation, waitSemaphores))) { + waitHandles, &allocation, &waitSemaphores)) || + ConsumedError(result->BindExternalMemory(descriptor, allocation, waitSemaphores))) { // Delete the Texture if it was created if (result != nullptr) { result->Release(); } - // Clear the signal semaphore - fn.DestroySemaphore(GetVkDevice(), signalSemaphore, nullptr); - // Clear image memory fn.FreeMemory(GetVkDevice(), allocation, nullptr); @@ -1015,11 +1064,6 @@ void Device::DestroyImpl() { } mRecordingContext.waitSemaphores.clear(); - for (VkSemaphore semaphore : mRecordingContext.signalSemaphores) { - fn.DestroySemaphore(mVkDevice, semaphore, nullptr); - } - mRecordingContext.signalSemaphores.clear(); - // Some commands might still be marked as in-flight if we shut down because of a device // loss. Recycle them as unused so that we free them below. RecycleCompletedCommands(); diff --git a/src/dawn/native/vulkan/DeviceVk.h b/src/dawn/native/vulkan/DeviceVk.h index b0638379bc..6e88d4cf86 100644 --- a/src/dawn/native/vulkan/DeviceVk.h +++ b/src/dawn/native/vulkan/DeviceVk.h @@ -61,6 +61,7 @@ class Device final : public DeviceBase { FencedDeleter* GetFencedDeleter() const; RenderPassCache* GetRenderPassCache() const; ResourceMemoryAllocator* GetResourceMemoryAllocator() const; + external_semaphore::Service* GetExternalSemaphoreService() const; CommandRecordingContext* GetPendingRecordingContext(); MaybeError SubmitPendingCommands(); @@ -216,7 +217,6 @@ class Device final : public DeviceBase { ExternalMemoryHandle memoryHandle, VkImage image, const std::vector& waitHandles, - VkSemaphore* outSignalSemaphore, VkDeviceMemory* outAllocation, std::vector* outWaitSemaphores); }; diff --git a/src/dawn/native/vulkan/ExternalHandle.h b/src/dawn/native/vulkan/ExternalHandle.h index c2d1433e4c..7b3ed9d291 100644 --- a/src/dawn/native/vulkan/ExternalHandle.h +++ b/src/dawn/native/vulkan/ExternalHandle.h @@ -24,15 +24,18 @@ namespace dawn::native::vulkan { using ExternalMemoryHandle = int; // File descriptor using ExternalSemaphoreHandle = int; +const ExternalSemaphoreHandle kNullExternalSemaphoreHandle = -1; #elif DAWN_PLATFORM_IS(FUCHSIA) // Really a Zircon vmo handle. using ExternalMemoryHandle = zx_handle_t; // Really a Zircon event handle. using ExternalSemaphoreHandle = zx_handle_t; +const ExternalSemaphoreHandle kNullExternalSemaphoreHandle = ZX_HANDLE_INVALID; #else // Generic types so that the Null service can compile, not used for real handles using ExternalMemoryHandle = void*; using ExternalSemaphoreHandle = void*; +const ExternalSemaphoreHandle kNullExternalSemaphoreHandle = nullptr; #endif } // namespace dawn::native::vulkan diff --git a/src/dawn/native/vulkan/TextureVk.cpp b/src/dawn/native/vulkan/TextureVk.cpp index da74315d5d..49df3ed7f0 100644 --- a/src/dawn/native/vulkan/TextureVk.cpp +++ b/src/dawn/native/vulkan/TextureVk.cpp @@ -783,7 +783,6 @@ void Texture::InitializeForSwapChain(VkImage nativeImage) { } MaybeError Texture::BindExternalMemory(const ExternalImageDescriptorVk* descriptor, - VkSemaphore signalSemaphore, VkDeviceMemory externalMemoryAllocation, std::vector waitSemaphores) { Device* device = ToBackend(GetDevice()); @@ -798,17 +797,53 @@ MaybeError Texture::BindExternalMemory(const ExternalImageDescriptorVk* descript // Success, acquire all the external objects. mExternalAllocation = externalMemoryAllocation; - mSignalSemaphore = signalSemaphore; mWaitRequirements = std::move(waitSemaphores); return {}; } +void Texture::TransitionEagerlyForExport(CommandRecordingContext* recordingContext) { + mExternalState = ExternalState::EagerlyTransitioned; + + Aspect aspects = ComputeAspectsForSubresourceStorage(); + ASSERT(GetNumMipLevels() == 1 && GetArrayLayers() == 1); + SubresourceRange range = {aspects, {0, 1}, {0, 1}}; + + wgpu::TextureUsage usage = mSubresourceLastUsages->Get(aspects, 0, 0); + + std::vector barriers; + VkPipelineStageFlags srcStages = 0; + VkPipelineStageFlags dstStages = 0; + + // Same usage as last. + TransitionUsageAndGetResourceBarrier(usage, range, &barriers, &srcStages, &dstStages); + + ASSERT(barriers.size() == 1); + VkImageMemoryBarrier& barrier = barriers[0]; + // The barrier must be paired with another barrier that will specify the dst access mask on the + // importing queue. + barrier.dstAccessMask = 0; + + if (mDesiredExportLayout != VK_IMAGE_LAYOUT_UNDEFINED) { + barrier.newLayout = mDesiredExportLayout; + } + + Device* device = ToBackend(GetDevice()); + barrier.srcQueueFamilyIndex = device->GetGraphicsQueueFamily(); + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL_KHR; + + // We don't know when the importing queue will need the texture, so pass + // VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT to ensure the barrier happens-before any usage in the + // importing queue. + dstStages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + + device->fn.CmdPipelineBarrier(recordingContext->commandBuffer, srcStages, dstStages, 0, 0, + nullptr, 0, nullptr, 1, &barrier); +} + MaybeError Texture::ExportExternalTexture(VkImageLayout desiredLayout, - VkSemaphore* signalSemaphore, + ExternalSemaphoreHandle* handle, VkImageLayout* releasedOldLayout, VkImageLayout* releasedNewLayout) { - Device* device = ToBackend(GetDevice()); - DAWN_INVALID_IF(mExternalState == ExternalState::Released, "Can't export a signal semaphore from signaled texture %s.", this); @@ -816,8 +851,6 @@ MaybeError Texture::ExportExternalTexture(VkImageLayout desiredLayout, "Can't export a signal semaphore from destroyed or non-external texture %s.", this); - ASSERT(mSignalSemaphore != VK_NULL_HANDLE); - // Release the texture mExternalState = ExternalState::Released; @@ -825,54 +858,28 @@ MaybeError Texture::ExportExternalTexture(VkImageLayout desiredLayout, ASSERT(GetNumMipLevels() == 1 && GetArrayLayers() == 1); wgpu::TextureUsage usage = mSubresourceLastUsages->Get(aspects, 0, 0); - VkImageMemoryBarrier barrier; - barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; - barrier.pNext = nullptr; - barrier.image = GetHandle(); - barrier.subresourceRange.aspectMask = VulkanAspectMask(aspects); - barrier.subresourceRange.baseMipLevel = 0; - barrier.subresourceRange.levelCount = 1; - barrier.subresourceRange.baseArrayLayer = 0; - barrier.subresourceRange.layerCount = 1; - - barrier.srcAccessMask = VulkanAccessFlags(usage, GetFormat()); - barrier.dstAccessMask = 0; // The barrier must be paired with another barrier that will - // specify the dst access mask on the importing queue. - - barrier.oldLayout = VulkanImageLayout(this, usage); - if (desiredLayout == VK_IMAGE_LAYOUT_UNDEFINED) { - // VK_IMAGE_LAYOUT_UNDEFINED is invalid here. We use it as a - // special value to indicate no layout transition should be done. - barrier.newLayout = barrier.oldLayout; - } else { - barrier.newLayout = desiredLayout; - } - - barrier.srcQueueFamilyIndex = device->GetGraphicsQueueFamily(); - barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL_KHR; - - VkPipelineStageFlags srcStages = VulkanPipelineStage(usage, GetFormat()); - VkPipelineStageFlags dstStages = - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; // We don't know when the importing queue will need - // the texture, so pass - // VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT to ensure - // the barrier happens-before any usage in the - // importing queue. - - CommandRecordingContext* recordingContext = device->GetPendingRecordingContext(); - device->fn.CmdPipelineBarrier(recordingContext->commandBuffer, srcStages, dstStages, 0, 0, - nullptr, 0, nullptr, 1, &barrier); - - // Queue submit to signal we are done with the texture - recordingContext->signalSemaphores.push_back(mSignalSemaphore); - DAWN_TRY(device->SubmitPendingCommands()); + VkImageLayout layout = VulkanImageLayout(this, usage); // Write out the layouts and signal semaphore - *releasedOldLayout = barrier.oldLayout; - *releasedNewLayout = barrier.newLayout; - *signalSemaphore = mSignalSemaphore; + *releasedOldLayout = layout; + *releasedNewLayout = (desiredLayout == VK_IMAGE_LAYOUT_UNDEFINED ? layout : desiredLayout); - mSignalSemaphore = VK_NULL_HANDLE; + mDesiredExportLayout = desiredLayout; + + // We have to manually trigger a transition if the texture hasn't been actually used, or the + // desired layout is not VK_IMAGE_LAYOUT_UNDEFINED. + // TODO(dawn:1509): Avoid the empty submit. + if (mExternalSemaphoreHandle == kNullExternalSemaphoreHandle || + desiredLayout != VK_IMAGE_LAYOUT_UNDEFINED) { + Device* device = ToBackend(GetDevice()); + CommandRecordingContext* recordingContext = device->GetPendingRecordingContext(); + recordingContext->externalTexturesForEagerTransition.insert(this); + DAWN_TRY(device->SubmitPendingCommands()); + } + ASSERT(mExternalSemaphoreHandle != kNullExternalSemaphoreHandle); + + *handle = mExternalSemaphoreHandle; + mExternalSemaphoreHandle = kNullExternalSemaphoreHandle; // Destroy the texture so it can't be used again Destroy(); @@ -907,8 +914,11 @@ void Texture::DestroyImpl() { mHandle = VK_NULL_HANDLE; mExternalAllocation = VK_NULL_HANDLE; - // If a signal semaphore exists it should be requested before we delete the texture - ASSERT(mSignalSemaphore == VK_NULL_HANDLE); + + if (mExternalSemaphoreHandle != kNullExternalSemaphoreHandle) { + device->GetExternalSemaphoreService()->CloseHandle(mExternalSemaphoreHandle); + } + mExternalSemaphoreHandle = kNullExternalSemaphoreHandle; } // For Vulkan, we currently run the base destruction code after the internal changes because // of the dependency on the texture state which the base code overwrites too early. @@ -929,7 +939,9 @@ void Texture::TweakTransitionForExternalUsage(CommandRecordingContext* recording // have already added into the vector during current transition. ASSERT(barriers->size() - transitionBarrierStart <= 1); - if (mExternalState == ExternalState::PendingAcquire) { + if (mExternalState == ExternalState::PendingAcquire || + mExternalState == ExternalState::EagerlyTransitioned) { + recordingContext->externalTexturesForEagerTransition.insert(this); if (barriers->size() == transitionBarrierStart) { barriers->push_back(BuildMemoryBarrier( this, wgpu::TextureUsage::None, wgpu::TextureUsage::None, @@ -946,27 +958,35 @@ void Texture::TweakTransitionForExternalUsage(CommandRecordingContext* recording // this. barrier->srcAccessMask = 0; - // This should be the first barrier after import. - ASSERT(barrier->oldLayout == VK_IMAGE_LAYOUT_UNDEFINED); - // Save the desired layout. We may need to transition through an intermediate // |mPendingAcquireLayout| first. VkImageLayout desiredLayout = barrier->newLayout; - bool isInitialized = IsSubresourceContentInitialized(GetAllSubresources()); + if (mExternalState == ExternalState::PendingAcquire) { + bool isInitialized = IsSubresourceContentInitialized(GetAllSubresources()); - // We don't care about the pending old layout if the texture is uninitialized. The - // driver is free to discard it. Also it is invalid to transition to layout UNDEFINED or - // PREINITIALIZED. If the embedder provided no new layout, or we don't care about the - // previous contents, we can skip the layout transition. - // https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#VUID-VkImageMemoryBarrier-newLayout-01198 - if (!isInitialized || mPendingAcquireNewLayout == VK_IMAGE_LAYOUT_UNDEFINED || - mPendingAcquireNewLayout == VK_IMAGE_LAYOUT_PREINITIALIZED) { - barrier->oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; - barrier->newLayout = desiredLayout; + // We don't care about the pending old layout if the texture is uninitialized. The + // driver is free to discard it. Also it is invalid to transition to layout UNDEFINED or + // PREINITIALIZED. If the embedder provided no new layout, or we don't care about the + // previous contents, we can skip the layout transition. + // https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#VUID-VkImageMemoryBarrier-newLayout-01198 + if (!isInitialized || mPendingAcquireNewLayout == VK_IMAGE_LAYOUT_UNDEFINED || + mPendingAcquireNewLayout == VK_IMAGE_LAYOUT_PREINITIALIZED) { + barrier->oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + barrier->newLayout = desiredLayout; + } else { + barrier->oldLayout = mPendingAcquireOldLayout; + barrier->newLayout = mPendingAcquireNewLayout; + } } else { - barrier->oldLayout = mPendingAcquireOldLayout; - barrier->newLayout = mPendingAcquireNewLayout; + // In case of ExternalState::EagerlyTransitioned, the layouts of the texture's queue + // release were always same. So we exactly match that here for the queue acquire. + // The spec text: + // If the transfer is via an image memory barrier, and an image layout transition is + // desired, then the values of oldLayout and newLayout in the release operation's memory + // barrier must be equal to values of oldLayout and newLayout in the acquire operation's + // memory barrier. + barrier->newLayout = barrier->oldLayout; } // If these are unequal, we need an another barrier to transition the layout. @@ -981,9 +1001,8 @@ void Texture::TweakTransitionForExternalUsage(CommandRecordingContext* recording layoutBarrier.oldLayout = barrier->newLayout; layoutBarrier.newLayout = desiredLayout; - // We already transitioned these. layoutBarrier.srcAccessMask = 0; - layoutBarrier.dstAccessMask = 0; + layoutBarrier.dstAccessMask = barrier->dstAccessMask; layoutBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; layoutBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; @@ -1322,6 +1341,14 @@ void Texture::EnsureSubresourceContentInitialized(CommandRecordingContext* recor } } +void Texture::UpdateExternalSemaphoreHandle(ExternalSemaphoreHandle handle) { + if (mExternalSemaphoreHandle != kNullExternalSemaphoreHandle) { + Device* device = ToBackend(GetDevice()); + device->GetExternalSemaphoreService()->CloseHandle(mExternalSemaphoreHandle); + } + mExternalSemaphoreHandle = handle; +} + VkImageLayout Texture::GetCurrentLayoutForSwapChain() const { ASSERT(GetFormat().aspects == Aspect::Color); return VulkanImageLayout(this, mSubresourceLastUsages->Get(Aspect::Color, 0, 0)); diff --git a/src/dawn/native/vulkan/TextureVk.h b/src/dawn/native/vulkan/TextureVk.h index 4be64775c5..4caf11b774 100644 --- a/src/dawn/native/vulkan/TextureVk.h +++ b/src/dawn/native/vulkan/TextureVk.h @@ -24,6 +24,7 @@ #include "dawn/native/Texture.h" #include "dawn/native/vulkan/ExternalHandle.h" #include "dawn/native/vulkan/external_memory/MemoryService.h" +#include "dawn/native/vulkan/external_semaphore/SemaphoreService.h" namespace dawn::native::vulkan { @@ -77,6 +78,9 @@ class Texture final : public TextureBase { VkPipelineStageFlags* srcStages, VkPipelineStageFlags* dstStages); + // Eagerly transition the texture for export. + void TransitionEagerlyForExport(CommandRecordingContext* recordingContext); + void EnsureSubresourceContentInitialized(CommandRecordingContext* recordingContext, const SubresourceRange& range); @@ -85,12 +89,12 @@ class Texture final : public TextureBase { // Binds externally allocated memory to the VkImage and on success, takes ownership of // semaphores. MaybeError BindExternalMemory(const ExternalImageDescriptorVk* descriptor, - VkSemaphore signalSemaphore, VkDeviceMemory externalMemoryAllocation, std::vector waitSemaphores); - + // Update the 'ExternalSemaphoreHandle' to be used for export with the newly submitted one. + void UpdateExternalSemaphoreHandle(ExternalSemaphoreHandle handle); MaybeError ExportExternalTexture(VkImageLayout desiredLayout, - VkSemaphore* signalSemaphore, + ExternalSemaphoreHandle* handle, VkImageLayout* releasedOldLayout, VkImageLayout* releasedNewLayout); @@ -156,14 +160,30 @@ class Texture final : public TextureBase { ResourceMemoryAllocation mMemoryAllocation; VkDeviceMemory mExternalAllocation = VK_NULL_HANDLE; - enum class ExternalState { InternalOnly, PendingAcquire, Acquired, Released }; + // The states of an external texture: + // InternalOnly: Not initialized as an external texture yet. + // PendingAcquire: Intialized as an external texture already, but unavailable for access yet. + // Acquired: Ready for access. + // EagerlyTransitioned: The texture has ever been used, and eagerly transitioned for export. + // Now it can be acquired for access again, or directly exported. Released: The texture has + // been destoried, and should no longer be used. + enum class ExternalState { + InternalOnly, + PendingAcquire, + Acquired, + EagerlyTransitioned, + Released + }; ExternalState mExternalState = ExternalState::InternalOnly; ExternalState mLastExternalState = ExternalState::InternalOnly; VkImageLayout mPendingAcquireOldLayout; VkImageLayout mPendingAcquireNewLayout; - VkSemaphore mSignalSemaphore = VK_NULL_HANDLE; + VkImageLayout mDesiredExportLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + ExternalSemaphoreHandle mExternalSemaphoreHandle = kNullExternalSemaphoreHandle; + std::vector mWaitRequirements; // Note that in early Vulkan versions it is not possible to transition depth and stencil diff --git a/src/dawn/native/vulkan/external_semaphore/SemaphoreService.h b/src/dawn/native/vulkan/external_semaphore/SemaphoreService.h index e27689fbf4..18043e405d 100644 --- a/src/dawn/native/vulkan/external_semaphore/SemaphoreService.h +++ b/src/dawn/native/vulkan/external_semaphore/SemaphoreService.h @@ -48,6 +48,12 @@ class Service { // Export a VkSemaphore into an external handle ResultOrError ExportSemaphore(VkSemaphore semaphore); + // Duplicate a new external handle from the given one. + ExternalSemaphoreHandle DuplicateHandle(ExternalSemaphoreHandle handle); + + // Close an external handle. + void CloseHandle(ExternalSemaphoreHandle handle); + private: Device* mDevice = nullptr; diff --git a/src/dawn/native/vulkan/external_semaphore/SemaphoreServiceFD.cpp b/src/dawn/native/vulkan/external_semaphore/SemaphoreServiceFD.cpp index 9c1d923dae..d424e62537 100644 --- a/src/dawn/native/vulkan/external_semaphore/SemaphoreServiceFD.cpp +++ b/src/dawn/native/vulkan/external_semaphore/SemaphoreServiceFD.cpp @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include #include "dawn/native/vulkan/AdapterVk.h" @@ -135,4 +136,15 @@ ResultOrError Service::ExportSemaphore(VkSemaphore sema return fd; } +ExternalSemaphoreHandle Service::DuplicateHandle(ExternalSemaphoreHandle handle) { + int fd = dup(handle); + ASSERT(fd >= 0); + return fd; +} + +void Service::CloseHandle(ExternalSemaphoreHandle handle) { + int ret = close(handle); + ASSERT(ret == 0); +} + } // namespace dawn::native::vulkan::external_semaphore diff --git a/src/dawn/native/vulkan/external_semaphore/SemaphoreServiceNull.cpp b/src/dawn/native/vulkan/external_semaphore/SemaphoreServiceNull.cpp index 1963524c82..370db872af 100644 --- a/src/dawn/native/vulkan/external_semaphore/SemaphoreServiceNull.cpp +++ b/src/dawn/native/vulkan/external_semaphore/SemaphoreServiceNull.cpp @@ -47,4 +47,10 @@ ResultOrError Service::ExportSemaphore(VkSemaphore sema return DAWN_UNIMPLEMENTED_ERROR("Using null semaphore service to interop inside Vulkan"); } +ExternalSemaphoreHandle Service::DuplicateHandle(ExternalSemaphoreHandle handle) { + return kNullExternalSemaphoreHandle; +} + +void Service::CloseHandle(ExternalSemaphoreHandle handle) {} + } // namespace dawn::native::vulkan::external_semaphore diff --git a/src/dawn/native/vulkan/external_semaphore/SemaphoreServiceZirconHandle.cpp b/src/dawn/native/vulkan/external_semaphore/SemaphoreServiceZirconHandle.cpp index af1f3f3e77..12d776e311 100644 --- a/src/dawn/native/vulkan/external_semaphore/SemaphoreServiceZirconHandle.cpp +++ b/src/dawn/native/vulkan/external_semaphore/SemaphoreServiceZirconHandle.cpp @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include #include "dawn/native/vulkan/AdapterVk.h" @@ -129,4 +130,16 @@ ResultOrError Service::ExportSemaphore(VkSemaphore sema return handle; } +ExternalSemaphoreHandle Service::DuplicateHandle(ExternalSemaphoreHandle handle) { + zx_handle_t out_handle = ZX_HANDLE_INVALID; + zx_status_t status = zx_handle_duplicate(handle, ZX_RIGHT_SAME_RIGHTS, &out_handle); + ASSERT(status == ZX_OK); + return out_handle; +} + +void Service::CloseHandle(ExternalSemaphoreHandle handle) { + zx_status_t status = zx_handle_close(handle); + ASSERT(status == ZX_OK); +} + } // namespace dawn::native::vulkan::external_semaphore diff --git a/src/dawn/tests/white_box/VulkanImageWrappingTests.cpp b/src/dawn/tests/white_box/VulkanImageWrappingTests.cpp index 46cb1b75c1..283a552547 100644 --- a/src/dawn/tests/white_box/VulkanImageWrappingTests.cpp +++ b/src/dawn/tests/white_box/VulkanImageWrappingTests.cpp @@ -110,7 +110,7 @@ class VulkanImageWrappingTestBase : public DawnTest { // assertion failure void IgnoreSignalSemaphore(wgpu::Texture wrappedTexture) { ExternalImageExportInfoVkForTesting exportInfo; - bool result = mBackend->ExportImage(wrappedTexture, VK_IMAGE_LAYOUT_GENERAL, &exportInfo); + bool result = mBackend->ExportImage(wrappedTexture, VK_IMAGE_LAYOUT_UNDEFINED, &exportInfo); ASSERT(result); } @@ -202,7 +202,7 @@ TEST_P(VulkanImageWrappingValidationTests, DoubleSignalSemaphoreExport) { ExternalImageExportInfoVkForTesting exportInfo; ASSERT_DEVICE_ERROR(bool success = - mBackend->ExportImage(texture, VK_IMAGE_LAYOUT_GENERAL, &exportInfo)); + mBackend->ExportImage(texture, VK_IMAGE_LAYOUT_UNDEFINED, &exportInfo)); ASSERT_FALSE(success); ASSERT_EQ(exportInfo.semaphores.size(), 0u); } @@ -214,7 +214,7 @@ TEST_P(VulkanImageWrappingValidationTests, NormalTextureSignalSemaphoreExport) { ExternalImageExportInfoVkForTesting exportInfo; ASSERT_DEVICE_ERROR(bool success = - mBackend->ExportImage(texture, VK_IMAGE_LAYOUT_GENERAL, &exportInfo)); + mBackend->ExportImage(texture, VK_IMAGE_LAYOUT_UNDEFINED, &exportInfo)); ASSERT_FALSE(success); ASSERT_EQ(exportInfo.semaphores.size(), 0u); } @@ -227,7 +227,7 @@ TEST_P(VulkanImageWrappingValidationTests, DestroyedTextureSignalSemaphoreExport ExternalImageExportInfoVkForTesting exportInfo; ASSERT_DEVICE_ERROR(bool success = - mBackend->ExportImage(texture, VK_IMAGE_LAYOUT_GENERAL, &exportInfo)); + mBackend->ExportImage(texture, VK_IMAGE_LAYOUT_UNDEFINED, &exportInfo)); ASSERT_FALSE(success); ASSERT_EQ(exportInfo.semaphores.size(), 0u); }