blob: b88a2185210808ab501565579cfec31154607f50 [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 "src/graphics/tests/common/utils.h"
namespace {
constexpr size_t kNumCommandBuffers = 1;
} // namespace
// 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() {
if (image_initialized_) {
vkFreeCommandBuffers(vk_device_, vk_command_pool_, kNumCommandBuffers, &vk_command_buffer_);
vkDestroyCommandPool(vk_device_, vk_command_pool_, nullptr /* pAllocator */);
if (VK_NULL_HANDLE != vk_device_memory_) {
vkFreeMemory(vk_device_, vk_device_memory_, nullptr /* allocator */);
}
if (VK_NULL_HANDLE != vk_imported_device_memory_) {
vkFreeMemory(vk_device_, vk_imported_device_memory_, nullptr /* allocator */);
}
vkDestroyImage(vk_device_, vk_image_, nullptr /* pAllocator */);
}
if (is_initialized_) {
vkDeviceWaitIdle(vk_device_);
vkDestroyDevice(vk_device_, nullptr /* pAllocator */);
vkDestroyInstance(vk_instance_, nullptr /* pAllocator */);
}
}
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");
}
is_initialized_ = true;
return true;
}
bool VkReadbackTest::InitVulkan() {
VkApplicationInfo app_info = {
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.pNext = nullptr,
.pApplicationName = "vkreadback",
.applicationVersion = 0,
.pEngineName = nullptr,
.engineVersion = 0,
.apiVersion = VK_API_VERSION_1_1,
};
VkInstanceCreateInfo create_info{
VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, // VkStructureType sType;
nullptr, // const void* pNext;
0, // VkInstanceCreateFlags flags;
&app_info, // const VkApplicationInfo* pApplicationInfo;
0, // uint32_t enabledLayerCount;
nullptr, // const char* const* ppEnabledLayerNames;
0, // instance extensions count
nullptr, // instance extensions,
};
VkAllocationCallbacks* allocation_callbacks = nullptr;
VkResult result;
if ((result = vkCreateInstance(&create_info, allocation_callbacks, &vk_instance_)) !=
VK_SUCCESS) {
RTN_MSG(false, "vkCreateInstance failed %d\n", result);
}
uint32_t physical_device_count;
if ((result = vkEnumeratePhysicalDevices(vk_instance_, &physical_device_count, nullptr)) !=
VK_SUCCESS) {
RTN_MSG(false, "vkEnumeratePhysicalDevices failed %d\n", result);
}
if (physical_device_count < 1) {
RTN_MSG(false, "Unexpected physical_device_count %d\n", physical_device_count);
}
std::vector<VkPhysicalDevice> physical_devices(physical_device_count);
if ((result = vkEnumeratePhysicalDevices(vk_instance_, &physical_device_count,
physical_devices.data())) != VK_SUCCESS) {
RTN_MSG(false, "vkEnumeratePhysicalDevices failed %d\n", result);
}
for (const auto& device : physical_devices) {
VkPhysicalDeviceProperties properties;
vkGetPhysicalDeviceProperties(device, &properties);
if (ext_ == NONE) {
continue;
}
if (VK_VERSION_MAJOR(properties.apiVersion) == 1 &&
VK_VERSION_MINOR(properties.apiVersion) == 0) {
printf("Skipping 1.1 checks");
continue;
}
// Test external buffer/image capabilities
VkPhysicalDeviceExternalBufferInfo buffer_info = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_BUFFER_INFO,
.flags = 0,
.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_TEMP_ZIRCON_VMO_BIT_FUCHSIA,
};
VkExternalBufferProperties buffer_props = {
.sType = VK_STRUCTURE_TYPE_EXTERNAL_BUFFER_PROPERTIES,
.pNext = nullptr,
};
vkGetPhysicalDeviceExternalBufferProperties(device, &buffer_info, &buffer_props);
EXPECT_EQ(
buffer_props.externalMemoryProperties.externalMemoryFeatures,
0u | VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT | VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT);
EXPECT_EQ(buffer_props.externalMemoryProperties.exportFromImportedHandleTypes,
VK_EXTERNAL_MEMORY_HANDLE_TYPE_TEMP_ZIRCON_VMO_BIT_FUCHSIA);
EXPECT_EQ(buffer_props.externalMemoryProperties.compatibleHandleTypes,
VK_EXTERNAL_MEMORY_HANDLE_TYPE_TEMP_ZIRCON_VMO_BIT_FUCHSIA);
VkPhysicalDeviceExternalImageFormatInfo ext_format_info = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO,
.pNext = nullptr,
.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_TEMP_ZIRCON_VMO_BIT_FUCHSIA,
};
VkPhysicalDeviceImageFormatInfo2 image_format_info = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2,
.pNext = &ext_format_info,
.format = VK_FORMAT_R8G8B8A8_UNORM,
.type = VK_IMAGE_TYPE_2D,
.tiling = VK_IMAGE_TILING_LINEAR,
.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
.flags = 0u,
};
VkExternalImageFormatProperties ext_format_props = {
.sType = VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES,
.pNext = nullptr,
};
VkImageFormatProperties2 image_format_props = {
.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2,
.pNext = &ext_format_props,
};
vkGetPhysicalDeviceImageFormatProperties2(device, &image_format_info, &image_format_props);
EXPECT_EQ(
ext_format_props.externalMemoryProperties.externalMemoryFeatures,
0u | VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT | VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT);
EXPECT_EQ(ext_format_props.externalMemoryProperties.exportFromImportedHandleTypes,
VK_EXTERNAL_MEMORY_HANDLE_TYPE_TEMP_ZIRCON_VMO_BIT_FUCHSIA);
EXPECT_EQ(ext_format_props.externalMemoryProperties.compatibleHandleTypes,
VK_EXTERNAL_MEMORY_HANDLE_TYPE_TEMP_ZIRCON_VMO_BIT_FUCHSIA);
}
uint32_t queue_family_count;
vkGetPhysicalDeviceQueueFamilyProperties(physical_devices[0], &queue_family_count, nullptr);
if (queue_family_count < 1) {
RTN_MSG(false, "Invalid queue_family_count %d\n", queue_family_count);
}
std::vector<VkQueueFamilyProperties> queue_family_properties(queue_family_count);
vkGetPhysicalDeviceQueueFamilyProperties(physical_devices[0], &queue_family_count,
queue_family_properties.data());
int32_t queue_family_index = -1;
for (uint32_t i = 0; i < queue_family_count; i++) {
if (queue_family_properties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
queue_family_index = i;
break;
}
}
if (queue_family_index < 0) {
RTN_MSG(false, "Couldn't find an appropriate queue\n");
}
constexpr float kQueuePriority = 0.0;
VkDeviceQueueCreateInfo queue_create_info = {.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.queueFamilyIndex = 0,
.queueCount = 1,
.pQueuePriorities = &kQueuePriority};
std::vector<const char*> enabled_extension_names;
switch (ext_) {
case VK_FUCHSIA_EXTERNAL_MEMORY:
enabled_extension_names.push_back(VK_FUCHSIA_EXTERNAL_MEMORY_EXTENSION_NAME);
break;
default:
break;
}
VkDeviceCreateInfo createInfo = {
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.queueCreateInfoCount = 1,
.pQueueCreateInfos = &queue_create_info,
.enabledLayerCount = 0,
.ppEnabledLayerNames = nullptr,
.enabledExtensionCount = static_cast<uint32_t>(enabled_extension_names.size()),
.ppEnabledExtensionNames = enabled_extension_names.data(),
.pEnabledFeatures = nullptr};
VkDevice vkdevice;
if ((result = vkCreateDevice(physical_devices[0], &createInfo, nullptr /* allocationcallbacks */,
&vkdevice)) != VK_SUCCESS) {
RTN_MSG(false, "vkCreateDevice failed: %d\n", result);
}
switch (ext_) {
case VK_FUCHSIA_EXTERNAL_MEMORY:
vkGetMemoryZirconHandleFUCHSIA_ = reinterpret_cast<PFN_vkGetMemoryZirconHandleFUCHSIA>(
vkGetInstanceProcAddr(vk_instance_, "vkGetMemoryZirconHandleFUCHSIA"));
if (!vkGetMemoryZirconHandleFUCHSIA_) {
RTN_MSG(false, "Couldn't find vkGetMemoryZirconHandleFUCHSIA\n");
}
vkGetMemoryZirconHandlePropertiesFUCHSIA_ =
reinterpret_cast<PFN_vkGetMemoryZirconHandlePropertiesFUCHSIA>(
vkGetInstanceProcAddr(vk_instance_, "vkGetMemoryZirconHandlePropertiesFUCHSIA"));
if (!vkGetMemoryZirconHandlePropertiesFUCHSIA_) {
RTN_MSG(false, "Couldn't find vkGetMemoryZirconHandlePropertiesFUCHSIA\n");
}
break;
default:
break;
}
vk_physical_device_ = physical_devices[0];
vk_device_ = vkdevice;
vkGetDeviceQueue(vkdevice, queue_family_index, 0, &vk_queue_);
return true;
}
bool VkReadbackTest::InitImage() {
VkImageCreateInfo image_create_info = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.pNext = nullptr,
.flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT,
.imageType = VK_IMAGE_TYPE_2D,
.format = VK_FORMAT_R8G8B8A8_UNORM,
.extent = VkExtent3D{kWidth, kHeight, 1},
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_LINEAR,
.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0, // not used since not sharing
.pQueueFamilyIndices = nullptr, // not used since not sharing
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
};
VkResult result;
if ((result = vkCreateImage(vk_device_, &image_create_info, nullptr, &vk_image_)) != VK_SUCCESS) {
RTN_MSG(false, "vkCreateImage failed: %d\n", result);
}
VkMemoryRequirements memory_reqs;
vkGetImageMemoryRequirements(vk_device_, vk_image_, &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_ = PAGE_SIZE + kOffset;
if (memory_reqs.alignment) {
bind_offset_ = round_up(bind_offset_, memory_reqs.alignment);
}
VkPhysicalDeviceMemoryProperties memory_props;
vkGetPhysicalDeviceMemoryProperties(vk_physical_device_, &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_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) {
break;
}
}
if (memory_type >= VK_MAX_MEMORY_TYPES) {
RTN_MSG(false, "Can't find compatible mappable memory for image\n");
}
VkExportMemoryAllocateInfoKHR export_info = {
.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO_KHR,
.pNext = nullptr,
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_TEMP_ZIRCON_VMO_BIT_FUCHSIA,
};
VkMemoryAllocateInfo alloc_info = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.pNext =
(ext_ == VK_FUCHSIA_EXTERNAL_MEMORY && !device_memory_handle_) ? &export_info : nullptr,
.allocationSize = memory_reqs.size + bind_offset_,
.memoryTypeIndex = memory_type,
};
if ((result = vkAllocateMemory(vk_device_, &alloc_info, nullptr, &vk_device_memory_)) !=
VK_SUCCESS) {
RTN_MSG(false, "vkAllocateMemory failed\n");
}
if (ext_ == VK_FUCHSIA_EXTERNAL_MEMORY && device_memory_handle_) {
size_t vmo_size;
zx_vmo_get_size(device_memory_handle_, &vmo_size);
VkMemoryZirconHandlePropertiesFUCHSIA properties{
.sType = VK_STRUCTURE_TYPE_TEMP_MEMORY_ZIRCON_HANDLE_PROPERTIES_FUCHSIA,
.pNext = nullptr,
};
result = vkGetMemoryZirconHandlePropertiesFUCHSIA_(
vk_device_, VK_EXTERNAL_MEMORY_HANDLE_TYPE_TEMP_ZIRCON_VMO_BIT_FUCHSIA,
device_memory_handle_, &properties);
if (result != VK_SUCCESS) {
RTN_MSG(false, "vkGetMemoryZirconHandlePropertiesFUCHSIA returned %d\n", result);
}
// Find index of lowest set bit.
memory_type = __builtin_ctz(properties.memoryTypeBits);
VkImportMemoryZirconHandleInfoFUCHSIA handle_info = {
.sType = VK_STRUCTURE_TYPE_TEMP_IMPORT_MEMORY_ZIRCON_HANDLE_INFO_FUCHSIA,
.pNext = nullptr,
.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_TEMP_ZIRCON_VMO_BIT_FUCHSIA,
.handle = device_memory_handle_};
VkMemoryAllocateInfo info = {.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.pNext = &handle_info,
.allocationSize = vmo_size,
.memoryTypeIndex = memory_type};
if ((result = vkAllocateMemory(vk_device_, &info, nullptr, &vk_imported_device_memory_)) !=
VK_SUCCESS) {
RTN_MSG(false, "vkAllocateMemory failed\n");
}
} else if (ext_ == VK_FUCHSIA_EXTERNAL_MEMORY) {
uint32_t handle;
VkMemoryGetZirconHandleInfoFUCHSIA get_handle_info = {
.sType = VK_STRUCTURE_TYPE_TEMP_MEMORY_GET_ZIRCON_HANDLE_INFO_FUCHSIA,
.pNext = nullptr,
.memory = vk_device_memory_,
.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_TEMP_ZIRCON_VMO_BIT_FUCHSIA};
if ((result = vkGetMemoryZirconHandleFUCHSIA_(vk_device_, &get_handle_info, &handle)) !=
VK_SUCCESS) {
RTN_MSG(false, "vkGetMemoryZirconHandleFUCHSIA failed\n");
}
VkMemoryZirconHandlePropertiesFUCHSIA properties{
.sType = VK_STRUCTURE_TYPE_TEMP_MEMORY_ZIRCON_HANDLE_PROPERTIES_FUCHSIA,
.pNext = nullptr,
};
result = vkGetMemoryZirconHandlePropertiesFUCHSIA_(
vk_device_, VK_EXTERNAL_MEMORY_HANDLE_TYPE_TEMP_ZIRCON_VMO_BIT_FUCHSIA, handle,
&properties);
if (result != VK_SUCCESS) {
RTN_MSG(false, "vkGetMemoryZirconHandlePropertiesFUCHSIA returned %d\n", result);
}
device_memory_handle_ = handle;
}
void* addr;
if ((result = vkMapMemory(vk_device_, vk_device_memory_, 0, VK_WHOLE_SIZE, 0, &addr)) !=
VK_SUCCESS) {
RTN_MSG(false, "vkMapMemory failed: %d\n", result);
}
constexpr int kFill = 0xab;
memset(addr, kFill, memory_reqs.size + bind_offset_);
vkUnmapMemory(vk_device_, vk_device_memory_);
if ((result = vkBindImageMemory(vk_device_, vk_image_, vk_device_memory_, bind_offset_)) !=
VK_SUCCESS) {
RTN_MSG(false, "vkBindImageMemory failed\n");
}
VkCommandPoolCreateInfo command_pool_create_info = {
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.queueFamilyIndex = 0,
};
if ((result = vkCreateCommandPool(vk_device_, &command_pool_create_info, nullptr,
&vk_command_pool_)) != VK_SUCCESS) {
RTN_MSG(false, "vkCreateCommandPool failed: %d\n", result);
}
VkCommandBufferAllocateInfo command_buffer_create_info = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.pNext = nullptr,
.commandPool = vk_command_pool_,
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = kNumCommandBuffers};
if ((result = vkAllocateCommandBuffers(vk_device_, &command_buffer_create_info,
&vk_command_buffer_)) != VK_SUCCESS) {
RTN_MSG(false, "vkAllocateCommandBuffers failed: %d\n", result);
}
VkCommandBufferBeginInfo begin_info = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.pNext = nullptr,
.flags = 0,
.pInheritanceInfo = nullptr, // ignored for primary buffers
};
if ((result = vkBeginCommandBuffer(vk_command_buffer_, &begin_info)) != VK_SUCCESS) {
RTN_MSG(false, "vkBeginCommandBuffer failed: %d\n", result);
}
VkClearColorValue color_value = {.float32 = {1.0f, 0.0f, 0.5f, 0.75f}};
VkImageSubresourceRange image_subres_range = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
};
vkCmdClearColorImage(vk_command_buffer_, vk_image_, VK_IMAGE_LAYOUT_GENERAL, &color_value, 1,
&image_subres_range);
if ((result = vkEndCommandBuffer(vk_command_buffer_)) != VK_SUCCESS) {
RTN_MSG(false, "vkEndCommandBuffer failed: %d\n", result);
}
image_initialized_ = true;
return true;
}
bool VkReadbackTest::Exec(VkFence fence) {
if (!Submit(fence)) {
return false;
}
Wait();
return true;
}
bool VkReadbackTest::Submit(VkFence fence) {
VkSubmitInfo submit_info = {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.pNext = nullptr,
.waitSemaphoreCount = 0,
.pWaitSemaphores = nullptr,
.pWaitDstStageMask = nullptr,
.commandBufferCount = 1,
.pCommandBuffers = &vk_command_buffer_,
.signalSemaphoreCount = 0,
.pSignalSemaphores = nullptr,
};
VkResult result;
if ((result = vkQueueSubmit(vk_queue_, 1, &submit_info, fence)) != VK_SUCCESS) {
RTN_MSG(false, "vkQueueSubmit failed\n");
}
return true;
}
void VkReadbackTest::Wait() { vkQueueWaitIdle(vk_queue_); }
bool VkReadbackTest::Readback() {
VkResult result;
void* addr;
VkDeviceMemory vk_device_memory =
ext_ == VkReadbackTest::NONE ? vk_device_memory_ : vk_imported_device_memory_;
if ((result = vkMapMemory(vk_device_, vk_device_memory, 0, VK_WHOLE_SIZE, 0, &addr)) !=
VK_SUCCESS) {
RTN_MSG(false, "vkMapMemory failed: %d\n", result);
}
auto* data = reinterpret_cast<uint32_t*>(static_cast<uint8_t*>(addr) + bind_offset_);
const uint32_t kExpectedValue = 0xBF8000FF;
uint32_t mismatches = 0;
for (uint32_t i = 0; i < kWidth * kHeight; i++) {
if (data[i] != kExpectedValue) {
constexpr int kMaxMismatches = 10;
if (mismatches++ < kMaxMismatches) {
fprintf(stderr, "Value Mismatch at index %d - expected 0x%04x, got 0x%08x\n", i,
kExpectedValue, data[i]);
}
}
}
if (mismatches) {
fprintf(stdout, "****** Test Failed! %d mismatches\n", mismatches);
}
vkUnmapMemory(vk_device_, vk_device_memory);
return mismatches == 0;
}