// 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 "common/Math.h" #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/ResourceMemoryAllocatorVk.h" #include "dawn_native/vulkan/TextureVk.h" #include "utils/SystemUtils.h" #include "utils/WGPUHelpers.h" namespace dawn_native { namespace vulkan { namespace { class VulkanImageWrappingTestBase : public DawnTest { public: void TestSetUp() override { if (UsesWire()) { return; } deviceVk = reinterpret_cast(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->GetResourceMemoryAllocatorForTesting()->FindBestTypeIndex( requirements, false); VkMemoryAllocateInfo allocateInfo; allocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; allocateInfo.pNext = &externalInfo; allocateInfo.allocationSize = requirements.size; allocateInfo.memoryTypeIndex = static_cast(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 wgpu::Texture WrapVulkanImage(wgpu::Device device, const wgpu::TextureDescriptor* textureDescriptor, int memoryFd, VkDeviceSize allocationSize, uint32_t memoryTypeIndex, std::vector waitFDs, bool isCleared = true, bool expectValid = true) { dawn_native::vulkan::ExternalImageDescriptorOpaqueFD descriptor; descriptor.cTextureDescriptor = reinterpret_cast(textureDescriptor); descriptor.isCleared = isCleared; descriptor.allocationSize = allocationSize; descriptor.memoryTypeIndex = memoryTypeIndex; descriptor.memoryFD = memoryFd; descriptor.waitFDs = waitFDs; WGPUTexture texture = dawn_native::vulkan::WrapVulkanImage(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 wgpu::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(wgpu::Device device, wgpu::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 TestSetUp() override { VulkanImageWrappingTestBase::TestSetUp(); if (UsesWire()) { return; } CreateBindExportImage(deviceVk, 1, 1, VK_FORMAT_R8G8B8A8_UNORM, &defaultImage, &defaultAllocation, &defaultAllocationSize, &defaultMemoryTypeIndex, &defaultFd); defaultDescriptor.dimension = wgpu::TextureDimension::e2D; defaultDescriptor.format = wgpu::TextureFormat::RGBA8Unorm; defaultDescriptor.size = {1, 1, 1}; defaultDescriptor.sampleCount = 1; defaultDescriptor.arrayLayerCount = 1; defaultDescriptor.mipLevelCount = 1; defaultDescriptor.usage = wgpu::TextureUsage::OutputAttachment | wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst; } void TearDown() override { if (UsesWire()) { VulkanImageWrappingTestBase::TearDown(); return; } deviceVk->GetFencedDeleter()->DeleteWhenUnused(defaultImage); deviceVk->GetFencedDeleter()->DeleteWhenUnused(defaultAllocation); VulkanImageWrappingTestBase::TearDown(); } protected: wgpu::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()); wgpu::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()); ASSERT_DEVICE_ERROR(wgpu::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()); wgpu::ChainedStruct chainedDescriptor; defaultDescriptor.nextInChain = &chainedDescriptor; ASSERT_DEVICE_ERROR(wgpu::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()); defaultDescriptor.dimension = wgpu::TextureDimension::e1D; ASSERT_DEVICE_ERROR(wgpu::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()); defaultDescriptor.mipLevelCount = 2; ASSERT_DEVICE_ERROR(wgpu::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()); defaultDescriptor.arrayLayerCount = 2; ASSERT_DEVICE_ERROR(wgpu::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()); defaultDescriptor.sampleCount = 4; ASSERT_DEVICE_ERROR(wgpu::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()); wgpu::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()); wgpu::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()); wgpu::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 TestSetUp() override { VulkanImageWrappingTestBase::TestSetUp(); if (UsesWire()) { return; } // Create another device based on the original backendAdapter = reinterpret_cast(deviceVk->GetAdapter()); deviceDescriptor.forceEnabledToggles = GetParam().forceEnabledWorkarounds; deviceDescriptor.forceDisabledToggles = GetParam().forceDisabledWorkarounds; secondDeviceVk = reinterpret_cast( backendAdapter->CreateDevice(&deviceDescriptor)); secondDevice = wgpu::Device::Acquire(reinterpret_cast(secondDeviceVk)); CreateBindExportImage(deviceVk, 1, 1, VK_FORMAT_R8G8B8A8_UNORM, &defaultImage, &defaultAllocation, &defaultAllocationSize, &defaultMemoryTypeIndex, &defaultFd); defaultDescriptor.dimension = wgpu::TextureDimension::e2D; defaultDescriptor.format = wgpu::TextureFormat::RGBA8Unorm; defaultDescriptor.size = {1, 1, 1}; defaultDescriptor.sampleCount = 1; defaultDescriptor.arrayLayerCount = 1; defaultDescriptor.mipLevelCount = 1; defaultDescriptor.usage = wgpu::TextureUsage::OutputAttachment | wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst; } void TearDown() override { if (UsesWire()) { VulkanImageWrappingTestBase::TearDown(); return; } deviceVk->GetFencedDeleter()->DeleteWhenUnused(defaultImage); deviceVk->GetFencedDeleter()->DeleteWhenUnused(defaultAllocation); VulkanImageWrappingTestBase::TearDown(); } protected: wgpu::Device secondDevice; dawn_native::vulkan::Device* secondDeviceVk; dawn_native::vulkan::Adapter* backendAdapter; dawn_native::DeviceDescriptor deviceDescriptor; wgpu::TextureDescriptor defaultDescriptor; VkImage defaultImage; VkDeviceMemory defaultAllocation; VkDeviceSize defaultAllocationSize; uint32_t defaultMemoryTypeIndex; int defaultFd; // Clear a texture on a given device void ClearImage(wgpu::Device device, wgpu::Texture wrappedTexture, wgpu::Color clearColor) { wgpu::TextureView wrappedView = wrappedTexture.CreateView(); // Submit a clear operation utils::ComboRenderPassDescriptor renderPassDescriptor({wrappedView}, {}); renderPassDescriptor.cColorAttachments[0].clearColor = clearColor; wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDescriptor); pass.EndPass(); wgpu::CommandBuffer commands = encoder.Finish(); wgpu::Queue queue = device.CreateQueue(); queue.Submit(1, &commands); } // Submits a 1x1x1 copy from source to destination void SimpleCopyTextureToTexture(wgpu::Device device, wgpu::Queue queue, wgpu::Texture source, wgpu::Texture destination) { wgpu::TextureCopyView copySrc; copySrc.texture = source; copySrc.mipLevel = 0; copySrc.arrayLayer = 0; copySrc.origin = {0, 0, 0}; wgpu::TextureCopyView copyDst; copyDst.texture = destination; copyDst.mipLevel = 0; copyDst.arrayLayer = 0; copyDst.origin = {0, 0, 0}; wgpu::Extent3D copySize = {1, 1, 1}; wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); encoder.CopyTextureToTexture(©Src, ©Dst, ©Size); wgpu::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()); // Import the image on |secondDevice| wgpu::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); wgpu::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()); // Import the image on |device wgpu::Texture wrappedTextureAlias = WrapVulkanImage(device, &defaultDescriptor, defaultFd, defaultAllocationSize, defaultMemoryTypeIndex, {}); int memoryFd = GetMemoryFd(deviceVk, defaultAllocation); // Import the image on |secondDevice| wgpu::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); wgpu::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()); // Import the image on |secondDevice| wgpu::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); wgpu::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()); // Import the image on |secondDevice| wgpu::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); wgpu::Texture deviceWrappedTexture = WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize, defaultMemoryTypeIndex, {signalFd}); // Create a second texture on |device| wgpu::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()); // Import the image on |device| wgpu::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); wgpu::Texture secondDeviceWrappedTexture = WrapVulkanImage(secondDevice, &defaultDescriptor, memoryFd, defaultAllocationSize, defaultMemoryTypeIndex, {signalFd}); // Create a texture with color B on |secondDevice| wgpu::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| wgpu::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); wgpu::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()); // Import the image on |secondDevice| wgpu::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); wgpu::Texture deviceWrappedTexture = WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize, defaultMemoryTypeIndex, {signalFd}); // Create a destination buffer on |device| wgpu::BufferDescriptor bufferDesc; bufferDesc.size = 4; bufferDesc.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc; wgpu::Buffer copyDstBuffer = device.CreateBuffer(&bufferDesc); // Copy |deviceWrappedTexture| into |copyDstBuffer| wgpu::TextureCopyView copySrc; copySrc.texture = deviceWrappedTexture; copySrc.mipLevel = 0; copySrc.arrayLayer = 0; copySrc.origin = {0, 0, 0}; wgpu::BufferCopyView copyDst; copyDst.buffer = copyDstBuffer; copyDst.offset = 0; copyDst.rowPitch = 256; copyDst.imageHeight = 0; wgpu::Extent3D copySize = {1, 1, 1}; wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); encoder.CopyTextureToBuffer(©Src, ©Dst, ©Size); wgpu::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()); // Import the image on |device| wgpu::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); wgpu::Texture secondDeviceWrappedTexture = WrapVulkanImage(secondDevice, &defaultDescriptor, memoryFd, defaultAllocationSize, defaultMemoryTypeIndex, {signalFd}); // Copy color B on |secondDevice| wgpu::Queue secondDeviceQueue = secondDevice.CreateQueue(); // Create a buffer on |secondDevice| wgpu::Buffer copySrcBuffer = utils::CreateBufferFromData(secondDevice, wgpu::BufferUsage::CopySrc, {0x04030201}); // Copy |copySrcBuffer| into |secondDeviceWrappedTexture| wgpu::BufferCopyView copySrc; copySrc.buffer = copySrcBuffer; copySrc.offset = 0; copySrc.rowPitch = 256; copySrc.imageHeight = 0; wgpu::TextureCopyView copyDst; copyDst.texture = secondDeviceWrappedTexture; copyDst.mipLevel = 0; copyDst.arrayLayer = 0; copyDst.origin = {0, 0, 0}; wgpu::Extent3D copySize = {1, 1, 1}; wgpu::CommandEncoder encoder = secondDevice.CreateCommandEncoder(); encoder.CopyBufferToTexture(©Src, ©Dst, ©Size); wgpu::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); wgpu::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()); // Import the image on |secondDevice| wgpu::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); wgpu::Texture deviceWrappedTexture = WrapVulkanImage(device, &defaultDescriptor, memoryFd, defaultAllocationSize, defaultMemoryTypeIndex, {signalFd}); // Create a second texture on |device| wgpu::Texture copyDstTexture = device.CreateTexture(&defaultDescriptor); // Create a third texture on |device| wgpu::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()); // 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( backendAdapter->CreateDevice(&deviceDescriptor)); wgpu::Device thirdDevice = wgpu::Device::Acquire(reinterpret_cast(thirdDeviceVk)); // Make queue for device 2 and 3 wgpu::Queue secondDeviceQueue = secondDevice.CreateQueue(); wgpu::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 wgpu::Texture wrappedTexADevice3 = WrapVulkanImage( thirdDevice, &defaultDescriptor, memoryFdA, allocationSizeA, memoryTypeIndexA, {}); wgpu::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); wgpu::Texture wrappedTexBDevice2 = WrapVulkanImage(secondDevice, &defaultDescriptor, memoryFdB, allocationSizeB, memoryTypeIndexB, {signalFdTexBDevice3}); wgpu::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); wgpu::Texture wrappedTexCDevice1 = WrapVulkanImage(device, &defaultDescriptor, memoryFdC, allocationSizeC, memoryTypeIndexC, {signalFdTexCDevice2}); // Create TexD on device 1 wgpu::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); } // Tests a larger image is preserved when importing // TODO(http://crbug.com/dawn/206): This fails on AMD TEST_P(VulkanImageWrappingUsageTests, LargerImage) { DAWN_SKIP_TEST_IF(UsesWire() || IsAMD()); close(defaultFd); wgpu::TextureDescriptor descriptor; descriptor.dimension = wgpu::TextureDimension::e2D; descriptor.size.width = 640; descriptor.size.height = 480; descriptor.size.depth = 1; descriptor.arrayLayerCount = 1; descriptor.sampleCount = 1; descriptor.format = wgpu::TextureFormat::BGRA8Unorm; descriptor.mipLevelCount = 1; descriptor.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc; // Fill memory with textures to trigger layout issues on AMD std::vector textures; for (int i = 0; i < 20; i++) { textures.push_back(device.CreateTexture(&descriptor)); } wgpu::Queue secondDeviceQueue = secondDevice.CreateQueue(); // Make an image on |secondDevice| VkImage imageA; VkDeviceMemory allocationA; int memoryFdA; VkDeviceSize allocationSizeA; uint32_t memoryTypeIndexA; CreateBindExportImage(secondDeviceVk, 640, 480, VK_FORMAT_R8G8B8A8_UNORM, &imageA, &allocationA, &allocationSizeA, &memoryTypeIndexA, &memoryFdA); // Import the image on |secondDevice| wgpu::Texture wrappedTexture = WrapVulkanImage(secondDevice, &descriptor, memoryFdA, allocationSizeA, memoryTypeIndexA, {}); // Draw a non-trivial picture int width = 640, height = 480, pixelSize = 4; uint32_t rowPitch = Align(width * pixelSize, kTextureRowPitchAlignment); uint32_t size = rowPitch * (height - 1) + width * pixelSize; unsigned char data[size]; for (int row = 0; row < height; row++) { for (int col = 0; col < width; col++) { float normRow = static_cast(row) / height; float normCol = static_cast(col) / width; float dist = sqrt(normRow * normRow + normCol * normCol) * 3; dist = dist - static_cast(dist); data[4 * (row * width + col)] = static_cast(dist * 255); data[4 * (row * width + col) + 1] = static_cast(dist * 255); data[4 * (row * width + col) + 2] = static_cast(dist * 255); data[4 * (row * width + col) + 3] = 255; } } // Write the picture { wgpu::Buffer copySrcBuffer = utils::CreateBufferFromData(secondDevice, data, size, wgpu::BufferUsage::CopySrc); wgpu::BufferCopyView copySrc = utils::CreateBufferCopyView(copySrcBuffer, 0, rowPitch, 0); wgpu::TextureCopyView copyDst = utils::CreateTextureCopyView(wrappedTexture, 0, 0, {0, 0, 0}); wgpu::Extent3D copySize = {width, height, 1}; wgpu::CommandEncoder encoder = secondDevice.CreateCommandEncoder(); encoder.CopyBufferToTexture(©Src, ©Dst, ©Size); wgpu::CommandBuffer commands = encoder.Finish(); secondDeviceQueue.Submit(1, &commands); } int signalFd = dawn_native::vulkan::ExportSignalSemaphoreOpaqueFD(secondDevice.Get(), wrappedTexture.Get()); int memoryFd = GetMemoryFd(secondDeviceVk, allocationA); // Import the image on |device| wgpu::Texture nextWrappedTexture = WrapVulkanImage( device, &descriptor, memoryFd, allocationSizeA, memoryTypeIndexA, {signalFd}); // Copy the image into a buffer for comparison wgpu::BufferDescriptor copyDesc; copyDesc.size = size; copyDesc.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst; wgpu::Buffer copyDstBuffer = device.CreateBuffer(©Desc); { wgpu::TextureCopyView copySrc = utils::CreateTextureCopyView(nextWrappedTexture, 0, 0, {0, 0, 0}); wgpu::BufferCopyView copyDst = utils::CreateBufferCopyView(copyDstBuffer, 0, rowPitch, 0); wgpu::Extent3D copySize = {width, height, 1}; wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); encoder.CopyTextureToBuffer(©Src, ©Dst, ©Size); wgpu::CommandBuffer commands = encoder.Finish(); queue.Submit(1, &commands); } // Check the image is not corrupted on |device| EXPECT_BUFFER_U32_RANGE_EQ(reinterpret_cast(data), copyDstBuffer, 0, size / 4); IgnoreSignalSemaphore(device, nextWrappedTexture); secondDeviceVk->GetFencedDeleter()->DeleteWhenUnused(imageA); secondDeviceVk->GetFencedDeleter()->DeleteWhenUnused(allocationA); } DAWN_INSTANTIATE_TEST(VulkanImageWrappingValidationTests, VulkanBackend()); DAWN_INSTANTIATE_TEST(VulkanImageWrappingUsageTests, VulkanBackend()); }} // namespace dawn_native::vulkan