dawn-cmake/src/tests/white_box/VulkanImageWrappingTestsOpaqueFD.cpp
Brian Ho 0bbfec1f7f Create VulkanImageWrappingTests for dma-buf images
This CL branches the existing VulkanImageWrappingTests to separate
tests for OpaqueFD-backed amd DmaBuf-backed external images. On
Chrome OS of Dawn, we no longer interop using opaque FDs, so these
tests were failing in the end2end test suite.

The new VulkanImageWrappingTestsDmaBuf tests are essentially 1:1
mappings of their counterparts in the Opaque FD version. The only
difference is that we allocate memory directly on the device using
GBM instead of creating a VkImage (which will likely call some GBM
methods under the hood) and then extracting the FD using a Vulkan
extension. We then communicate this to Dawn via the DmaBuf
ExternalImageDescriptor.

Also, this fixes VulkanImageWrappingUsageTests::LargeImage on AMD
devices (assuming the extension is implemented) as we can now
specify DRM modifiers.

Bug: chromium:996470
Change-Id: I2b3c57d7f5ff14131d415e99a09d32d2f16b3e54
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/15800
Commit-Queue: Brian Ho <hob@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
2020-02-18 20:57:36 +00:00

1033 lines
47 KiB
C++

// 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<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->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<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
wgpu::Texture WrapVulkanImage(wgpu::Device device,
const wgpu::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 WGPUTextureDescriptor*>(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<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 = wgpu::Device::Acquire(reinterpret_cast<WGPUDevice>(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(&copySrc, &copyDst, &copySize);
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(&copySrc, &copyDst, &copySize);
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(&copySrc, &copyDst, &copySize);
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<dawn_native::vulkan::Device*>(
backendAdapter->CreateDevice(&deviceDescriptor));
wgpu::Device thirdDevice =
wgpu::Device::Acquire(reinterpret_cast<WGPUDevice>(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<wgpu::Texture> 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<float>(row) / height;
float normCol = static_cast<float>(col) / width;
float dist = sqrt(normRow * normRow + normCol * normCol) * 3;
dist = dist - static_cast<int>(dist);
data[4 * (row * width + col)] = static_cast<unsigned char>(dist * 255);
data[4 * (row * width + col) + 1] = static_cast<unsigned char>(dist * 255);
data[4 * (row * width + col) + 2] = static_cast<unsigned char>(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(&copySrc, &copyDst, &copySize);
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(&copyDesc);
{
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(&copySrc, &copyDst, &copySize);
wgpu::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
}
// Check the image is not corrupted on |device|
EXPECT_BUFFER_U32_RANGE_EQ(reinterpret_cast<uint32_t*>(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