Add wrapping for using external vulkan images as textures
This change adds platform-dependent services that handle creating
semaphores and importing image memory. Then, we use them to wrap a
texture from an outside source, and release a signal semaphore back
when we're done with it. This will be used to allow chrome to render
dawn on Vulkan platforms.
Bug: chromium:976495
Change-Id: I9f07eaf436e10aa6bd88cffdc74fd23834d62ee0
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/8340
Commit-Queue: Idan Raiter <idanr@google.com>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index aed63b1..4375a36 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -362,6 +362,7 @@
"src/dawn_native/vulkan/ComputePipelineVk.h",
"src/dawn_native/vulkan/DeviceVk.cpp",
"src/dawn_native/vulkan/DeviceVk.h",
+ "src/dawn_native/vulkan/ExternalHandle.h",
"src/dawn_native/vulkan/FencedDeleter.cpp",
"src/dawn_native/vulkan/FencedDeleter.h",
"src/dawn_native/vulkan/Forward.h",
@@ -395,7 +396,21 @@
"src/dawn_native/vulkan/VulkanFunctions.h",
"src/dawn_native/vulkan/VulkanInfo.cpp",
"src/dawn_native/vulkan/VulkanInfo.h",
+ "src/dawn_native/vulkan/external_memory/MemoryService.h",
+ "src/dawn_native/vulkan/external_semaphore/SemaphoreService.h",
]
+
+ if (is_linux) {
+ sources += [
+ "src/dawn_native/vulkan/external_memory/MemoryServiceOpaqueFD.cpp",
+ "src/dawn_native/vulkan/external_semaphore/SemaphoreServiceOpaqueFD.cpp",
+ ]
+ } else {
+ sources += [
+ "src/dawn_native/vulkan/external_memory/MemoryServiceNull.cpp",
+ "src/dawn_native/vulkan/external_semaphore/SemaphoreServiceNull.cpp",
+ ]
+ }
}
}
@@ -667,8 +682,9 @@
}
}
-test("dawn_end2end_tests") {
+source_set("dawn_end2end_tests_sources") {
configs += [ "${dawn_root}/src/common:dawn_internal" ]
+ testonly = true
deps = [
":dawn_utils",
@@ -681,7 +697,6 @@
]
sources = [
- "src/tests/DawnTest.cpp",
"src/tests/DawnTest.h",
"src/tests/end2end/BasicTests.cpp",
"src/tests/end2end/BindGroupTests.cpp",
@@ -729,6 +744,59 @@
libs += [ "IOSurface.framework" ]
}
+}
+
+source_set("dawn_white_box_tests_sources") {
+ configs += [ "${dawn_root}/src/common:dawn_internal" ]
+ testonly = true
+
+ deps = [
+ ":dawn_utils",
+ ":libdawn_native",
+ ":libdawn_native_sources",
+ ":libdawn_wire",
+ "${dawn_root}/src/common",
+ "${dawn_root}/src/dawn:libdawn",
+ "third_party:glfw",
+ "third_party:gmock_and_gtest",
+ ]
+
+ sources = [
+ "src/tests/DawnTest.h",
+ ]
+
+ if (dawn_enable_vulkan) {
+ deps += [ "third_party:vulkan_headers" ]
+
+ if (is_linux) {
+ sources += [ "src/tests/white_box/VulkanImageWrappingTests.cpp" ]
+ }
+ }
+
+ libs = []
+}
+
+test("dawn_end2end_tests") {
+ configs += [ "${dawn_root}/src/common:dawn_internal" ]
+
+ deps = [
+ ":dawn_end2end_tests_sources",
+ ":dawn_utils",
+ ":dawn_white_box_tests_sources",
+ ":libdawn_native",
+ ":libdawn_wire",
+ "${dawn_root}/src/common",
+ "${dawn_root}/src/dawn:libdawn",
+ "third_party:glfw",
+ "third_party:gmock_and_gtest",
+ ]
+
+ sources = [
+ "src/tests/DawnTest.cpp",
+ "src/tests/DawnTest.h",
+ ]
+
+ libs = []
# When building inside Chromium, use their gtest main function because it is
# needed to run in swarming correctly.
diff --git a/src/dawn_native/Texture.cpp b/src/dawn_native/Texture.cpp
index 224a10d..87433ad 100644
--- a/src/dawn_native/Texture.cpp
+++ b/src/dawn_native/Texture.cpp
@@ -208,6 +208,9 @@
MaybeError ValidateTextureDescriptor(const DeviceBase* device,
const TextureDescriptor* descriptor) {
+ if (descriptor == nullptr) {
+ return DAWN_VALIDATION_ERROR("Texture descriptor is nullptr");
+ }
if (descriptor->nextInChain != nullptr) {
return DAWN_VALIDATION_ERROR("nextInChain must be nullptr");
}
diff --git a/src/dawn_native/vulkan/DeviceVk.cpp b/src/dawn_native/vulkan/DeviceVk.cpp
index fc3b152..354809f 100644
--- a/src/dawn_native/vulkan/DeviceVk.cpp
+++ b/src/dawn_native/vulkan/DeviceVk.cpp
@@ -18,7 +18,9 @@
#include "dawn_native/BackendConnection.h"
#include "dawn_native/Commands.h"
#include "dawn_native/DynamicUploader.h"
+#include "dawn_native/Error.h"
#include "dawn_native/ErrorData.h"
+#include "dawn_native/VulkanBackend.h"
#include "dawn_native/vulkan/AdapterVk.h"
#include "dawn_native/vulkan/BackendVk.h"
#include "dawn_native/vulkan/BindGroupLayoutVk.h"
@@ -68,6 +70,9 @@
mMemoryAllocator = std::make_unique<MemoryAllocator>(this);
mRenderPassCache = std::make_unique<RenderPassCache>(this);
+ mExternalMemoryService = std::make_unique<external_memory::Service>(this);
+ mExternalSemaphoreService = std::make_unique<external_semaphore::Service>(this);
+
return {};
}
@@ -568,4 +573,91 @@
return {};
}
+
+ MaybeError Device::ImportExternalImage(const ExternalImageDescriptor* descriptor,
+ ExternalMemoryHandle memoryHandle,
+ const std::vector<ExternalSemaphoreHandle>& waitHandles,
+ VkSemaphore* outSignalSemaphore,
+ VkDeviceMemory* outAllocation,
+ std::vector<VkSemaphore>* outWaitSemaphores) {
+ // Check services support this combination of handle type / image info
+ if (!mExternalSemaphoreService->Supported()) {
+ return DAWN_VALIDATION_ERROR("External semaphore usage not supported");
+ }
+ if (!mExternalMemoryService->Supported()) {
+ return DAWN_VALIDATION_ERROR("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
+ DAWN_TRY_ASSIGN(*outAllocation,
+ mExternalMemoryService->ImportMemory(
+ memoryHandle, descriptor->allocationSize, descriptor->memoryTypeIndex));
+
+ // Import semaphores we have to wait on before using the texture
+ for (const ExternalSemaphoreHandle& handle : waitHandles) {
+ VkSemaphore semaphore = VK_NULL_HANDLE;
+ DAWN_TRY_ASSIGN(semaphore, mExternalSemaphoreService->ImportSemaphore(handle));
+ outWaitSemaphores->push_back(semaphore);
+ }
+
+ return {};
+ }
+
+ MaybeError Device::SignalAndExportExternalTexture(Texture* texture,
+ ExternalSemaphoreHandle* outHandle) {
+ DAWN_TRY(ValidateObject(texture));
+
+ VkSemaphore outSignalSemaphore;
+ DAWN_TRY(texture->SignalAndDestroy(&outSignalSemaphore));
+
+ // This has to happen right after SignalAndDestroy, since the semaphore will be
+ // deleted when the fenced deleter runs after the queue submission
+ DAWN_TRY_ASSIGN(*outHandle, mExternalSemaphoreService->ExportSemaphore(outSignalSemaphore));
+
+ return {};
+ }
+
+ TextureBase* Device::CreateTextureWrappingVulkanImage(
+ const ExternalImageDescriptor* descriptor,
+ ExternalMemoryHandle memoryHandle,
+ const std::vector<ExternalSemaphoreHandle>& waitHandles) {
+ const TextureDescriptor* textureDescriptor =
+ reinterpret_cast<const TextureDescriptor*>(descriptor->cTextureDescriptor);
+
+ // Initial validation
+ if (ConsumedError(ValidateTextureDescriptor(this, textureDescriptor))) {
+ return nullptr;
+ }
+ if (ConsumedError(ValidateVulkanImageCanBeWrapped(this, textureDescriptor))) {
+ return nullptr;
+ }
+
+ VkSemaphore signalSemaphore = VK_NULL_HANDLE;
+ VkDeviceMemory allocation = VK_NULL_HANDLE;
+ std::vector<VkSemaphore> waitSemaphores;
+ waitSemaphores.reserve(waitHandles.size());
+
+ // If failed, cleanup
+ if (ConsumedError(ImportExternalImage(descriptor, memoryHandle, waitHandles,
+ &signalSemaphore, &allocation, &waitSemaphores))) {
+ // Clear the signal semaphore
+ fn.DestroySemaphore(GetVkDevice(), signalSemaphore, nullptr);
+
+ // Clear image memory
+ fn.FreeMemory(GetVkDevice(), allocation, nullptr);
+
+ // Clear any wait semaphores we were able to import
+ for (VkSemaphore semaphore : waitSemaphores) {
+ fn.DestroySemaphore(GetVkDevice(), semaphore, nullptr);
+ }
+ return nullptr;
+ }
+
+ return new Texture(this, descriptor, textureDescriptor, signalSemaphore, allocation,
+ waitSemaphores);
+ }
}} // namespace dawn_native::vulkan
diff --git a/src/dawn_native/vulkan/DeviceVk.h b/src/dawn_native/vulkan/DeviceVk.h
index be76c16..2aba428 100644
--- a/src/dawn_native/vulkan/DeviceVk.h
+++ b/src/dawn_native/vulkan/DeviceVk.h
@@ -25,6 +25,9 @@
#include "dawn_native/vulkan/VulkanFunctions.h"
#include "dawn_native/vulkan/VulkanInfo.h"
+#include "dawn_native/vulkan/external_memory/MemoryService.h"
+#include "dawn_native/vulkan/external_semaphore/SemaphoreService.h"
+
#include <memory>
#include <queue>
@@ -32,6 +35,7 @@
class Adapter;
class BufferUploader;
+ struct ExternalImageDescriptor;
class FencedDeleter;
class MapRequestTracker;
class MemoryAllocator;
@@ -64,6 +68,14 @@
Serial GetPendingCommandSerial() const override;
void SubmitPendingCommands();
+ TextureBase* CreateTextureWrappingVulkanImage(
+ const ExternalImageDescriptor* descriptor,
+ ExternalMemoryHandle memoryHandle,
+ const std::vector<ExternalSemaphoreHandle>& waitHandles);
+
+ MaybeError SignalAndExportExternalTexture(Texture* texture,
+ ExternalSemaphoreHandle* outHandle);
+
// Dawn API
CommandBufferBase* CreateCommandBuffer(CommandEncoderBase* encoder,
const CommandBufferDescriptor* descriptor) override;
@@ -119,6 +131,9 @@
std::unique_ptr<MemoryAllocator> mMemoryAllocator;
std::unique_ptr<RenderPassCache> mRenderPassCache;
+ std::unique_ptr<external_memory::Service> mExternalMemoryService;
+ std::unique_ptr<external_semaphore::Service> mExternalSemaphoreService;
+
VkFence GetUnusedFence();
void CheckPassedFences();
@@ -143,8 +158,14 @@
SerialQueue<CommandPoolAndBuffer> mCommandsInFlight;
std::vector<CommandPoolAndBuffer> mUnusedCommands;
CommandPoolAndBuffer mPendingCommands;
-
CommandRecordingContext mRecordingContext;
+
+ MaybeError ImportExternalImage(const ExternalImageDescriptor* descriptor,
+ ExternalMemoryHandle memoryHandle,
+ const std::vector<ExternalSemaphoreHandle>& waitHandles,
+ VkSemaphore* outSignalSemaphore,
+ VkDeviceMemory* outAllocation,
+ std::vector<VkSemaphore>* outWaitSemaphores);
};
}} // namespace dawn_native::vulkan
diff --git a/src/dawn_native/vulkan/ExternalHandle.h b/src/dawn_native/vulkan/ExternalHandle.h
new file mode 100644
index 0000000..37a2e21
--- /dev/null
+++ b/src/dawn_native/vulkan/ExternalHandle.h
@@ -0,0 +1,19 @@
+#ifndef DAWNNATIVE_VULKAN_EXTERNALHANDLE_H_
+#define DAWNNATIVE_VULKAN_EXTERNALHANDLE_H_
+
+namespace dawn_native { namespace vulkan {
+
+#ifdef DAWN_PLATFORM_LINUX
+ // File descriptor
+ using ExternalMemoryHandle = int;
+ // File descriptor
+ using ExternalSemaphoreHandle = int;
+#else
+ // Generic types so that the Null service can compile, not used for real handles
+ using ExternalMemoryHandle = void*;
+ using ExternalSemaphoreHandle = void*;
+#endif
+
+}} // namespace dawn_native::vulkan
+
+#endif // DAWNNATIVE_VULKAN_EXTERNALHANDLE_H_
diff --git a/src/dawn_native/vulkan/MemoryAllocator.cpp b/src/dawn_native/vulkan/MemoryAllocator.cpp
index 66a779e..abd53da 100644
--- a/src/dawn_native/vulkan/MemoryAllocator.cpp
+++ b/src/dawn_native/vulkan/MemoryAllocator.cpp
@@ -41,9 +41,7 @@
MemoryAllocator::~MemoryAllocator() {
}
- bool MemoryAllocator::Allocate(VkMemoryRequirements requirements,
- bool mappable,
- DeviceMemoryAllocation* allocation) {
+ int MemoryAllocator::FindBestTypeIndex(VkMemoryRequirements requirements, bool mappable) {
const VulkanDeviceInfo& info = mDevice->GetDeviceInfo();
// Find a suitable memory type for this allocation
@@ -93,6 +91,14 @@
}
}
+ return bestType;
+ }
+
+ bool MemoryAllocator::Allocate(VkMemoryRequirements requirements,
+ bool mappable,
+ DeviceMemoryAllocation* allocation) {
+ int bestType = FindBestTypeIndex(requirements, mappable);
+
// TODO(cwallez@chromium.org): I think the Vulkan spec guarantees this should never happen
if (bestType == -1) {
ASSERT(false);
diff --git a/src/dawn_native/vulkan/MemoryAllocator.h b/src/dawn_native/vulkan/MemoryAllocator.h
index afea7af..56d3350 100644
--- a/src/dawn_native/vulkan/MemoryAllocator.h
+++ b/src/dawn_native/vulkan/MemoryAllocator.h
@@ -42,6 +42,7 @@
MemoryAllocator(Device* device);
~MemoryAllocator();
+ int FindBestTypeIndex(VkMemoryRequirements requirements, bool mappable);
bool Allocate(VkMemoryRequirements requirements,
bool mappable,
DeviceMemoryAllocation* allocation);
diff --git a/src/dawn_native/vulkan/TextureVk.cpp b/src/dawn_native/vulkan/TextureVk.cpp
index 727292c..ad21992 100644
--- a/src/dawn_native/vulkan/TextureVk.cpp
+++ b/src/dawn_native/vulkan/TextureVk.cpp
@@ -14,6 +14,7 @@
#include "dawn_native/vulkan/TextureVk.h"
+#include "dawn_native/VulkanBackend.h"
#include "dawn_native/vulkan/AdapterVk.h"
#include "dawn_native/vulkan/CommandRecordingContext.h"
#include "dawn_native/vulkan/DeviceVk.h"
@@ -379,12 +380,33 @@
}
}
+ MaybeError ValidateVulkanImageCanBeWrapped(const DeviceBase*,
+ const TextureDescriptor* descriptor) {
+ if (descriptor->dimension != dawn::TextureDimension::e2D) {
+ return DAWN_VALIDATION_ERROR("Texture must be 2D");
+ }
+
+ if (descriptor->mipLevelCount != 1) {
+ return DAWN_VALIDATION_ERROR("Mip level count must be 1");
+ }
+
+ if (descriptor->arrayLayerCount != 1) {
+ return DAWN_VALIDATION_ERROR("Array layer count must be 1");
+ }
+
+ if (descriptor->sampleCount != 1) {
+ return DAWN_VALIDATION_ERROR("Sample count must be 1");
+ }
+
+ return {};
+ }
+
Texture::Texture(Device* device, const TextureDescriptor* descriptor)
: TextureBase(device, descriptor, TextureState::OwnedInternal) {
// Create the Vulkan image "container". We don't need to check that the format supports the
// combination of sample, usage etc. because validation should have been done in the Dawn
// frontend already based on the minimum supported formats in the Vulkan spec
- VkImageCreateInfo createInfo;
+ VkImageCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
createInfo.pNext = nullptr;
createInfo.flags = 0;
@@ -466,6 +488,95 @@
: TextureBase(device, descriptor, TextureState::OwnedExternal), mHandle(nativeImage) {
}
+ // Internally managed, but imported from file descriptor
+ Texture::Texture(Device* device,
+ const ExternalImageDescriptor* descriptor,
+ const TextureDescriptor* textureDescriptor,
+ VkSemaphore signalSemaphore,
+ VkDeviceMemory externalMemoryAllocation,
+ std::vector<VkSemaphore> waitSemaphores)
+ : TextureBase(device, textureDescriptor, TextureState::OwnedInternal),
+ mExternalAllocation(externalMemoryAllocation),
+ mExternalState(ExternalState::PendingAcquire),
+ mSignalSemaphore(signalSemaphore),
+ mWaitRequirements(std::move(waitSemaphores)) {
+ VkImageCreateInfo createInfo = {};
+ createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+ createInfo.pNext = nullptr;
+ createInfo.flags = VK_IMAGE_CREATE_ALIAS_BIT_KHR;
+ createInfo.imageType = VulkanImageType(GetDimension());
+ createInfo.format = VulkanImageFormat(GetFormat().format);
+ createInfo.extent = VulkanExtent3D(GetSize());
+ createInfo.mipLevels = GetNumMipLevels();
+ createInfo.arrayLayers = GetArrayLayers();
+ createInfo.samples = VulkanSampleCount(GetSampleCount());
+ createInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+ createInfo.usage = VulkanImageUsage(GetUsage(), GetFormat());
+ createInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+ createInfo.queueFamilyIndexCount = 0;
+ createInfo.pQueueFamilyIndices = nullptr;
+ createInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+
+ ASSERT(IsSampleCountSupported(device, createInfo));
+
+ // We always set VK_IMAGE_USAGE_TRANSFER_DST_BIT unconditionally beause the Vulkan images
+ // that are used in vkCmdClearColorImage() must have been created with this flag, which is
+ // also required for the implementation of robust resource initialization.
+ createInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+
+ if (device->fn.CreateImage(device->GetVkDevice(), &createInfo, nullptr, &mHandle) !=
+ VK_SUCCESS) {
+ ASSERT(false);
+ }
+
+ // Create the image memory and associate it with the container
+ VkMemoryRequirements requirements;
+ device->fn.GetImageMemoryRequirements(device->GetVkDevice(), mHandle, &requirements);
+
+ ASSERT(requirements.size <= descriptor->allocationSize);
+
+ if (device->fn.BindImageMemory(device->GetVkDevice(), mHandle, mExternalAllocation, 0) !=
+ VK_SUCCESS) {
+ ASSERT(false);
+ }
+
+ // Don't clear imported texture if already cleared
+ if (descriptor->isCleared) {
+ SetIsSubresourceContentInitialized(0, 1, 0, 1);
+ }
+ }
+
+ MaybeError Texture::SignalAndDestroy(VkSemaphore* outSignalSemaphore) {
+ Device* device = ToBackend(GetDevice());
+
+ if (mExternalState == ExternalState::Released) {
+ return DAWN_VALIDATION_ERROR("Can't export signal semaphore from signaled texture");
+ }
+
+ if (mExternalAllocation == VK_NULL_HANDLE) {
+ return DAWN_VALIDATION_ERROR(
+ "Can't export signal semaphore from destroyed / non-external texture");
+ }
+
+ ASSERT(mSignalSemaphore != VK_NULL_HANDLE);
+
+ // Release the texture
+ mExternalState = ExternalState::PendingRelease;
+ TransitionUsageNow(device->GetPendingRecordingContext(), dawn::TextureUsageBit::None);
+
+ // Queue submit to signal we are done with the texture
+ device->GetPendingRecordingContext()->signalSemaphores.push_back(mSignalSemaphore);
+ device->SubmitPendingCommands();
+
+ // Write out the signal semaphore
+ *outSignalSemaphore = mSignalSemaphore;
+ mSignalSemaphore = VK_NULL_HANDLE;
+
+ // Destroy the texture so it can't be used again
+ DestroyInternal();
+ return {};
+ }
+
Texture::~Texture() {
DestroyInternal();
}
@@ -479,12 +590,20 @@
// freed after the VkImage is destroyed and this is taken care of by the
// FencedDeleter.
device->GetMemoryAllocator()->Free(&mMemoryAllocation);
-
- if (mHandle != VK_NULL_HANDLE) {
- device->GetFencedDeleter()->DeleteWhenUnused(mHandle);
- }
}
+
+ if (mHandle != VK_NULL_HANDLE) {
+ device->GetFencedDeleter()->DeleteWhenUnused(mHandle);
+ }
+
+ if (mExternalAllocation != VK_NULL_HANDLE) {
+ device->GetFencedDeleter()->DeleteWhenUnused(mExternalAllocation);
+ }
+
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);
}
VkImage Texture::GetHandle() const {
@@ -499,7 +618,7 @@
dawn::TextureUsageBit usage) {
// Avoid encoding barriers when it isn't needed.
bool lastReadOnly = (mLastUsage & kReadOnlyTextureUsages) == mLastUsage;
- if (lastReadOnly && mLastUsage == usage) {
+ if (lastReadOnly && mLastUsage == usage && mLastExternalState == mExternalState) {
return;
}
@@ -515,8 +634,6 @@
barrier.dstAccessMask = VulkanAccessFlags(usage, format);
barrier.oldLayout = VulkanImageLayout(mLastUsage, format);
barrier.newLayout = VulkanImageLayout(usage, format);
- barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
- barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = mHandle;
// This transitions the whole resource but assumes it is a 2D texture
ASSERT(GetDimension() == dawn::TextureDimension::e2D);
@@ -526,11 +643,36 @@
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = GetArrayLayers();
+ barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+ barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+
+ if (mExternalState == ExternalState::PendingAcquire) {
+ // Transfer texture from external queue to graphics queue
+ barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL_KHR;
+ barrier.dstQueueFamilyIndex = ToBackend(GetDevice())->GetGraphicsQueueFamily();
+ // Don't override oldLayout to leave it as VK_IMAGE_LAYOUT_UNDEFINED
+ // TODO(http://crbug.com/dawn/200)
+ mExternalState = ExternalState::Acquired;
+
+ } else if (mExternalState == ExternalState::PendingRelease) {
+ // Transfer texture from graphics queue to external queue
+ barrier.srcQueueFamilyIndex = ToBackend(GetDevice())->GetGraphicsQueueFamily();
+ barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL_KHR;
+ barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
+ mExternalState = ExternalState::Released;
+ }
+
+ // Move required semaphores into waitSemaphores
+ recordingContext->waitSemaphores.insert(recordingContext->waitSemaphores.end(),
+ mWaitRequirements.begin(), mWaitRequirements.end());
+ mWaitRequirements.clear();
+
ToBackend(GetDevice())
->fn.CmdPipelineBarrier(recordingContext->commandBuffer, srcStages, dstStages, 0, 0,
nullptr, 0, nullptr, 1, &barrier);
mLastUsage = usage;
+ mLastExternalState = mExternalState;
}
void Texture::ClearTexture(CommandRecordingContext* recordingContext,
diff --git a/src/dawn_native/vulkan/TextureVk.h b/src/dawn_native/vulkan/TextureVk.h
index 9f71692..52236d3 100644
--- a/src/dawn_native/vulkan/TextureVk.h
+++ b/src/dawn_native/vulkan/TextureVk.h
@@ -18,20 +18,39 @@
#include "dawn_native/Texture.h"
#include "common/vulkan_platform.h"
+#include "dawn_native/vulkan/ExternalHandle.h"
#include "dawn_native/vulkan/MemoryAllocator.h"
namespace dawn_native { namespace vulkan {
struct CommandRecordingContext;
+ struct ExternalImageDescriptor;
VkFormat VulkanImageFormat(dawn::TextureFormat format);
VkImageUsageFlags VulkanImageUsage(dawn::TextureUsageBit usage, const Format& format);
VkSampleCountFlagBits VulkanSampleCount(uint32_t sampleCount);
+ MaybeError ValidateVulkanImageCanBeWrapped(const DeviceBase* device,
+ const TextureDescriptor* descriptor);
+
class Texture : public TextureBase {
public:
+ enum class ExternalState {
+ InternalOnly,
+ PendingAcquire,
+ Acquired,
+ PendingRelease,
+ Released
+ };
+
Texture(Device* device, const TextureDescriptor* descriptor);
Texture(Device* device, const TextureDescriptor* descriptor, VkImage nativeImage);
+ Texture(Device* device,
+ const ExternalImageDescriptor* descriptor,
+ const TextureDescriptor* textureDescriptor,
+ VkSemaphore signalSemaphore,
+ VkDeviceMemory externalMemoryAllocation,
+ std::vector<VkSemaphore> waitSemaphores);
~Texture();
VkImage GetHandle() const;
@@ -48,6 +67,8 @@
uint32_t baseArrayLayer,
uint32_t layerCount);
+ MaybeError SignalAndDestroy(VkSemaphore* outSignalSemaphore);
+
private:
void DestroyImpl() override;
void ClearTexture(CommandRecordingContext* recordingContext,
@@ -58,6 +79,12 @@
VkImage mHandle = VK_NULL_HANDLE;
DeviceMemoryAllocation mMemoryAllocation;
+ VkDeviceMemory mExternalAllocation = VK_NULL_HANDLE;
+
+ ExternalState mExternalState = ExternalState::InternalOnly;
+ ExternalState mLastExternalState = ExternalState::InternalOnly;
+ VkSemaphore mSignalSemaphore = VK_NULL_HANDLE;
+ std::vector<VkSemaphore> mWaitRequirements;
// A usage of none will make sure the texture is transitioned before its first use as
// required by the spec.
diff --git a/src/dawn_native/vulkan/VulkanBackend.cpp b/src/dawn_native/vulkan/VulkanBackend.cpp
index acc624a..566605e 100644
--- a/src/dawn_native/vulkan/VulkanBackend.cpp
+++ b/src/dawn_native/vulkan/VulkanBackend.cpp
@@ -24,6 +24,7 @@
#include "common/SwapChainUtils.h"
#include "dawn_native/vulkan/DeviceVk.h"
#include "dawn_native/vulkan/NativeSwapChainImplVk.h"
+#include "dawn_native/vulkan/TextureVk.h"
namespace dawn_native { namespace vulkan {
@@ -52,4 +53,32 @@
return static_cast<DawnTextureFormat>(impl->GetPreferredFormat());
}
+#ifdef DAWN_PLATFORM_LINUX
+ DawnTexture WrapVulkanImageOpaqueFD(DawnDevice cDevice,
+ const ExternalImageDescriptorOpaqueFD* descriptor) {
+ Device* device = reinterpret_cast<Device*>(cDevice);
+
+ TextureBase* texture = device->CreateTextureWrappingVulkanImage(
+ descriptor, descriptor->memoryFD, descriptor->waitFDs);
+
+ return reinterpret_cast<DawnTexture>(texture);
+ }
+
+ int ExportSignalSemaphoreOpaqueFD(DawnDevice cDevice, DawnTexture cTexture) {
+ Device* device = reinterpret_cast<Device*>(cDevice);
+ Texture* texture = reinterpret_cast<Texture*>(cTexture);
+
+ if (!texture) {
+ return -1;
+ }
+
+ ExternalSemaphoreHandle outHandle;
+ if (device->ConsumedError(device->SignalAndExportExternalTexture(texture, &outHandle))) {
+ return -1;
+ }
+
+ return outHandle;
+ }
+#endif
+
}} // namespace dawn_native::vulkan
diff --git a/src/dawn_native/vulkan/external_memory/MemoryService.h b/src/dawn_native/vulkan/external_memory/MemoryService.h
new file mode 100644
index 0000000..8d27db6
--- /dev/null
+++ b/src/dawn_native/vulkan/external_memory/MemoryService.h
@@ -0,0 +1,50 @@
+// Copyright 2019 The Dawn Authors
+//
+// 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.
+
+#ifndef DAWNNATIVE_VULKAN_EXTERNALMEMORY_SERVICE_H_
+#define DAWNNATIVE_VULKAN_EXTERNALMEMORY_SERVICE_H_
+
+#include "common/vulkan_platform.h"
+#include "dawn_native/Error.h"
+#include "dawn_native/vulkan/ExternalHandle.h"
+
+namespace dawn_native { namespace vulkan {
+ class Device;
+}} // namespace dawn_native::vulkan
+
+namespace dawn_native { namespace vulkan { namespace external_memory {
+
+ class Service {
+ public:
+ explicit Service(Device* device);
+ ~Service();
+
+ // True if the device reports it supports this feature
+ bool Supported();
+
+ // Given an external handle pointing to memory, import it into a VkDeviceMemory
+ ResultOrError<VkDeviceMemory> ImportMemory(ExternalMemoryHandle handle,
+ VkDeviceSize allocationSize,
+ uint32_t memoryTypeIndex);
+
+ private:
+ Device* mDevice = nullptr;
+
+ // True if early checks pass that determine if the service is supported
+ bool mSupportedFirstPass = false;
+ };
+
+}}} // namespace dawn_native::vulkan::external_memory
+
+#endif // DAWNNATIVE_VULKAN_EXTERNALMEMORY_SERVICE_H_
diff --git a/src/dawn_native/vulkan/external_memory/MemoryServiceNull.cpp b/src/dawn_native/vulkan/external_memory/MemoryServiceNull.cpp
new file mode 100644
index 0000000..d10ddc7
--- /dev/null
+++ b/src/dawn_native/vulkan/external_memory/MemoryServiceNull.cpp
@@ -0,0 +1,37 @@
+// Copyright 2019 The Dawn Authors
+//
+// 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.
+
+#include "dawn_native/vulkan/DeviceVk.h"
+#include "dawn_native/vulkan/external_memory/MemoryService.h"
+
+namespace dawn_native { namespace vulkan { namespace external_memory {
+
+ Service::Service(Device* device) : mDevice(device) {
+ DAWN_UNUSED(mDevice);
+ DAWN_UNUSED(mSupportedFirstPass);
+ }
+
+ Service::~Service() = default;
+
+ bool Service::Supported() {
+ return false;
+ }
+
+ ResultOrError<VkDeviceMemory> Service::ImportMemory(ExternalMemoryHandle handle,
+ VkDeviceSize allocationSize,
+ uint32_t memoryTypeIndex) {
+ return DAWN_UNIMPLEMENTED_ERROR("Using null semaphore service to interop inside Vulkan");
+ }
+
+}}} // namespace dawn_native::vulkan::external_memory
diff --git a/src/dawn_native/vulkan/external_memory/MemoryServiceOpaqueFD.cpp b/src/dawn_native/vulkan/external_memory/MemoryServiceOpaqueFD.cpp
new file mode 100644
index 0000000..e1f38db
--- /dev/null
+++ b/src/dawn_native/vulkan/external_memory/MemoryServiceOpaqueFD.cpp
@@ -0,0 +1,61 @@
+// Copyright 2019 The Dawn Authors
+//
+// 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.
+
+#include "dawn_native/vulkan/DeviceVk.h"
+#include "dawn_native/vulkan/VulkanError.h"
+#include "dawn_native/vulkan/external_memory/MemoryService.h"
+
+namespace dawn_native { namespace vulkan { namespace external_memory {
+
+ Service::Service(Device* device) : mDevice(device) {
+ const VulkanDeviceInfo& info = mDevice->GetDeviceInfo();
+ mSupportedFirstPass = info.externalMemory && info.externalMemoryFD;
+ }
+
+ Service::~Service() = default;
+
+ bool Service::Supported() {
+ // TODO(idanr): Query device here for additional support information, decide if supported
+ // This will likely be done through vkGetPhysicalDeviceImageFormatProperties2KHR, where
+ // we give it the intended image properties and handle type and see if it's supported
+ return mSupportedFirstPass;
+ }
+
+ ResultOrError<VkDeviceMemory> Service::ImportMemory(ExternalMemoryHandle handle,
+ VkDeviceSize allocationSize,
+ uint32_t memoryTypeIndex) {
+ if (handle < 0) {
+ return DAWN_VALIDATION_ERROR("Trying to import memory with invalid handle");
+ }
+
+ VkImportMemoryFdInfoKHR importMemoryFdInfo;
+ importMemoryFdInfo.sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR;
+ importMemoryFdInfo.pNext = nullptr;
+ importMemoryFdInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR;
+ importMemoryFdInfo.fd = handle;
+
+ VkMemoryAllocateInfo allocateInfo;
+ allocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+ allocateInfo.pNext = &importMemoryFdInfo;
+ allocateInfo.allocationSize = allocationSize;
+ allocateInfo.memoryTypeIndex = memoryTypeIndex;
+
+ VkDeviceMemory allocatedMemory = VK_NULL_HANDLE;
+ DAWN_TRY(CheckVkSuccess(mDevice->fn.AllocateMemory(mDevice->GetVkDevice(), &allocateInfo,
+ nullptr, &allocatedMemory),
+ "vkAllocateMemory"));
+ return allocatedMemory;
+ }
+
+}}} // namespace dawn_native::vulkan::external_memory
diff --git a/src/dawn_native/vulkan/external_semaphore/SemaphoreService.h b/src/dawn_native/vulkan/external_semaphore/SemaphoreService.h
new file mode 100644
index 0000000..bfa72c4
--- /dev/null
+++ b/src/dawn_native/vulkan/external_semaphore/SemaphoreService.h
@@ -0,0 +1,54 @@
+// Copyright 2019 The Dawn Authors
+//
+// 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.
+
+#ifndef DAWNNATIVE_VULKAN_EXTERNALSEMAPHORE_SERVICE_H_
+#define DAWNNATIVE_VULKAN_EXTERNALSEMAPHORE_SERVICE_H_
+
+#include "common/vulkan_platform.h"
+#include "dawn_native/Error.h"
+#include "dawn_native/vulkan/ExternalHandle.h"
+
+namespace dawn_native { namespace vulkan {
+ class Device;
+}} // namespace dawn_native::vulkan
+
+namespace dawn_native { namespace vulkan { namespace external_semaphore {
+
+ class Service {
+ public:
+ explicit Service(Device* device);
+ ~Service();
+
+ // True if the device reports it supports this feature
+ bool Supported();
+
+ // Given an external handle, import it into a VkSemaphore
+ ResultOrError<VkSemaphore> ImportSemaphore(ExternalSemaphoreHandle handle);
+
+ // Create a VkSemaphore that is exportable into an external handle later
+ ResultOrError<VkSemaphore> CreateExportableSemaphore();
+
+ // Export a VkSemaphore into an external handle
+ ResultOrError<ExternalSemaphoreHandle> ExportSemaphore(VkSemaphore semaphore);
+
+ private:
+ Device* mDevice = nullptr;
+
+ // True if early checks pass that determine if the service is supported
+ bool mSupportedFirstPass = false;
+ };
+
+}}} // namespace dawn_native::vulkan::external_semaphore
+
+#endif // DAWNNATIVE_VULKAN_EXTERNALSEMAPHORE_SERVICE_H_
diff --git a/src/dawn_native/vulkan/external_semaphore/SemaphoreServiceNull.cpp b/src/dawn_native/vulkan/external_semaphore/SemaphoreServiceNull.cpp
new file mode 100644
index 0000000..e7fd859
--- /dev/null
+++ b/src/dawn_native/vulkan/external_semaphore/SemaphoreServiceNull.cpp
@@ -0,0 +1,43 @@
+// Copyright 2019 The Dawn Authors
+//
+// 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.
+
+#include "dawn_native/vulkan/DeviceVk.h"
+#include "dawn_native/vulkan/external_semaphore/SemaphoreService.h"
+
+namespace dawn_native { namespace vulkan { namespace external_semaphore {
+
+ Service::Service(Device* device) : mDevice(device) {
+ DAWN_UNUSED(mDevice);
+ DAWN_UNUSED(mSupportedFirstPass);
+ }
+
+ Service::~Service() = default;
+
+ bool Service::Supported() {
+ return false;
+ }
+
+ ResultOrError<VkSemaphore> Service::ImportSemaphore(ExternalSemaphoreHandle handle) {
+ return DAWN_UNIMPLEMENTED_ERROR("Using null semaphore service to interop inside Vulkan");
+ }
+
+ ResultOrError<VkSemaphore> Service::CreateExportableSemaphore() {
+ return DAWN_UNIMPLEMENTED_ERROR("Using null semaphore service to interop inside Vulkan");
+ }
+
+ ResultOrError<ExternalSemaphoreHandle> Service::ExportSemaphore(VkSemaphore semaphore) {
+ return DAWN_UNIMPLEMENTED_ERROR("Using null semaphore service to interop inside Vulkan");
+ }
+
+}}} // namespace dawn_native::vulkan::external_semaphore
diff --git a/src/dawn_native/vulkan/external_semaphore/SemaphoreServiceOpaqueFD.cpp b/src/dawn_native/vulkan/external_semaphore/SemaphoreServiceOpaqueFD.cpp
new file mode 100644
index 0000000..7312142
--- /dev/null
+++ b/src/dawn_native/vulkan/external_semaphore/SemaphoreServiceOpaqueFD.cpp
@@ -0,0 +1,105 @@
+// Copyright 2019 The Dawn Authors
+//
+// 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.
+
+#include "dawn_native/vulkan/DeviceVk.h"
+#include "dawn_native/vulkan/VulkanError.h"
+#include "dawn_native/vulkan/external_semaphore/SemaphoreService.h"
+
+namespace dawn_native { namespace vulkan { namespace external_semaphore {
+
+ Service::Service(Device* device) : mDevice(device) {
+ const VulkanDeviceInfo& info = mDevice->GetDeviceInfo();
+ mSupportedFirstPass = info.externalSemaphore && info.externalSemaphoreFD;
+ // TODO(idanr): Query device here for additional support information, decide if supported
+ // This will likely be done through vkGetPhysicalDeviceExternalSemaphorePropertiesKHR, where
+ // we give it the intended handle type and see if it's supported
+ }
+
+ Service::~Service() = default;
+
+ bool Service::Supported() {
+ return mSupportedFirstPass;
+ }
+
+ ResultOrError<VkSemaphore> Service::ImportSemaphore(ExternalSemaphoreHandle handle) {
+ if (handle < 0) {
+ return DAWN_VALIDATION_ERROR("Trying to import semaphore with invalid handle");
+ }
+
+ VkSemaphore semaphore = VK_NULL_HANDLE;
+ VkSemaphoreCreateInfo info;
+ info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+ info.pNext = nullptr;
+ info.flags = 0;
+
+ DAWN_TRY(CheckVkSuccess(
+ mDevice->fn.CreateSemaphore(mDevice->GetVkDevice(), &info, nullptr, &semaphore),
+ "vkCreateSemaphore"));
+
+ VkImportSemaphoreFdInfoKHR importSemaphoreFdInfo;
+ importSemaphoreFdInfo.sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR;
+ importSemaphoreFdInfo.pNext = nullptr;
+ importSemaphoreFdInfo.semaphore = semaphore;
+ importSemaphoreFdInfo.flags = 0;
+ importSemaphoreFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT_KHR;
+ importSemaphoreFdInfo.fd = handle;
+
+ MaybeError status = CheckVkSuccess(
+ mDevice->fn.ImportSemaphoreFdKHR(mDevice->GetVkDevice(), &importSemaphoreFdInfo),
+ "vkImportSemaphoreFdKHR");
+
+ if (status.IsError()) {
+ mDevice->fn.DestroySemaphore(mDevice->GetVkDevice(), semaphore, nullptr);
+ DAWN_TRY(std::move(status));
+ }
+
+ return semaphore;
+ }
+
+ ResultOrError<VkSemaphore> Service::CreateExportableSemaphore() {
+ VkExportSemaphoreCreateInfoKHR exportSemaphoreInfo;
+ exportSemaphoreInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO_KHR;
+ exportSemaphoreInfo.pNext = nullptr;
+ exportSemaphoreInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT_KHR;
+
+ VkSemaphoreCreateInfo semaphoreCreateInfo;
+ semaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+ semaphoreCreateInfo.pNext = &exportSemaphoreInfo;
+ semaphoreCreateInfo.flags = 0;
+
+ VkSemaphore signalSemaphore;
+ DAWN_TRY(
+ CheckVkSuccess(mDevice->fn.CreateSemaphore(mDevice->GetVkDevice(), &semaphoreCreateInfo,
+ nullptr, &signalSemaphore),
+ "vkCreateSemaphore"));
+ return signalSemaphore;
+ }
+
+ ResultOrError<ExternalSemaphoreHandle> Service::ExportSemaphore(VkSemaphore semaphore) {
+ VkSemaphoreGetFdInfoKHR semaphoreGetFdInfo;
+ semaphoreGetFdInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR;
+ semaphoreGetFdInfo.pNext = nullptr;
+ semaphoreGetFdInfo.semaphore = semaphore;
+ semaphoreGetFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT_KHR;
+
+ int fd = -1;
+ DAWN_TRY(CheckVkSuccess(
+ mDevice->fn.GetSemaphoreFdKHR(mDevice->GetVkDevice(), &semaphoreGetFdInfo, &fd),
+ "vkGetSemaphoreFdKHR"));
+
+ ASSERT(fd >= 0);
+ return fd;
+ }
+
+}}} // namespace dawn_native::vulkan::external_semaphore
diff --git a/src/include/dawn_native/VulkanBackend.h b/src/include/dawn_native/VulkanBackend.h
index eeb2b0b..f874259 100644
--- a/src/include/dawn_native/VulkanBackend.h
+++ b/src/include/dawn_native/VulkanBackend.h
@@ -23,12 +23,44 @@
#include <vector>
namespace dawn_native { namespace vulkan {
+
+ // Common properties of external images
+ struct ExternalImageDescriptor {
+ const DawnTextureDescriptor* cTextureDescriptor; // Must match image creation params
+ bool isCleared; // Sets whether the texture will be cleared before use
+ VkDeviceSize allocationSize; // Must match VkMemoryAllocateInfo from image creation
+ uint32_t memoryTypeIndex; // Must match VkMemoryAllocateInfo from image creation
+ };
+
DAWN_NATIVE_EXPORT VkInstance GetInstance(DawnDevice device);
DAWN_NATIVE_EXPORT DawnSwapChainImplementation CreateNativeSwapChainImpl(DawnDevice device,
VkSurfaceKHR surface);
DAWN_NATIVE_EXPORT DawnTextureFormat
GetNativeSwapChainPreferredFormat(const DawnSwapChainImplementation* swapChain);
+
+// Can't use DAWN_PLATFORM_LINUX since header included in both dawn and chrome
+#ifdef __linux__
+ // Descriptor for opaque file descriptor image import
+ struct ExternalImageDescriptorOpaqueFD : ExternalImageDescriptor {
+ int memoryFD; // A file descriptor from an export of the memory of the image
+ std::vector<int> waitFDs; // File descriptors of semaphores which will be waited on
+ };
+
+ // Imports an external vulkan image from an opaque file descriptor. Internally, this uses
+ // external memory / semaphore extensions to import the image. Then, waits on the provided
+ // |descriptor->waitFDs| before the texture can be used. Finally, a signal semaphore
+ // can be exported, transferring control back to the caller.
+ // On failure, returns a nullptr
+ DAWN_NATIVE_EXPORT DawnTexture
+ WrapVulkanImageOpaqueFD(DawnDevice cDevice,
+ const ExternalImageDescriptorOpaqueFD* descriptor);
+
+ // Exports a signal semaphore from a wrapped texture. This must be called on wrapped
+ // textures before they are destroyed. On failure, returns -1
+ DAWN_NATIVE_EXPORT int ExportSignalSemaphoreOpaqueFD(DawnDevice cDevice,
+ DawnTexture cTexture);
+#endif // __linux__
}} // namespace dawn_native::vulkan
#endif // DAWNNATIVE_VULKANBACKEND_H_
diff --git a/src/tests/white_box/VulkanImageWrappingTests.cpp b/src/tests/white_box/VulkanImageWrappingTests.cpp
new file mode 100644
index 0000000..4284a00
--- /dev/null
+++ b/src/tests/white_box/VulkanImageWrappingTests.cpp
@@ -0,0 +1,912 @@
+// Copyright 2019 The Dawn Authors
+//
+// 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.
+
+#include "tests/DawnTest.h"
+
+#include "common/vulkan_platform.h"
+#include "dawn_native/VulkanBackend.h"
+#include "dawn_native/vulkan/AdapterVk.h"
+#include "dawn_native/vulkan/DeviceVk.h"
+#include "dawn_native/vulkan/FencedDeleter.h"
+#include "dawn_native/vulkan/MemoryAllocator.h"
+#include "dawn_native/vulkan/TextureVk.h"
+#include "utils/DawnHelpers.h"
+#include "utils/SystemUtils.h"
+
+// TODO(crbug.com/966500): Intel is disabled until upgrade is finished
+
+namespace {
+
+ class VulkanImageWrappingTestBase : public DawnTest {
+ public:
+ void SetUp() override {
+ DawnTest::SetUp();
+ if (UsesWire() || IsIntel()) {
+ return;
+ }
+
+ deviceVk = reinterpret_cast<dawn_native::vulkan::Device*>(device.Get());
+ }
+
+ // Creates a VkImage with external memory
+ VkResult CreateImage(dawn_native::vulkan::Device* deviceVk,
+ uint32_t width,
+ uint32_t height,
+ VkFormat format,
+ VkImage* image) {
+ VkExternalMemoryImageCreateInfoKHR externalInfo;
+ externalInfo.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO_KHR;
+ externalInfo.pNext = nullptr;
+ externalInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR;
+
+ auto usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
+ VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+
+ VkImageCreateInfo createInfo;
+ createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+ createInfo.pNext = &externalInfo;
+ createInfo.flags = VK_IMAGE_CREATE_ALIAS_BIT_KHR;
+ createInfo.imageType = VK_IMAGE_TYPE_2D;
+ createInfo.format = format;
+ createInfo.extent = {width, height, 1};
+ createInfo.mipLevels = 1;
+ createInfo.arrayLayers = 1;
+ createInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+ createInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+ createInfo.usage = usage;
+ createInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+ createInfo.queueFamilyIndexCount = 0;
+ createInfo.pQueueFamilyIndices = nullptr;
+ createInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+
+ return deviceVk->fn.CreateImage(deviceVk->GetVkDevice(), &createInfo, nullptr, image);
+ }
+
+ // Allocates memory for an image
+ VkResult AllocateMemory(dawn_native::vulkan::Device* deviceVk,
+ VkImage handle,
+ VkDeviceMemory* allocation,
+ VkDeviceSize* allocationSize,
+ uint32_t* memoryTypeIndex) {
+ // Create the image memory and associate it with the container
+ VkMemoryRequirements requirements;
+ deviceVk->fn.GetImageMemoryRequirements(deviceVk->GetVkDevice(), handle, &requirements);
+
+ // Import memory from file descriptor
+ VkExportMemoryAllocateInfoKHR externalInfo;
+ externalInfo.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO_KHR;
+ externalInfo.pNext = nullptr;
+ externalInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR;
+
+ int bestType = deviceVk->GetMemoryAllocator()->FindBestTypeIndex(requirements, false);
+ VkMemoryAllocateInfo allocateInfo;
+ allocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+ allocateInfo.pNext = &externalInfo;
+ allocateInfo.allocationSize = requirements.size;
+ allocateInfo.memoryTypeIndex = static_cast<uint32_t>(bestType);
+
+ *allocationSize = allocateInfo.allocationSize;
+ *memoryTypeIndex = allocateInfo.memoryTypeIndex;
+
+ return deviceVk->fn.AllocateMemory(deviceVk->GetVkDevice(), &allocateInfo, nullptr,
+ allocation);
+ }
+
+ // Binds memory to an image
+ VkResult BindMemory(dawn_native::vulkan::Device* deviceVk,
+ VkImage handle,
+ VkDeviceMemory* memory) {
+ return deviceVk->fn.BindImageMemory(deviceVk->GetVkDevice(), handle, *memory, 0);
+ }
+
+ // Extracts a file descriptor representing memory on a device
+ int GetMemoryFd(dawn_native::vulkan::Device* deviceVk, VkDeviceMemory memory) {
+ VkMemoryGetFdInfoKHR getFdInfo;
+ getFdInfo.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR;
+ getFdInfo.pNext = nullptr;
+ getFdInfo.memory = memory;
+ getFdInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR;
+
+ int memoryFd = -1;
+ deviceVk->fn.GetMemoryFdKHR(deviceVk->GetVkDevice(), &getFdInfo, &memoryFd);
+
+ EXPECT_GE(memoryFd, 0) << "Failed to get file descriptor for external memory";
+ return memoryFd;
+ }
+
+ // Prepares and exports memory for an image on a given device
+ void CreateBindExportImage(dawn_native::vulkan::Device* deviceVk,
+ uint32_t width,
+ uint32_t height,
+ VkFormat format,
+ VkImage* handle,
+ VkDeviceMemory* allocation,
+ VkDeviceSize* allocationSize,
+ uint32_t* memoryTypeIndex,
+ int* memoryFd) {
+ VkResult result = CreateImage(deviceVk, width, height, format, handle);
+ EXPECT_EQ(result, VK_SUCCESS) << "Failed to create external image";
+
+ VkResult resultBool =
+ AllocateMemory(deviceVk, *handle, allocation, allocationSize, memoryTypeIndex);
+ EXPECT_EQ(resultBool, VK_SUCCESS) << "Failed to allocate external memory";
+
+ result = BindMemory(deviceVk, *handle, allocation);
+ EXPECT_EQ(result, VK_SUCCESS) << "Failed to bind image memory";
+
+ *memoryFd = GetMemoryFd(deviceVk, *allocation);
+ }
+
+ // Wraps a vulkan image from external memory
+ dawn::Texture WrapVulkanImage(dawn::Device device,
+ const dawn::TextureDescriptor* textureDescriptor,
+ int memoryFd,
+ VkDeviceSize allocationSize,
+ uint32_t memoryTypeIndex,
+ std::vector<int> waitFDs,
+ bool isCleared = true,
+ bool expectValid = true) {
+ dawn_native::vulkan::ExternalImageDescriptorOpaqueFD descriptor;
+ descriptor.cTextureDescriptor =
+ reinterpret_cast<const DawnTextureDescriptor*>(textureDescriptor);
+ descriptor.isCleared = isCleared;
+ descriptor.allocationSize = allocationSize;
+ descriptor.memoryTypeIndex = memoryTypeIndex;
+ descriptor.memoryFD = memoryFd;
+ descriptor.waitFDs = waitFDs;
+
+ DawnTexture texture =
+ dawn_native::vulkan::WrapVulkanImageOpaqueFD(device.Get(), &descriptor);
+
+ if (expectValid) {
+ EXPECT_NE(texture, nullptr) << "Failed to wrap image, are external memory / "
+ "semaphore extensions supported?";
+ } else {
+ EXPECT_EQ(texture, nullptr);
+ }
+
+ return dawn::Texture::Acquire(texture);
+ }
+
+ // Exports the signal from a wrapped texture and ignores it
+ // We have to export the signal before destroying the wrapped texture else it's an assertion
+ // failure
+ void IgnoreSignalSemaphore(dawn::Device device, dawn::Texture wrappedTexture) {
+ int fd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(device.Get(),
+ wrappedTexture.Get());
+ ASSERT_NE(fd, -1);
+ close(fd);
+ }
+
+ protected:
+ dawn_native::vulkan::Device* deviceVk;
+ };
+
+} // anonymous namespace
+
+class VulkanImageWrappingValidationTests : public VulkanImageWrappingTestBase {
+ public:
+ void SetUp() override {
+ VulkanImageWrappingTestBase::SetUp();
+ if (UsesWire() || IsIntel()) {
+ return;
+ }
+
+ CreateBindExportImage(deviceVk, 1, 1, VK_FORMAT_R8G8B8A8_UNORM, &defaultImage,
+ &defaultAllocation, &defaultAllocationSize, &defaultMemoryTypeIndex,
+ &defaultFd);
+ defaultDescriptor.dimension = dawn::TextureDimension::e2D;
+ defaultDescriptor.format = dawn::TextureFormat::RGBA8Unorm;
+ defaultDescriptor.size = {1, 1, 1};
+ defaultDescriptor.sampleCount = 1;
+ defaultDescriptor.arrayLayerCount = 1;
+ defaultDescriptor.mipLevelCount = 1;
+ defaultDescriptor.usage = dawn::TextureUsageBit::OutputAttachment |
+ dawn::TextureUsageBit::CopySrc | dawn::TextureUsageBit::CopyDst;
+ }
+
+ void TearDown() override {
+ if (UsesWire() || IsIntel()) {
+ VulkanImageWrappingTestBase::TearDown();
+ return;
+ }
+
+ deviceVk->GetFencedDeleter()->DeleteWhenUnused(defaultImage);
+ deviceVk->GetFencedDeleter()->DeleteWhenUnused(defaultAllocation);
+ VulkanImageWrappingTestBase::TearDown();
+ }
+
+ protected:
+ dawn::TextureDescriptor defaultDescriptor;
+ VkImage defaultImage;
+ VkDeviceMemory defaultAllocation;
+ VkDeviceSize defaultAllocationSize;
+ uint32_t defaultMemoryTypeIndex;
+ int defaultFd;
+};
+
+// Test no error occurs if the import is valid
+TEST_P(VulkanImageWrappingValidationTests, SuccessfulImport) {
+ DAWN_SKIP_TEST_IF(UsesWire() || IsIntel());
+ dawn::Texture texture =
+ WrapVulkanImage(device, &defaultDescriptor, defaultFd, defaultAllocationSize,
+ defaultMemoryTypeIndex, {}, true, true);
+ EXPECT_NE(texture.Get(), nullptr);
+ IgnoreSignalSemaphore(device, texture);
+}
+
+// Test an error occurs if the texture descriptor is missing
+TEST_P(VulkanImageWrappingValidationTests, MissingTextureDescriptor) {
+ DAWN_SKIP_TEST_IF(UsesWire() || IsIntel());
+ ASSERT_DEVICE_ERROR(dawn::Texture texture =
+ WrapVulkanImage(device, nullptr, defaultFd, defaultAllocationSize,
+ defaultMemoryTypeIndex, {}, true, false));
+ EXPECT_EQ(texture.Get(), nullptr);
+}
+
+// Test an error occurs if the texture descriptor is invalid
+TEST_P(VulkanImageWrappingValidationTests, InvalidTextureDescriptor) {
+ DAWN_SKIP_TEST_IF(UsesWire() || IsIntel());
+ defaultDescriptor.nextInChain = this;
+
+ ASSERT_DEVICE_ERROR(dawn::Texture texture = WrapVulkanImage(
+ device, &defaultDescriptor, defaultFd, defaultAllocationSize,
+ defaultMemoryTypeIndex, {}, true, false));
+ EXPECT_EQ(texture.Get(), nullptr);
+}
+
+// Test an error occurs if the descriptor dimension isn't 2D
+TEST_P(VulkanImageWrappingValidationTests, InvalidTextureDimension) {
+ DAWN_SKIP_TEST_IF(UsesWire() || IsIntel());
+ defaultDescriptor.dimension = dawn::TextureDimension::e1D;
+
+ ASSERT_DEVICE_ERROR(dawn::Texture texture = WrapVulkanImage(
+ device, &defaultDescriptor, defaultFd, defaultAllocationSize,
+ defaultMemoryTypeIndex, {}, true, false));
+ EXPECT_EQ(texture.Get(), nullptr);
+}
+
+// Test an error occurs if the descriptor mip level count isn't 1
+TEST_P(VulkanImageWrappingValidationTests, InvalidMipLevelCount) {
+ DAWN_SKIP_TEST_IF(UsesWire() || IsIntel());
+ defaultDescriptor.mipLevelCount = 2;
+
+ ASSERT_DEVICE_ERROR(dawn::Texture texture = WrapVulkanImage(
+ device, &defaultDescriptor, defaultFd, defaultAllocationSize,
+ defaultMemoryTypeIndex, {}, true, false));
+ EXPECT_EQ(texture.Get(), nullptr);
+}
+
+// Test an error occurs if the descriptor array layer count isn't 1
+TEST_P(VulkanImageWrappingValidationTests, InvalidArrayLayerCount) {
+ DAWN_SKIP_TEST_IF(UsesWire() || IsIntel());
+ defaultDescriptor.arrayLayerCount = 2;
+
+ ASSERT_DEVICE_ERROR(dawn::Texture texture = WrapVulkanImage(
+ device, &defaultDescriptor, defaultFd, defaultAllocationSize,
+ defaultMemoryTypeIndex, {}, true, false));
+ EXPECT_EQ(texture.Get(), nullptr);
+}
+
+// Test an error occurs if the descriptor sample count isn't 1
+TEST_P(VulkanImageWrappingValidationTests, InvalidSampleCount) {
+ DAWN_SKIP_TEST_IF(UsesWire() || IsIntel());
+ defaultDescriptor.sampleCount = 4;
+
+ ASSERT_DEVICE_ERROR(dawn::Texture texture = WrapVulkanImage(
+ device, &defaultDescriptor, defaultFd, defaultAllocationSize,
+ defaultMemoryTypeIndex, {}, true, false));
+ EXPECT_EQ(texture.Get(), nullptr);
+}
+
+// Test an error occurs if we try to export the signal semaphore twice
+TEST_P(VulkanImageWrappingValidationTests, DoubleSignalSemaphoreExport) {
+ DAWN_SKIP_TEST_IF(UsesWire() || IsIntel());
+ dawn::Texture texture =
+ WrapVulkanImage(device, &defaultDescriptor, defaultFd, defaultAllocationSize,
+ defaultMemoryTypeIndex, {}, true, true);
+ ASSERT_NE(texture.Get(), nullptr);
+ IgnoreSignalSemaphore(device, texture);
+ ASSERT_DEVICE_ERROR(
+ int fd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(device.Get(), texture.Get()));
+ ASSERT_EQ(fd, -1);
+}
+
+// Test an error occurs if we try to export the signal semaphore from a normal texture
+TEST_P(VulkanImageWrappingValidationTests, NormalTextureSignalSemaphoreExport) {
+ DAWN_SKIP_TEST_IF(UsesWire() || IsIntel());
+ dawn::Texture texture = device.CreateTexture(&defaultDescriptor);
+ ASSERT_NE(texture.Get(), nullptr);
+ ASSERT_DEVICE_ERROR(
+ int fd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(device.Get(), texture.Get()));
+ ASSERT_EQ(fd, -1);
+}
+
+// Test an error occurs if we try to export the signal semaphore from a destroyed texture
+TEST_P(VulkanImageWrappingValidationTests, DestroyedTextureSignalSemaphoreExport) {
+ DAWN_SKIP_TEST_IF(UsesWire() || IsIntel());
+ dawn::Texture texture = device.CreateTexture(&defaultDescriptor);
+ ASSERT_NE(texture.Get(), nullptr);
+ texture.Destroy();
+ ASSERT_DEVICE_ERROR(
+ int fd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(device.Get(), texture.Get()));
+ ASSERT_EQ(fd, -1);
+}
+
+// Fixture to test using external memory textures through different usages.
+// These tests are skipped if the harness is using the wire.
+class VulkanImageWrappingUsageTests : public VulkanImageWrappingTestBase {
+ public:
+ void SetUp() override {
+ VulkanImageWrappingTestBase::SetUp();
+ if (UsesWire() || IsIntel()) {
+ return;
+ }
+
+ // Create another device based on the original
+ backendAdapter = reinterpret_cast<dawn_native::vulkan::Adapter*>(deviceVk->GetAdapter());
+ deviceDescriptor.forceEnabledToggles = GetParam().forceEnabledWorkarounds;
+ deviceDescriptor.forceDisabledToggles = GetParam().forceDisabledWorkarounds;
+
+ secondDeviceVk = reinterpret_cast<dawn_native::vulkan::Device*>(
+ backendAdapter->CreateDevice(&deviceDescriptor));
+ secondDevice = dawn::Device::Acquire(reinterpret_cast<DawnDevice>(secondDeviceVk));
+
+ CreateBindExportImage(deviceVk, 1, 1, VK_FORMAT_R8G8B8A8_UNORM, &defaultImage,
+ &defaultAllocation, &defaultAllocationSize, &defaultMemoryTypeIndex,
+ &defaultFd);
+ defaultDescriptor.dimension = dawn::TextureDimension::e2D;
+ defaultDescriptor.format = dawn::TextureFormat::RGBA8Unorm;
+ defaultDescriptor.size = {1, 1, 1};
+ defaultDescriptor.sampleCount = 1;
+ defaultDescriptor.arrayLayerCount = 1;
+ defaultDescriptor.mipLevelCount = 1;
+ defaultDescriptor.usage = dawn::TextureUsageBit::OutputAttachment |
+ dawn::TextureUsageBit::CopySrc | dawn::TextureUsageBit::CopyDst;
+ }
+
+ void TearDown() override {
+ if (UsesWire() || IsIntel()) {
+ VulkanImageWrappingTestBase::TearDown();
+ return;
+ }
+
+ deviceVk->GetFencedDeleter()->DeleteWhenUnused(defaultImage);
+ deviceVk->GetFencedDeleter()->DeleteWhenUnused(defaultAllocation);
+ VulkanImageWrappingTestBase::TearDown();
+ }
+
+ protected:
+ dawn::Device secondDevice;
+ dawn_native::vulkan::Device* secondDeviceVk;
+
+ dawn_native::vulkan::Adapter* backendAdapter;
+ dawn_native::DeviceDescriptor deviceDescriptor;
+
+ dawn::TextureDescriptor defaultDescriptor;
+ VkImage defaultImage;
+ VkDeviceMemory defaultAllocation;
+ VkDeviceSize defaultAllocationSize;
+ uint32_t defaultMemoryTypeIndex;
+ int defaultFd;
+
+ // Clear a texture on a given device
+ void ClearImage(dawn::Device device, dawn::Texture wrappedTexture, dawn::Color clearColor) {
+ dawn::TextureView wrappedView = wrappedTexture.CreateDefaultView();
+
+ // Submit a clear operation
+ utils::ComboRenderPassDescriptor renderPassDescriptor({wrappedView}, {});
+ renderPassDescriptor.cColorAttachmentsInfoPtr[0]->clearColor = clearColor;
+
+ dawn::CommandEncoder encoder = device.CreateCommandEncoder();
+ dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDescriptor);
+ pass.EndPass();
+
+ dawn::CommandBuffer commands = encoder.Finish();
+
+ dawn::Queue queue = device.CreateQueue();
+ queue.Submit(1, &commands);
+ }
+
+ // Submits a 1x1x1 copy from source to destination
+ void SimpleCopyTextureToTexture(dawn::Device device,
+ dawn::Queue queue,
+ dawn::Texture source,
+ dawn::Texture destination) {
+ dawn::TextureCopyView copySrc;
+ copySrc.texture = source;
+ copySrc.mipLevel = 0;
+ copySrc.arrayLayer = 0;
+ copySrc.origin = {0, 0, 0};
+
+ dawn::TextureCopyView copyDst;
+ copyDst.texture = destination;
+ copyDst.mipLevel = 0;
+ copyDst.arrayLayer = 0;
+ copyDst.origin = {0, 0, 0};
+
+ dawn::Extent3D copySize = {1, 1, 1};
+
+ dawn::CommandEncoder encoder = device.CreateCommandEncoder();
+ encoder.CopyTextureToTexture(©Src, ©Dst, ©Size);
+ dawn::CommandBuffer commands = encoder.Finish();
+
+ queue.Submit(1, &commands);
+ }
+};
+
+// Clear an image in |secondDevice|
+// Verify clear color is visible in |device|
+TEST_P(VulkanImageWrappingUsageTests, ClearImageAcrossDevices) {
+ DAWN_SKIP_TEST_IF(UsesWire() || IsIntel());
+
+ // Import the image on |secondDevice|
+ dawn::Texture wrappedTexture =
+ WrapVulkanImage(secondDevice, &defaultDescriptor, defaultFd, defaultAllocationSize,
+ defaultMemoryTypeIndex, {});
+
+ // Clear |wrappedTexture| on |secondDevice|
+ ClearImage(secondDevice, wrappedTexture, {1 / 255.0f, 2 / 255.0f, 3 / 255.0f, 4 / 255.0f});
+
+ int signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(secondDevice.Get(),
+ wrappedTexture.Get());
+
+ // Import the image to |device|, making sure we wait on signalFd
+ int memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
+ dawn::Texture nextWrappedTexture =
+ WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize,
+ defaultMemoryTypeIndex, {signalFd});
+
+ // Verify |device| sees the changes from |secondDevice|
+ EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), nextWrappedTexture, 0, 0);
+
+ IgnoreSignalSemaphore(device, nextWrappedTexture);
+}
+
+// Import texture to |device| and |secondDevice|
+// Clear image in |secondDevice|
+// Verify clear color is visible in |device|
+// Verify the very first import into |device| also sees the change, since it should
+// alias the same memory
+TEST_P(VulkanImageWrappingUsageTests, ClearImageAcrossDevicesAliased) {
+ DAWN_SKIP_TEST_IF(UsesWire() || IsIntel());
+ // Import the image on |device
+ dawn::Texture wrappedTextureAlias = WrapVulkanImage(
+ device, &defaultDescriptor, defaultFd, defaultAllocationSize, defaultMemoryTypeIndex, {});
+
+ int memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
+
+ // Import the image on |secondDevice|
+ dawn::Texture wrappedTexture =
+ WrapVulkanImage(secondDevice, &defaultDescriptor, defaultFd, defaultAllocationSize,
+ defaultMemoryTypeIndex, {});
+
+ // Clear |wrappedTexture| on |secondDevice|
+ ClearImage(secondDevice, wrappedTexture, {1 / 255.0f, 2 / 255.0f, 3 / 255.0f, 4 / 255.0f});
+
+ int signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(secondDevice.Get(),
+ wrappedTexture.Get());
+
+ // Import the image to |device|, making sure we wait on signalFd
+ memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
+ dawn::Texture nextWrappedTexture =
+ WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize,
+ defaultMemoryTypeIndex, {signalFd});
+
+ // Verify |device| sees the changes from |secondDevice| (waits)
+ EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), nextWrappedTexture, 0, 0);
+
+ // Verify aliased texture sees changes from |secondDevice| (without waiting!)
+ EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), wrappedTextureAlias, 0, 0);
+
+ IgnoreSignalSemaphore(device, nextWrappedTexture);
+ IgnoreSignalSemaphore(device, wrappedTextureAlias);
+}
+
+// Clear an image in |secondDevice|
+// Verify clear color is not visible in |device| if we import the texture as not cleared
+TEST_P(VulkanImageWrappingUsageTests, UnclearedTextureIsCleared) {
+ DAWN_SKIP_TEST_IF(UsesWire() || IsIntel());
+
+ // Import the image on |secondDevice|
+ dawn::Texture wrappedTexture =
+ WrapVulkanImage(secondDevice, &defaultDescriptor, defaultFd, defaultAllocationSize,
+ defaultMemoryTypeIndex, {});
+
+ // Clear |wrappedTexture| on |secondDevice|
+ ClearImage(secondDevice, wrappedTexture, {1 / 255.0f, 2 / 255.0f, 3 / 255.0f, 4 / 255.0f});
+
+ int signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(secondDevice.Get(),
+ wrappedTexture.Get());
+
+ // Import the image to |device|, making sure we wait on signalFd
+ int memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
+ dawn::Texture nextWrappedTexture =
+ WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize,
+ defaultMemoryTypeIndex, {signalFd}, false);
+
+ // Verify |device| doesn't see the changes from |secondDevice|
+ EXPECT_PIXEL_RGBA8_EQ(RGBA8(0, 0, 0, 0), nextWrappedTexture, 0, 0);
+
+ IgnoreSignalSemaphore(device, nextWrappedTexture);
+}
+
+// Import a texture into |secondDevice|
+// Issue a copy of the imported texture inside |device| to |copyDstTexture|
+// Verify the clear color from |secondDevice| is visible in |copyDstTexture|
+TEST_P(VulkanImageWrappingUsageTests, CopyTextureToTextureSrcSync) {
+ DAWN_SKIP_TEST_IF(UsesWire() || IsIntel());
+
+ // Import the image on |secondDevice|
+ dawn::Texture wrappedTexture =
+ WrapVulkanImage(secondDevice, &defaultDescriptor, defaultFd, defaultAllocationSize,
+ defaultMemoryTypeIndex, {});
+
+ // Clear |wrappedTexture| on |secondDevice|
+ ClearImage(secondDevice, wrappedTexture, {1 / 255.0f, 2 / 255.0f, 3 / 255.0f, 4 / 255.0f});
+
+ int signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(secondDevice.Get(),
+ wrappedTexture.Get());
+
+ // Import the image to |device|, making sure we wait on |signalFd|
+ int memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
+ dawn::Texture deviceWrappedTexture =
+ WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize,
+ defaultMemoryTypeIndex, {signalFd});
+
+ // Create a second texture on |device|
+ dawn::Texture copyDstTexture = device.CreateTexture(&defaultDescriptor);
+
+ // Copy |deviceWrappedTexture| into |copyDstTexture|
+ SimpleCopyTextureToTexture(device, queue, deviceWrappedTexture, copyDstTexture);
+
+ // Verify |copyDstTexture| sees changes from |secondDevice|
+ EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), copyDstTexture, 0, 0);
+
+ IgnoreSignalSemaphore(device, deviceWrappedTexture);
+}
+
+// Import a texture into |device|
+// Copy color A into texture on |device|
+// Import same texture into |secondDevice|, waiting on the copy signal
+// Copy color B using Texture to Texture copy on |secondDevice|
+// Import texture back into |device|, waiting on color B signal
+// Verify texture contains color B
+// If texture destination isn't synchronized, |secondDevice| could copy color B
+// into the texture first, then |device| writes color A
+TEST_P(VulkanImageWrappingUsageTests, CopyTextureToTextureDstSync) {
+ DAWN_SKIP_TEST_IF(UsesWire() || IsIntel());
+
+ // Import the image on |device|
+ dawn::Texture wrappedTexture = WrapVulkanImage(
+ device, &defaultDescriptor, defaultFd, defaultAllocationSize, defaultMemoryTypeIndex, {});
+
+ // Clear |wrappedTexture| on |device|
+ ClearImage(device, wrappedTexture, {5 / 255.0f, 6 / 255.0f, 7 / 255.0f, 8 / 255.0f});
+
+ int signalFd =
+ dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(device.Get(), wrappedTexture.Get());
+
+ // Import the image to |secondDevice|, making sure we wait on |signalFd|
+ int memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
+ dawn::Texture secondDeviceWrappedTexture =
+ WrapVulkanImage(secondDevice, &defaultDescriptor, memoryFd, defaultAllocationSize,
+ defaultMemoryTypeIndex, {signalFd});
+
+ // Create a texture with color B on |secondDevice|
+ dawn::Texture copySrcTexture = secondDevice.CreateTexture(&defaultDescriptor);
+ ClearImage(secondDevice, copySrcTexture, {1 / 255.0f, 2 / 255.0f, 3 / 255.0f, 4 / 255.0f});
+
+ // Copy color B on |secondDevice|
+ dawn::Queue secondDeviceQueue = secondDevice.CreateQueue();
+ SimpleCopyTextureToTexture(secondDevice, secondDeviceQueue, copySrcTexture,
+ secondDeviceWrappedTexture);
+
+ // Re-import back into |device|, waiting on |secondDevice|'s signal
+ signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(secondDevice.Get(),
+ secondDeviceWrappedTexture.Get());
+ memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
+
+ dawn::Texture nextWrappedTexture =
+ WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize,
+ defaultMemoryTypeIndex, {signalFd});
+
+ // Verify |nextWrappedTexture| contains the color from our copy
+ EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), nextWrappedTexture, 0, 0);
+
+ IgnoreSignalSemaphore(device, nextWrappedTexture);
+}
+
+// Import a texture from |secondDevice|
+// Issue a copy of the imported texture inside |device| to |copyDstBuffer|
+// Verify the clear color from |secondDevice| is visible in |copyDstBuffer|
+TEST_P(VulkanImageWrappingUsageTests, CopyTextureToBufferSrcSync) {
+ DAWN_SKIP_TEST_IF(UsesWire() || IsIntel());
+
+ // Import the image on |secondDevice|
+ dawn::Texture wrappedTexture =
+ WrapVulkanImage(secondDevice, &defaultDescriptor, defaultFd, defaultAllocationSize,
+ defaultMemoryTypeIndex, {});
+
+ // Clear |wrappedTexture| on |secondDevice|
+ ClearImage(secondDevice, wrappedTexture, {1 / 255.0f, 2 / 255.0f, 3 / 255.0f, 4 / 255.0f});
+
+ int signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(secondDevice.Get(),
+ wrappedTexture.Get());
+
+ // Import the image to |device|, making sure we wait on |signalFd|
+ int memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
+ dawn::Texture deviceWrappedTexture =
+ WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize,
+ defaultMemoryTypeIndex, {signalFd});
+
+ // Create a destination buffer on |device|
+ dawn::BufferDescriptor bufferDesc;
+ bufferDesc.size = 4;
+ bufferDesc.usage = dawn::BufferUsageBit::CopyDst | dawn::BufferUsageBit::CopySrc;
+ dawn::Buffer copyDstBuffer = device.CreateBuffer(&bufferDesc);
+
+ // Copy |deviceWrappedTexture| into |copyDstBuffer|
+ dawn::TextureCopyView copySrc;
+ copySrc.texture = deviceWrappedTexture;
+ copySrc.mipLevel = 0;
+ copySrc.arrayLayer = 0;
+ copySrc.origin = {0, 0, 0};
+
+ dawn::BufferCopyView copyDst;
+ copyDst.buffer = copyDstBuffer;
+ copyDst.offset = 0;
+ copyDst.rowPitch = 256;
+ copyDst.imageHeight = 0;
+
+ dawn::Extent3D copySize = {1, 1, 1};
+
+ dawn::CommandEncoder encoder = device.CreateCommandEncoder();
+ encoder.CopyTextureToBuffer(©Src, ©Dst, ©Size);
+ dawn::CommandBuffer commands = encoder.Finish();
+ queue.Submit(1, &commands);
+
+ // Verify |copyDstBuffer| sees changes from |secondDevice|
+ uint32_t expected = 0x04030201;
+ EXPECT_BUFFER_U32_EQ(expected, copyDstBuffer, 0);
+
+ IgnoreSignalSemaphore(device, deviceWrappedTexture);
+}
+
+// Import a texture into |device|
+// Copy color A into texture on |device|
+// Import same texture into |secondDevice|, waiting on the copy signal
+// Copy color B using Buffer to Texture copy on |secondDevice|
+// Import texture back into |device|, waiting on color B signal
+// Verify texture contains color B
+// If texture destination isn't synchronized, |secondDevice| could copy color B
+// into the texture first, then |device| writes color A
+TEST_P(VulkanImageWrappingUsageTests, CopyBufferToTextureDstSync) {
+ DAWN_SKIP_TEST_IF(UsesWire() || IsIntel());
+
+ // Import the image on |device|
+ dawn::Texture wrappedTexture = WrapVulkanImage(
+ device, &defaultDescriptor, defaultFd, defaultAllocationSize, defaultMemoryTypeIndex, {});
+
+ // Clear |wrappedTexture| on |device|
+ ClearImage(device, wrappedTexture, {5 / 255.0f, 6 / 255.0f, 7 / 255.0f, 8 / 255.0f});
+
+ int signalFd =
+ dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(device.Get(), wrappedTexture.Get());
+
+ // Import the image to |secondDevice|, making sure we wait on |signalFd|
+ int memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
+ dawn::Texture secondDeviceWrappedTexture =
+ WrapVulkanImage(secondDevice, &defaultDescriptor, memoryFd, defaultAllocationSize,
+ defaultMemoryTypeIndex, {signalFd});
+
+ // Copy color B on |secondDevice|
+ dawn::Queue secondDeviceQueue = secondDevice.CreateQueue();
+
+ // Create a buffer on |secondDevice|
+ dawn::Buffer copySrcBuffer =
+ utils::CreateBufferFromData(secondDevice, dawn::BufferUsageBit::CopySrc, {0x04030201});
+
+ // Copy |copySrcBuffer| into |secondDeviceWrappedTexture|
+ dawn::BufferCopyView copySrc;
+ copySrc.buffer = copySrcBuffer;
+ copySrc.offset = 0;
+ copySrc.rowPitch = 256;
+ copySrc.imageHeight = 0;
+
+ dawn::TextureCopyView copyDst;
+ copyDst.texture = secondDeviceWrappedTexture;
+ copyDst.mipLevel = 0;
+ copyDst.arrayLayer = 0;
+ copyDst.origin = {0, 0, 0};
+
+ dawn::Extent3D copySize = {1, 1, 1};
+
+ dawn::CommandEncoder encoder = secondDevice.CreateCommandEncoder();
+ encoder.CopyBufferToTexture(©Src, ©Dst, ©Size);
+ dawn::CommandBuffer commands = encoder.Finish();
+ secondDeviceQueue.Submit(1, &commands);
+
+ // Re-import back into |device|, waiting on |secondDevice|'s signal
+ signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(secondDevice.Get(),
+ secondDeviceWrappedTexture.Get());
+ memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
+
+ dawn::Texture nextWrappedTexture =
+ WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize,
+ defaultMemoryTypeIndex, {signalFd});
+
+ // Verify |nextWrappedTexture| contains the color from our copy
+ EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), nextWrappedTexture, 0, 0);
+
+ IgnoreSignalSemaphore(device, nextWrappedTexture);
+}
+
+// Import a texture from |secondDevice|
+// Issue a copy of the imported texture inside |device| to |copyDstTexture|
+// Issue second copy to |secondCopyDstTexture|
+// Verify the clear color from |secondDevice| is visible in both copies
+TEST_P(VulkanImageWrappingUsageTests, DoubleTextureUsage) {
+ DAWN_SKIP_TEST_IF(UsesWire() || IsIntel());
+
+ // Import the image on |secondDevice|
+ dawn::Texture wrappedTexture =
+ WrapVulkanImage(secondDevice, &defaultDescriptor, defaultFd, defaultAllocationSize,
+ defaultMemoryTypeIndex, {});
+
+ // Clear |wrappedTexture| on |secondDevice|
+ ClearImage(secondDevice, wrappedTexture, {1 / 255.0f, 2 / 255.0f, 3 / 255.0f, 4 / 255.0f});
+
+ int signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(secondDevice.Get(),
+ wrappedTexture.Get());
+
+ // Import the image to |device|, making sure we wait on |signalFd|
+ int memoryFd = GetMemoryFd(deviceVk, defaultAllocation);
+ dawn::Texture deviceWrappedTexture =
+ WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize,
+ defaultMemoryTypeIndex, {signalFd});
+
+ // Create a second texture on |device|
+ dawn::Texture copyDstTexture = device.CreateTexture(&defaultDescriptor);
+
+ // Create a third texture on |device|
+ dawn::Texture secondCopyDstTexture = device.CreateTexture(&defaultDescriptor);
+
+ // Copy |deviceWrappedTexture| into |copyDstTexture|
+ SimpleCopyTextureToTexture(device, queue, deviceWrappedTexture, copyDstTexture);
+
+ // Copy |deviceWrappedTexture| into |secondCopyDstTexture|
+ SimpleCopyTextureToTexture(device, queue, deviceWrappedTexture, secondCopyDstTexture);
+
+ // Verify |copyDstTexture| sees changes from |secondDevice|
+ EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), copyDstTexture, 0, 0);
+
+ // Verify |secondCopyDstTexture| sees changes from |secondDevice|
+ EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), secondCopyDstTexture, 0, 0);
+
+ IgnoreSignalSemaphore(device, deviceWrappedTexture);
+}
+
+// Tex A on device 3 (external export)
+// Tex B on device 2 (external export)
+// Tex C on device 1 (external export)
+// Clear color for A on device 3
+// Copy A->B on device 3
+// Copy B->C on device 2 (wait on B from previous op)
+// Copy C->D on device 1 (wait on C from previous op)
+// Verify D has same color as A
+TEST_P(VulkanImageWrappingUsageTests, ChainTextureCopy) {
+ DAWN_SKIP_TEST_IF(UsesWire() || IsIntel());
+
+ // Close |defaultFd| since this test doesn't import it anywhere
+ close(defaultFd);
+
+ // device 1 = |device|
+ // device 2 = |secondDevice|
+ // Create device 3
+ dawn_native::vulkan::Device* thirdDeviceVk = reinterpret_cast<dawn_native::vulkan::Device*>(
+ backendAdapter->CreateDevice(&deviceDescriptor));
+ dawn::Device thirdDevice = dawn::Device::Acquire(reinterpret_cast<DawnDevice>(thirdDeviceVk));
+
+ // Make queue for device 2 and 3
+ dawn::Queue secondDeviceQueue = secondDevice.CreateQueue();
+ dawn::Queue thirdDeviceQueue = thirdDevice.CreateQueue();
+
+ // Allocate memory for A, B, C
+ VkImage imageA;
+ VkDeviceMemory allocationA;
+ int memoryFdA;
+ VkDeviceSize allocationSizeA;
+ uint32_t memoryTypeIndexA;
+ CreateBindExportImage(thirdDeviceVk, 1, 1, VK_FORMAT_R8G8B8A8_UNORM, &imageA, &allocationA,
+ &allocationSizeA, &memoryTypeIndexA, &memoryFdA);
+
+ VkImage imageB;
+ VkDeviceMemory allocationB;
+ int memoryFdB;
+ VkDeviceSize allocationSizeB;
+ uint32_t memoryTypeIndexB;
+ CreateBindExportImage(secondDeviceVk, 1, 1, VK_FORMAT_R8G8B8A8_UNORM, &imageB, &allocationB,
+ &allocationSizeB, &memoryTypeIndexB, &memoryFdB);
+
+ VkImage imageC;
+ VkDeviceMemory allocationC;
+ int memoryFdC;
+ VkDeviceSize allocationSizeC;
+ uint32_t memoryTypeIndexC;
+ CreateBindExportImage(deviceVk, 1, 1, VK_FORMAT_R8G8B8A8_UNORM, &imageC, &allocationC,
+ &allocationSizeC, &memoryTypeIndexC, &memoryFdC);
+
+ // Import TexA, TexB on device 3
+ dawn::Texture wrappedTexADevice3 = WrapVulkanImage(thirdDevice, &defaultDescriptor, memoryFdA,
+ allocationSizeA, memoryTypeIndexA, {});
+
+ dawn::Texture wrappedTexBDevice3 = WrapVulkanImage(thirdDevice, &defaultDescriptor, memoryFdB,
+ allocationSizeB, memoryTypeIndexB, {});
+
+ // Clear TexA
+ ClearImage(thirdDevice, wrappedTexADevice3, {1 / 255.0f, 2 / 255.0f, 3 / 255.0f, 4 / 255.0f});
+
+ // Copy A->B
+ SimpleCopyTextureToTexture(thirdDevice, thirdDeviceQueue, wrappedTexADevice3,
+ wrappedTexBDevice3);
+
+ int signalFdTexBDevice3 = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(
+ thirdDevice.Get(), wrappedTexBDevice3.Get());
+ IgnoreSignalSemaphore(thirdDevice, wrappedTexADevice3);
+
+ // Import TexB, TexC on device 2
+ memoryFdB = GetMemoryFd(secondDeviceVk, allocationB);
+ dawn::Texture wrappedTexBDevice2 =
+ WrapVulkanImage(secondDevice, &defaultDescriptor, memoryFdB, allocationSizeB,
+ memoryTypeIndexB, {signalFdTexBDevice3});
+
+ dawn::Texture wrappedTexCDevice2 = WrapVulkanImage(secondDevice, &defaultDescriptor, memoryFdC,
+ allocationSizeC, memoryTypeIndexC, {});
+
+ // Copy B->C on device 2
+ SimpleCopyTextureToTexture(secondDevice, secondDeviceQueue, wrappedTexBDevice2,
+ wrappedTexCDevice2);
+
+ int signalFdTexCDevice2 = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(
+ secondDevice.Get(), wrappedTexCDevice2.Get());
+ IgnoreSignalSemaphore(secondDevice, wrappedTexBDevice2);
+
+ // Import TexC on device 1
+ memoryFdC = GetMemoryFd(deviceVk, allocationC);
+ dawn::Texture wrappedTexCDevice1 =
+ WrapVulkanImage(device, &defaultDescriptor, memoryFdC, allocationSizeC, memoryTypeIndexC,
+ {signalFdTexCDevice2});
+
+ // Create TexD on device 1
+ dawn::Texture texD = device.CreateTexture(&defaultDescriptor);
+
+ // Copy C->D on device 1
+ SimpleCopyTextureToTexture(device, queue, wrappedTexCDevice1, texD);
+
+ // Verify D matches clear color
+ EXPECT_PIXEL_RGBA8_EQ(RGBA8(1, 2, 3, 4), texD, 0, 0);
+
+ thirdDeviceVk->GetFencedDeleter()->DeleteWhenUnused(imageA);
+ thirdDeviceVk->GetFencedDeleter()->DeleteWhenUnused(allocationA);
+ secondDeviceVk->GetFencedDeleter()->DeleteWhenUnused(imageB);
+ secondDeviceVk->GetFencedDeleter()->DeleteWhenUnused(allocationB);
+ deviceVk->GetFencedDeleter()->DeleteWhenUnused(imageC);
+ deviceVk->GetFencedDeleter()->DeleteWhenUnused(allocationC);
+
+ IgnoreSignalSemaphore(device, wrappedTexCDevice1);
+}
+
+DAWN_INSTANTIATE_TEST(VulkanImageWrappingValidationTests, VulkanBackend);
+DAWN_INSTANTIATE_TEST(VulkanImageWrappingUsageTests, VulkanBackend);