blob: 0983787c561fe80b6fc1fb85270a9f3e0ff0c5e8 [file] [log] [blame]
// Copyright 2016 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 "vkreadback.h"
#include <array>
#include "src/graphics/tests/common/utils.h"
#include "vulkan/vulkan.h"
#include "vulkan/vulkan_core.h"
#include "vulkan/vulkan.hpp"
namespace {
constexpr size_t kPageSize = 4096;
} // namespace
#ifdef __Fuchsia__
#include <zircon/syscalls.h>
#endif
// Note, alignment must be a power of 2
template <class T>
static inline T round_up(T val, uint32_t alignment) {
return ((val - 1) | (alignment - 1)) + 1;
}
VkReadbackTest::VkReadbackTest(Extension ext)
: ext_(ext),
import_export_((ext == VK_FUCHSIA_EXTERNAL_MEMORY) ? EXPORT_EXTERNAL_MEMORY : SELF),
command_buffers_(kNumCommandBuffers) {}
VkReadbackTest::VkReadbackTest(uint32_t exported_memory_handle)
: ext_(VK_FUCHSIA_EXTERNAL_MEMORY),
exported_memory_handle_(exported_memory_handle),
import_export_(IMPORT_EXTERNAL_MEMORY),
command_buffers_(kNumCommandBuffers) {}
VkReadbackTest::~VkReadbackTest() {
if (image_initialized_) {
if (VK_NULL_HANDLE != device_memory_) {
ctx_->device()->freeMemory(device_memory_, nullptr /* allocator */);
}
if (VK_NULL_HANDLE != imported_device_memory_) {
ctx_->device()->freeMemory(imported_device_memory_, nullptr /* allocator */);
}
}
}
bool VkReadbackTest::Initialize() {
if (is_initialized_) {
return false;
}
if (!InitVulkan()) {
RTN_MSG(false, "Failed to initialize Vulkan\n");
}
if (!InitImage()) {
RTN_MSG(false, "InitImage failed\n");
}
if (!InitCommandBuffers()) {
RTN_MSG(false, "InitCommandBuffers failed\n");
}
is_initialized_ = command_buffers_initialized_ && image_initialized_ && vulkan_initialized_;
return true;
}
#ifdef __Fuchsia__
void VkReadbackTest::VerifyExpectedImageFormats() const {
const auto& instance = ctx_->instance();
auto [rv_physical_devices, physical_devices] = instance->enumeratePhysicalDevices();
if (vk::Result::eSuccess != rv_physical_devices || physical_devices.empty()) {
RTN_MSG(/* void */, "No physical device found: 0x%0x", rv_physical_devices);
}
for (const auto& phys_device : physical_devices) {
vk::PhysicalDeviceProperties properties;
phys_device.getProperties(&properties);
if (VK_VERSION_MAJOR(properties.apiVersion) == 1 &&
VK_VERSION_MINOR(properties.apiVersion) == 0) {
printf("Skipping phys device that doesn't support Vulkan 1.1.\n");
continue;
}
// Test external buffer/image capabilities
vk::PhysicalDeviceExternalBufferInfo buffer_info;
buffer_info.usage = vk::BufferUsageFlagBits::eStorageBuffer;
buffer_info.handleType = vk::ExternalMemoryHandleTypeFlagBits::eTempZirconVmoFUCHSIA;
vk::ExternalBufferProperties buffer_props;
phys_device.getExternalBufferProperties(&buffer_info, &buffer_props);
EXPECT_EQ(buffer_props.externalMemoryProperties.externalMemoryFeatures,
vk::ExternalMemoryFeatureFlagBits::eExportable |
vk::ExternalMemoryFeatureFlagBits::eImportable);
EXPECT_EQ(buffer_props.externalMemoryProperties.exportFromImportedHandleTypes,
vk::ExternalMemoryHandleTypeFlagBits::eTempZirconVmoFUCHSIA);
EXPECT_EQ(buffer_props.externalMemoryProperties.compatibleHandleTypes,
vk::ExternalMemoryHandleTypeFlagBits::eTempZirconVmoFUCHSIA);
vk::PhysicalDeviceExternalImageFormatInfo ext_image_format_info;
ext_image_format_info.handleType = vk::ExternalMemoryHandleTypeFlagBits::eTempZirconVmoFUCHSIA;
vk::PhysicalDeviceImageFormatInfo2 image_format_info;
image_format_info.pNext = &ext_image_format_info;
image_format_info.format = vk::Format::eR8G8B8A8Unorm;
image_format_info.type = vk::ImageType::e2D;
image_format_info.tiling = vk::ImageTiling::eLinear;
image_format_info.usage = vk::ImageUsageFlagBits::eTransferDst;
vk::ExternalImageFormatProperties ext_format_props;
vk::ImageFormatProperties2 image_format_props2;
image_format_props2.pNext = &ext_format_props;
auto rv_image_format_props =
phys_device.getImageFormatProperties2(&image_format_info, &image_format_props2);
EXPECT_EQ(rv_image_format_props, vk::Result::eSuccess);
EXPECT_EQ(ext_format_props.externalMemoryProperties.externalMemoryFeatures,
vk::ExternalMemoryFeatureFlagBits::eExportable |
vk::ExternalMemoryFeatureFlagBits::eImportable);
EXPECT_EQ(ext_format_props.externalMemoryProperties.exportFromImportedHandleTypes,
vk::ExternalMemoryHandleTypeFlagBits::eTempZirconVmoFUCHSIA);
EXPECT_EQ(ext_format_props.externalMemoryProperties.compatibleHandleTypes,
vk::ExternalMemoryHandleTypeFlagBits::eTempZirconVmoFUCHSIA);
}
}
#endif // __Fuchsia__
bool VkReadbackTest::InitVulkan() {
if (vulkan_initialized_) {
RTN_MSG(false, "InitVulkan failed. Already initialized.\n")
}
std::vector<const char*> enabled_extension_names;
#ifdef __Fuchsia__
if (import_export_ == IMPORT_EXTERNAL_MEMORY || import_export_ == EXPORT_EXTERNAL_MEMORY) {
enabled_extension_names.push_back(VK_FUCHSIA_EXTERNAL_MEMORY_EXTENSION_NAME);
}
#endif
vk::ApplicationInfo app_info;
app_info.pApplicationName = "vkreadback";
app_info.apiVersion = VK_API_VERSION_1_1;
vk::InstanceCreateInfo instance_info;
instance_info.pApplicationInfo = &app_info;
// Copy the builder's default device info, which has its queue info
// properly configured and modify the desired extension fields only.
// Send the amended |device_info| back into the builder's
// set_device_info() during unique context construction.
VulkanContext::Builder builder;
vk::DeviceCreateInfo device_info = builder.DeviceInfo();
device_info.enabledExtensionCount = enabled_extension_names.size();
device_info.ppEnabledExtensionNames = enabled_extension_names.data();
ctx_ = builder.set_instance_info(instance_info).set_device_info(device_info).Unique();
#ifdef __Fuchsia__
// Initialize Fuchsia external memory procs.
if (import_export_ != SELF) {
vkGetMemoryZirconHandleFUCHSIA_ = reinterpret_cast<PFN_vkGetMemoryZirconHandleFUCHSIA>(
ctx_->instance()->getProcAddr("vkGetMemoryZirconHandleFUCHSIA"));
if (!vkGetMemoryZirconHandleFUCHSIA_) {
RTN_MSG(false, "Couldn't find vkGetMemoryZirconHandleFUCHSIA\n");
}
vkGetMemoryZirconHandlePropertiesFUCHSIA_ =
reinterpret_cast<PFN_vkGetMemoryZirconHandlePropertiesFUCHSIA>(
ctx_->instance()->getProcAddr("vkGetMemoryZirconHandlePropertiesFUCHSIA"));
if (!vkGetMemoryZirconHandlePropertiesFUCHSIA_) {
RTN_MSG(false, "Couldn't find vkGetMemoryZirconHandlePropertiesFUCHSIA\n");
}
VerifyExpectedImageFormats();
}
#endif
vulkan_initialized_ = true;
return true;
}
bool VkReadbackTest::InitImage() {
if (image_initialized_) {
RTN_MSG(false, "Image already initialized.\n");
}
vk::ImageCreateInfo image_create_info;
image_create_info.flags = vk::ImageCreateFlagBits::eMutableFormat;
image_create_info.imageType = vk::ImageType::e2D;
image_create_info.format = vk::Format::eR8G8B8A8Unorm;
image_create_info.extent = vk::Extent3D(kWidth, kHeight, 1);
image_create_info.mipLevels = 1;
image_create_info.arrayLayers = 1;
image_create_info.samples = vk::SampleCountFlagBits::e1;
image_create_info.tiling = vk::ImageTiling::eLinear;
image_create_info.usage = vk::ImageUsageFlagBits::eTransferDst;
image_create_info.sharingMode = vk::SharingMode::eExclusive;
image_create_info.queueFamilyIndexCount = 0;
image_create_info.initialLayout = vk::ImageLayout::ePreinitialized;
#ifdef __Fuchsia__
vk::ExternalMemoryImageCreateInfo external_memory_create_info;
if (import_export_ != SELF) {
external_memory_create_info.handleTypes =
vk::ExternalMemoryHandleTypeFlagBits::eTempZirconVmoFUCHSIA;
image_create_info.pNext = &external_memory_create_info;
}
#endif
vk::PhysicalDeviceImageFormatInfo2 image_format_info;
image_format_info.format = vk::Format::eR8G8B8A8Unorm;
image_format_info.type = vk::ImageType::e2D;
image_format_info.tiling = vk::ImageTiling::eLinear;
image_format_info.usage = vk::ImageUsageFlagBits::eTransferDst;
const auto& phys_device = ctx_->physical_device();
vk::ImageFormatProperties2 image_format_properties2;
auto rv_get_image_props =
phys_device.getImageFormatProperties2(&image_format_info, &image_format_properties2);
RTN_IF_VKH_ERR(false, rv_get_image_props, "vk::PhysicalDevice::getImageFormatProperties2()\n");
const auto& device = ctx_->device();
auto [rv_image, image] = device->createImageUnique(image_create_info);
RTN_IF_VKH_ERR(false, rv_image, "vk::Device::createImageUnique()\n");
image_ = std::move(image);
vk::MemoryRequirements memory_reqs;
device->getImageMemoryRequirements(image_.get(), &memory_reqs);
// Add an offset to all operations that's correctly aligned and at least a
// page in size, to ensure rounding the VMO down to a page offset will
// cause it to point to a separate page.
constexpr uint32_t kOffset = 128;
bind_offset_ = kPageSize + kOffset;
if (memory_reqs.alignment) {
bind_offset_ = round_up(bind_offset_, memory_reqs.alignment);
}
vk::PhysicalDeviceMemoryProperties memory_props;
ctx_->physical_device().getMemoryProperties(&memory_props);
uint32_t memory_type = 0;
for (; memory_type < VK_MAX_MEMORY_TYPES; memory_type++) {
if ((memory_reqs.memoryTypeBits & (1 << memory_type)) &&
(memory_props.memoryTypes[memory_type].propertyFlags &
vk::MemoryPropertyFlagBits::eHostVisible)) {
break;
}
}
if (memory_type >= VK_MAX_MEMORY_TYPES) {
RTN_MSG(false, "Can't find host mappable memory type for image.\n");
}
vk::ExportMemoryAllocateInfoKHR export_info;
export_info.handleTypes = vk::ExternalMemoryHandleTypeFlagBits::eTempZirconVmoFUCHSIA;
vk::MemoryAllocateInfo mem_alloc_info;
mem_alloc_info.pNext = (import_export_ == IMPORT_EXTERNAL_MEMORY) ? &export_info : nullptr;
mem_alloc_info.allocationSize = memory_reqs.size + bind_offset_;
mem_alloc_info.memoryTypeIndex = memory_type;
auto rv_device_memory =
device->allocateMemory(&mem_alloc_info, nullptr /* allocator */, &device_memory_);
RTN_IF_VKH_ERR(false, rv_device_memory, "vk::Device::allocateMemory()\n");
#ifdef __Fuchsia__
if (import_export_ == IMPORT_EXTERNAL_MEMORY) {
if (!AllocateFuchsiaImportedMemory(exported_memory_handle_)) {
RTN_MSG(false, "AllocateFuchsiaImportedMemory failed.\n");
}
} else if (import_export_ == EXPORT_EXTERNAL_MEMORY) {
if (!AssignExportedMemoryHandle()) {
RTN_MSG(false, "AssignExportedMemoryHandle failed.\n");
}
}
#endif // __Fuchsia__
void* addr;
auto rv_map_mem =
device->mapMemory(device_memory_, 0 /* offset */, VK_WHOLE_SIZE, vk::MemoryMapFlags{}, &addr);
RTN_IF_VKH_ERR(false, rv_map_mem, "vk::Device::mapMemory()\n");
constexpr int kFill = 0xab;
memset(addr, kFill, memory_reqs.size + bind_offset_);
device->unmapMemory(device_memory_);
auto rv_bind = device->bindImageMemory(image_.get(), device_memory_, bind_offset_);
RTN_IF_VKH_ERR(false, rv_bind, "vk::Device::bindImageMemory()\n");
image_initialized_ = true;
return true;
}
#ifdef __Fuchsia__
bool VkReadbackTest::AllocateFuchsiaImportedMemory(uint32_t exported_memory_handle) {
const auto& device = ctx_->device();
if (exported_memory_handle == 0u) {
RTN_MSG(false, "|exported_memory_handle| must be initialized.\n");
}
size_t vmo_size;
zx_vmo_get_size(exported_memory_handle, &vmo_size);
VkMemoryZirconHandlePropertiesFUCHSIA zircon_handle_props{
.sType = VK_STRUCTURE_TYPE_TEMP_MEMORY_ZIRCON_HANDLE_PROPERTIES_FUCHSIA,
.pNext = nullptr,
};
VkResult result = vkGetMemoryZirconHandlePropertiesFUCHSIA_(
*device, VK_EXTERNAL_MEMORY_HANDLE_TYPE_TEMP_ZIRCON_VMO_BIT_FUCHSIA, exported_memory_handle,
&zircon_handle_props);
RTN_IF_VK_ERR(false, result, "vkGetMemoryZirconHandlePropertiesFUCHSIA failed.\n");
// Find index of lowest set bit.
uint32_t memory_type = __builtin_ctz(zircon_handle_props.memoryTypeBits);
vk::ImportMemoryZirconHandleInfoFUCHSIA import_memory_handle_info;
import_memory_handle_info.pNext = nullptr;
import_memory_handle_info.handleType =
vk::ExternalMemoryHandleTypeFlagBits::eTempZirconVmoFUCHSIA;
import_memory_handle_info.handle = exported_memory_handle;
vk::MemoryAllocateInfo imported_mem_alloc_info;
imported_mem_alloc_info.pNext = &import_memory_handle_info;
imported_mem_alloc_info.allocationSize = vmo_size;
imported_mem_alloc_info.memoryTypeIndex = memory_type;
auto rv_imported_device_memory = device->allocateMemory(
&imported_mem_alloc_info, nullptr /* allocator */, &imported_device_memory_);
RTN_IF_VKH_ERR(false, rv_imported_device_memory,
"vk::Device::allocateMemory() failed for import memory\n");
return true;
}
bool VkReadbackTest::AssignExportedMemoryHandle() {
const auto& device = ctx_->device();
VkMemoryGetZirconHandleInfoFUCHSIA get_handle_info = {
.sType = VK_STRUCTURE_TYPE_TEMP_MEMORY_GET_ZIRCON_HANDLE_INFO_FUCHSIA,
.pNext = nullptr,
.memory = device_memory_,
.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_TEMP_ZIRCON_VMO_BIT_FUCHSIA};
VkResult result =
vkGetMemoryZirconHandleFUCHSIA_(*device, &get_handle_info, &exported_memory_handle_);
RTN_IF_VK_ERR(false, result, "vkGetMemoryZirconHandleFUCHSIA.\n");
VkMemoryZirconHandlePropertiesFUCHSIA zircon_handle_props{
.sType = VK_STRUCTURE_TYPE_TEMP_MEMORY_ZIRCON_HANDLE_PROPERTIES_FUCHSIA,
.pNext = nullptr,
};
result = vkGetMemoryZirconHandlePropertiesFUCHSIA_(
*device, VK_EXTERNAL_MEMORY_HANDLE_TYPE_TEMP_ZIRCON_VMO_BIT_FUCHSIA, exported_memory_handle_,
&zircon_handle_props);
RTN_IF_VK_ERR(false, result, "vkGetMemoryZirconHandlePropertiesFUCHSIA\n");
return true;
}
#endif // __Fuchsia__
bool VkReadbackTest::FillCommandBuffer(vk::CommandBuffer& command_buffer, bool transition_image) {
auto rv_begin = command_buffer.begin(vk::CommandBufferBeginInfo{});
RTN_IF_VKH_ERR(false, rv_begin, "vk::CommandBuffer::begin()\n");
if (transition_image) {
// Transition image for clear operation.
vk::ImageMemoryBarrier image_barrier;
image_barrier.image = image_.get();
image_barrier.oldLayout = vk::ImageLayout::ePreinitialized;
image_barrier.newLayout = vk::ImageLayout::eGeneral;
image_barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
image_barrier.subresourceRange.levelCount = 1;
image_barrier.subresourceRange.layerCount = 1;
command_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe, /* srcStageMask */
vk::PipelineStageFlagBits::eTransfer, /* dstStageMask */
vk::DependencyFlags{}, 0 /* memoryBarrierCount */,
nullptr /* pMemoryBarriers */, 0 /* bufferMemoryBarrierCount */,
nullptr /* pBufferMemoryBarriers */,
1 /* imageMemoryBarrierCount */, &image_barrier);
}
// RGBA
vk::ClearColorValue clear_color(std::array<float, 4>{1.0f, 0.0f, 0.5f, 0.75f});
vk::ImageSubresourceRange image_subres_range;
image_subres_range.aspectMask = vk::ImageAspectFlagBits::eColor;
image_subres_range.baseMipLevel = 0;
image_subres_range.levelCount = 1;
image_subres_range.baseArrayLayer = 0;
image_subres_range.layerCount = 1;
command_buffer.clearColorImage(image_.get(), vk::ImageLayout::eGeneral, &clear_color,
1 /* rangeCount */, &image_subres_range);
auto rv_command_buf_end = command_buffer.end();
RTN_IF_VKH_ERR(false, rv_command_buf_end, "vk::UniqueCommandBuffer::end()\n");
return true;
}
bool VkReadbackTest::InitCommandBuffers() {
if (command_buffers_initialized_) {
RTN_MSG(false, "ERROR: Command buffers are already initialized.\n");
}
const auto& device = ctx_->device();
vk::CommandPoolCreateInfo command_pool_create_info;
command_pool_create_info.queueFamilyIndex = ctx_->queue_family_index();
auto [rv_command_pool, command_pool] = device->createCommandPoolUnique(command_pool_create_info);
RTN_IF_VKH_ERR(false, rv_command_pool, "vk::Device::createCommandPoolUnique()\n");
command_pool_ = std::move(command_pool);
vk::CommandBufferAllocateInfo command_buffer_alloc_info;
command_buffer_alloc_info.commandPool = command_pool_.get();
command_buffer_alloc_info.level = vk::CommandBufferLevel::ePrimary;
command_buffer_alloc_info.commandBufferCount = kNumCommandBuffers;
auto [rv_alloc_cmd_bufs, command_buffers] =
device->allocateCommandBuffersUnique(command_buffer_alloc_info);
RTN_IF_VKH_ERR(false, rv_alloc_cmd_bufs, "vk::Device::allocateCommandBuffersUnique()\n");
command_buffers_ = std::move(command_buffers);
if (!FillCommandBuffer(command_buffers_[0].get(), true /* transition_image */))
return false;
if (!FillCommandBuffer(command_buffers_[1].get(), false /* transition_image */))
return false;
command_buffers_initialized_ = true;
return true;
}
bool VkReadbackTest::Exec(vk::Fence fence) {
if (!Submit(fence, true /* transition_image */)) {
return false;
}
return Wait();
}
bool VkReadbackTest::Submit(vk::Fence fence, bool transition_image) {
vk::SubmitInfo submit_info;
submit_info.commandBufferCount = 1;
const vk::CommandBuffer& command_buffer =
(transition_image ? command_buffers_[0].get() : command_buffers_[1].get());
submit_info.pCommandBuffers = &command_buffer;
auto rv_submit = ctx_->queue().submit(1, &submit_info, fence);
RTN_IF_VKH_ERR(false, rv_submit, "vk::Queue::submit()\n");
return true;
}
bool VkReadbackTest::Wait() {
auto rv_idle = ctx_->queue().waitIdle();
RTN_IF_VKH_ERR(false, rv_idle, "vk::Queue::waitIdle()\n");
return true;
}
bool VkReadbackTest::Readback() {
void* addr;
const vk::DeviceMemory& device_memory =
ext_ == VkReadbackTest::NONE ? device_memory_ : imported_device_memory_;
auto rv_map = ctx_->device()->mapMemory(device_memory, vk::DeviceSize{} /* offset */,
VK_WHOLE_SIZE, vk::MemoryMapFlags{}, &addr);
RTN_IF_VKH_ERR(false, rv_map, "vk::Device::mapMemory()\n");
auto* data = reinterpret_cast<uint32_t*>(static_cast<uint8_t*>(addr) + bind_offset_);
// ABGR ordering of clear color value.
const uint32_t kExpectedClearColorValue = 0xBF8000FF;
uint32_t mismatches = 0;
for (uint32_t i = 0; i < kWidth * kHeight; i++) {
if (data[i] != kExpectedClearColorValue) {
constexpr int kMaxMismatches = 10;
if (mismatches++ < kMaxMismatches) {
fprintf(stderr, "Clear Color Value Mismatch at index %d - expected 0x%08x, got 0x%08x\n", i,
kExpectedClearColorValue, data[i]);
}
}
}
if (mismatches) {
fprintf(stdout, "****** Test Failed! %d mismatches\n", mismatches);
}
ctx_->device()->unmapMemory(device_memory);
return mismatches == 0;
}