[graphics][tests] Add Linux test for Vulkan with GBM
Change-Id: Iadfa788438380818ee079f903c75dda78147b83a
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/514961
Commit-Queue: Craig Stout <cstout@google.com>
Reviewed-by: John Bauman <jbauman@google.com>
Reviewed-by: John Rosasco <rosasco@google.com>
diff --git a/src/graphics/tests/vkgbm/BUILD.gn b/src/graphics/tests/vkgbm/BUILD.gn
new file mode 100644
index 0000000..1b26041
--- /dev/null
+++ b/src/graphics/tests/vkgbm/BUILD.gn
@@ -0,0 +1,16 @@
+# Copyright 2021 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+executable("vkgbm") {
+ testonly = true
+
+ sources = [ "test_vkgbm.cc" ]
+
+ deps = [
+ "//src/graphics/tests/common",
+ "//src/lib/fxl/test:gtest_main",
+ "//third_party/googletest:gtest",
+ "//third_party/minigbm",
+ ]
+}
diff --git a/src/graphics/tests/vkgbm/test_vkgbm.cc b/src/graphics/tests/vkgbm/test_vkgbm.cc
new file mode 100644
index 0000000..80b897f
--- /dev/null
+++ b/src/graphics/tests/vkgbm/test_vkgbm.cc
@@ -0,0 +1,356 @@
+// Copyright 2021 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <fcntl.h>
+#include <gbm.h>
+
+#include <gtest/gtest.h>
+
+#include "src/graphics/tests/common/vulkan_context.h"
+
+#include <vulkan/vulkan.hpp>
+
+class VkGbm : public testing::Test {
+ public:
+ void SetUp() override {
+ fd_ = open("/dev/magma0", O_RDWR | O_CLOEXEC);
+ ASSERT_GE(fd_, 0);
+
+ device_ = gbm_create_device(fd_);
+ ASSERT_TRUE(device_);
+
+ const char *app_name = "vkgbm";
+ vk::ApplicationInfo app_info;
+ app_info.pApplicationName = app_name;
+ vk::InstanceCreateInfo instance_info;
+ instance_info.pApplicationInfo = &app_info;
+
+ context_ = VulkanContext::Builder{}
+ .set_instance_info(instance_info)
+ .set_validation_layers_enabled(false)
+ .Unique();
+ ASSERT_TRUE(context_);
+ }
+
+ void TearDown() override {
+ gbm_device_destroy(device_);
+ device_ = nullptr;
+
+ close(fd_);
+ fd_ = -1;
+ }
+
+ VulkanContext *context() { return context_.get(); }
+
+ struct gbm_device *device() {
+ return device_;
+ }
+
+ void IsMemoryTypeCoherent(uint32_t memoryTypeIndex, bool *is_coherent_out);
+ void WriteLinearImage(vk::DeviceMemory memory, bool is_coherent, uint32_t row_bytes,
+ uint32_t height, uint32_t fill);
+ void CheckLinearImage(vk::DeviceMemory memory, bool is_coherent, uint32_t row_bytes,
+ uint32_t height, uint32_t fill);
+
+ private:
+ int fd_ = -1;
+ struct gbm_device *device_ = nullptr;
+ std::unique_ptr<VulkanContext> context_;
+};
+
+void VkGbm::IsMemoryTypeCoherent(uint32_t memoryTypeIndex, bool *is_coherent_out) {
+ vk::PhysicalDeviceMemoryProperties props = context_->physical_device().getMemoryProperties();
+ ASSERT_LT(memoryTypeIndex, props.memoryTypeCount);
+ *is_coherent_out = static_cast<bool>(props.memoryTypes[memoryTypeIndex].propertyFlags &
+ vk::MemoryPropertyFlagBits::eHostCoherent);
+}
+
+void VkGbm::WriteLinearImage(vk::DeviceMemory memory, bool is_coherent, uint32_t row_bytes,
+ uint32_t height, uint32_t fill) {
+ void *addr;
+ vk::Result result = context_->device()->mapMemory(memory, 0 /* offset */, VK_WHOLE_SIZE,
+ vk::MemoryMapFlags{}, &addr);
+ ASSERT_EQ(vk::Result::eSuccess, result);
+
+ for (uint32_t y = 0; y < height; y++) {
+ auto row_addr = reinterpret_cast<uint8_t *>(addr) + y * row_bytes;
+ for (uint32_t x = 0; x < row_bytes; x += sizeof(uint32_t)) {
+ *reinterpret_cast<uint32_t *>(row_addr + x) = fill;
+ }
+ }
+
+ if (!is_coherent) {
+ auto range = vk::MappedMemoryRange().setMemory(memory).setSize(VK_WHOLE_SIZE);
+ context_->device()->flushMappedMemoryRanges(1, &range);
+ }
+
+ context_->device()->unmapMemory(memory);
+}
+
+void VkGbm::CheckLinearImage(vk::DeviceMemory memory, bool is_coherent, uint32_t row_bytes,
+ uint32_t height, uint32_t fill) {
+ void *addr;
+ vk::Result result = context_->device()->mapMemory(memory, 0 /* offset */, VK_WHOLE_SIZE,
+ vk::MemoryMapFlags{}, &addr);
+ ASSERT_EQ(vk::Result::eSuccess, result);
+
+ if (!is_coherent) {
+ auto range = vk::MappedMemoryRange().setMemory(memory).setSize(VK_WHOLE_SIZE);
+ context_->device()->invalidateMappedMemoryRanges(1, &range);
+ }
+
+ for (uint32_t y = 0; y < height; y++) {
+ auto row_addr = reinterpret_cast<uint8_t *>(addr) + y * row_bytes;
+ for (uint32_t x = 0; x < row_bytes; x += sizeof(uint32_t)) {
+ EXPECT_EQ(fill, *reinterpret_cast<uint32_t *>(row_addr + x))
+ << "offset " << y * row_bytes + x;
+ }
+ }
+
+ context_->device()->unmapMemory(memory);
+}
+
+constexpr uint32_t kDefaultWidth = 1920;
+constexpr uint32_t kDefaultHeight = 1080;
+constexpr uint32_t kDefaultGbmFormat = GBM_FORMAT_ARGB8888;
+constexpr vk::Format kDefaultVkFormat = vk::Format::eB8G8R8A8Unorm;
+constexpr uint32_t kPattern = 0xaabbccdd;
+
+using UniqueGbmBo = std::unique_ptr<struct gbm_bo, decltype(&gbm_bo_destroy)>;
+
+TEST_F(VkGbm, ImageCopy) {
+ auto src_bo = UniqueGbmBo(
+ gbm_bo_create(device(), kDefaultWidth, kDefaultHeight, kDefaultGbmFormat, GBM_BO_USE_LINEAR),
+ gbm_bo_destroy);
+ ASSERT_TRUE(src_bo);
+
+ auto dst_bo =
+ UniqueGbmBo(gbm_bo_create(device(), kDefaultWidth, kDefaultHeight, kDefaultGbmFormat,
+ GBM_BO_USE_LINEAR | GBM_BO_USE_RENDERING),
+ gbm_bo_destroy);
+ ASSERT_TRUE(dst_bo);
+
+ vk::UniqueImage src_image;
+ vk::UniqueDeviceMemory src_memory;
+ uint32_t src_row_bytes;
+
+ {
+ auto create_info = vk::ImageCreateInfo()
+ .setImageType(vk::ImageType::e2D)
+ .setFormat(kDefaultVkFormat)
+ .setExtent(vk::Extent3D(kDefaultWidth, kDefaultHeight, 1))
+ .setMipLevels(1)
+ .setArrayLayers(1)
+ .setSamples(vk::SampleCountFlagBits::e1)
+ .setTiling(vk::ImageTiling::eLinear)
+ .setUsage(vk::ImageUsageFlagBits::eTransferSrc)
+ .setSharingMode(vk::SharingMode::eExclusive)
+ .setInitialLayout(vk::ImageLayout::ePreinitialized);
+
+ auto result = context()->device()->createImageUnique(create_info);
+ ASSERT_EQ(vk::Result::eSuccess, result.result);
+ src_image = std::move(result.value);
+
+ auto subresource = vk::ImageSubresource().setAspectMask(vk::ImageAspectFlagBits::eColor);
+ auto layout = context()->device()->getImageSubresourceLayout(src_image.get(), subresource);
+ ASSERT_EQ(layout.offset, 0u);
+ src_row_bytes = layout.rowPitch;
+ EXPECT_GE(src_row_bytes, kDefaultWidth * sizeof(uint32_t));
+ }
+
+ {
+ vk::MemoryRequirements mem_reqs =
+ context()->device()->getImageMemoryRequirements(src_image.get());
+ uint32_t memory_type_index = __builtin_ctz(mem_reqs.memoryTypeBits);
+
+ int fd = gbm_bo_get_fd(src_bo.get());
+ EXPECT_GE(fd, 0);
+
+ auto import_info = vk::ImportMemoryFdInfoKHR().setFd(fd).setHandleType(
+ vk::ExternalMemoryHandleTypeFlagBitsKHR::eOpaqueFd);
+
+ auto alloc_info = vk::MemoryAllocateInfo()
+ .setAllocationSize(mem_reqs.size)
+ .setMemoryTypeIndex(memory_type_index)
+ .setPNext(&import_info);
+
+ auto result = context()->device()->allocateMemoryUnique(alloc_info);
+ ASSERT_EQ(result.result, vk::Result::eSuccess);
+ src_memory = std::move(result.value);
+
+ ASSERT_EQ(vk::Result::eSuccess,
+ context()->device()->bindImageMemory(src_image.get(), src_memory.get(), 0u));
+
+ bool is_coherent;
+ IsMemoryTypeCoherent(memory_type_index, &is_coherent);
+
+ WriteLinearImage(src_memory.get(), is_coherent, src_row_bytes, kDefaultHeight, kPattern);
+ }
+
+ vk::UniqueImage dst_image;
+ vk::UniqueDeviceMemory dst_memory;
+ bool dst_is_coherent;
+ uint32_t dst_row_bytes;
+
+ {
+ auto create_info = vk::ImageCreateInfo()
+ .setImageType(vk::ImageType::e2D)
+ .setFormat(kDefaultVkFormat)
+ .setExtent(vk::Extent3D(kDefaultWidth, kDefaultHeight, 1))
+ .setMipLevels(1)
+ .setArrayLayers(1)
+ .setSamples(vk::SampleCountFlagBits::e1)
+ .setTiling(vk::ImageTiling::eLinear)
+ .setUsage(vk::ImageUsageFlagBits::eTransferDst)
+ .setSharingMode(vk::SharingMode::eExclusive)
+ .setInitialLayout(vk::ImageLayout::eUndefined);
+
+ auto result = context()->device()->createImageUnique(create_info);
+ ASSERT_EQ(vk::Result::eSuccess, result.result);
+ dst_image = std::move(result.value);
+
+ auto subresource = vk::ImageSubresource().setAspectMask(vk::ImageAspectFlagBits::eColor);
+ auto layout = context()->device()->getImageSubresourceLayout(dst_image.get(), subresource);
+ ASSERT_EQ(layout.offset, 0u);
+ dst_row_bytes = layout.rowPitch;
+ EXPECT_GE(dst_row_bytes, kDefaultWidth * sizeof(uint32_t));
+ }
+
+ {
+ vk::MemoryRequirements mem_reqs =
+ context()->device()->getImageMemoryRequirements(dst_image.get());
+ uint32_t memory_type_index = __builtin_ctz(mem_reqs.memoryTypeBits);
+
+ int fd = gbm_bo_get_fd(dst_bo.get());
+ EXPECT_GE(fd, 0);
+
+ vk::StructureChain<vk::MemoryAllocateInfo, vk::ImportMemoryFdInfoKHR> alloc_info(
+ vk::MemoryAllocateInfo()
+ .setAllocationSize(mem_reqs.size)
+ .setMemoryTypeIndex(memory_type_index),
+ vk::ImportMemoryFdInfoKHR().setFd(fd).setHandleType(
+ vk::ExternalMemoryHandleTypeFlagBitsKHR::eOpaqueFd));
+
+ auto result =
+ context()->device()->allocateMemoryUnique(alloc_info.get<vk::MemoryAllocateInfo>());
+ ASSERT_EQ(result.result, vk::Result::eSuccess);
+ dst_memory = std::move(result.value);
+
+ ASSERT_EQ(vk::Result::eSuccess,
+ context()->device()->bindImageMemory(dst_image.get(), dst_memory.get(), 0u));
+
+ IsMemoryTypeCoherent(memory_type_index, &dst_is_coherent);
+
+ WriteLinearImage(dst_memory.get(), dst_is_coherent, dst_row_bytes, kDefaultHeight, 0xffffffff);
+ }
+
+ vk::UniqueCommandPool command_pool;
+ {
+ auto info = vk::CommandPoolCreateInfo().setQueueFamilyIndex(context()->queue_family_index());
+ auto result = context()->device()->createCommandPoolUnique(info);
+ ASSERT_EQ(vk::Result::eSuccess, result.result);
+ command_pool = std::move(result.value);
+ }
+
+ std::vector<vk::UniqueCommandBuffer> command_buffers;
+ {
+ auto info = vk::CommandBufferAllocateInfo()
+ .setCommandPool(command_pool.get())
+ .setLevel(vk::CommandBufferLevel::ePrimary)
+ .setCommandBufferCount(1);
+ auto result = context()->device()->allocateCommandBuffersUnique(info);
+ ASSERT_EQ(vk::Result::eSuccess, result.result);
+ command_buffers = std::move(result.value);
+ }
+
+ {
+ auto info = vk::CommandBufferBeginInfo();
+ command_buffers[0]->begin(&info);
+ }
+
+ {
+ auto range = vk::ImageSubresourceRange()
+ .setAspectMask(vk::ImageAspectFlagBits::eColor)
+ .setLevelCount(1)
+ .setLayerCount(1);
+ auto barrier = vk::ImageMemoryBarrier()
+ .setImage(src_image.get())
+ .setSrcAccessMask(vk::AccessFlagBits::eHostWrite)
+ .setDstAccessMask(vk::AccessFlagBits::eTransferRead)
+ .setOldLayout(vk::ImageLayout::ePreinitialized)
+ .setNewLayout(vk::ImageLayout::eTransferSrcOptimal)
+ .setSubresourceRange(range);
+ command_buffers[0]->pipelineBarrier(
+ vk::PipelineStageFlagBits::eHost, /* srcStageMask */
+ vk::PipelineStageFlagBits::eTransfer, /* dstStageMask */
+ vk::DependencyFlags{}, 0 /* memoryBarrierCount */, nullptr /* pMemoryBarriers */,
+ 0 /* bufferMemoryBarrierCount */, nullptr /* pBufferMemoryBarriers */,
+ 1 /* imageMemoryBarrierCount */, &barrier);
+ }
+ {
+ auto range = vk::ImageSubresourceRange()
+ .setAspectMask(vk::ImageAspectFlagBits::eColor)
+ .setLevelCount(1)
+ .setLayerCount(1);
+ auto barrier = vk::ImageMemoryBarrier()
+ .setImage(dst_image.get())
+ .setSrcAccessMask(vk::AccessFlagBits::eHostWrite)
+ .setDstAccessMask(vk::AccessFlagBits::eTransferWrite)
+ .setOldLayout(vk::ImageLayout::ePreinitialized)
+ .setNewLayout(vk::ImageLayout::eTransferDstOptimal)
+ .setSubresourceRange(range);
+ command_buffers[0]->pipelineBarrier(
+ vk::PipelineStageFlagBits::eHost, /* srcStageMask */
+ vk::PipelineStageFlagBits::eTransfer, /* dstStageMask */
+ vk::DependencyFlags{}, 0 /* memoryBarrierCount */, nullptr /* pMemoryBarriers */,
+ 0 /* bufferMemoryBarrierCount */, nullptr /* pBufferMemoryBarriers */,
+ 1 /* imageMemoryBarrierCount */, &barrier);
+ }
+
+ {
+ auto layer = vk::ImageSubresourceLayers()
+ .setAspectMask(vk::ImageAspectFlagBits::eColor)
+ .setLayerCount(1);
+ auto copy = vk::ImageCopy()
+ .setSrcSubresource(layer)
+ .setDstSubresource(layer)
+ .setSrcOffset({0, 0, 0})
+ .setDstOffset({0, 0, 0})
+ .setExtent({kDefaultWidth, kDefaultHeight, 1});
+ command_buffers[0]->copyImage(src_image.get(), vk::ImageLayout::eTransferSrcOptimal,
+ dst_image.get(), vk::ImageLayout::eTransferDstOptimal, 1, ©);
+ }
+
+ {
+ auto range = vk::ImageSubresourceRange()
+ .setAspectMask(vk::ImageAspectFlagBits::eColor)
+ .setLevelCount(1)
+ .setLayerCount(1);
+ auto barrier = vk::ImageMemoryBarrier()
+ .setImage(dst_image.get())
+ .setSrcAccessMask(vk::AccessFlagBits::eTransferWrite)
+ .setDstAccessMask(vk::AccessFlagBits::eHostRead)
+ .setOldLayout(vk::ImageLayout::eTransferDstOptimal)
+ .setNewLayout(vk::ImageLayout::eGeneral)
+ .setSubresourceRange(range);
+ command_buffers[0]->pipelineBarrier(
+ vk::PipelineStageFlagBits::eTransfer, /* srcStageMask */
+ vk::PipelineStageFlagBits::eHost, /* dstStageMask */
+ vk::DependencyFlags{}, 0 /* memoryBarrierCount */, nullptr /* pMemoryBarriers */,
+ 0 /* bufferMemoryBarrierCount */, nullptr /* pBufferMemoryBarriers */,
+ 1 /* imageMemoryBarrierCount */, &barrier);
+ }
+
+ command_buffers[0]->end();
+
+ {
+ auto command_buffer_temp = command_buffers[0].get();
+ auto info = vk::SubmitInfo().setCommandBufferCount(1).setPCommandBuffers(&command_buffer_temp);
+ context()->queue().submit(1, &info, vk::Fence());
+ }
+
+ context()->queue().waitIdle();
+
+ CheckLinearImage(dst_memory.get(), dst_is_coherent, dst_row_bytes, kDefaultHeight, kPattern);
+}