blob: cf857de06a4221183ed5f3abeea02ebedf4cad03 [file]
// Copyright 2019 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 <lib/fdio/directory.h>
#include <lib/zx/channel.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vector>
#include <vulkan/vulkan.h>
#include "fuchsia/sysmem/cpp/fidl.h"
#include "gtest/gtest.h"
#define PRINT_STDERR(format, ...) \
fprintf(stderr, "%s:%d " format "\n", __FILE__, __LINE__, ##__VA_ARGS__)
namespace {
VkImageCreateInfo GetDefaultImageCreateInfo(bool use_protected_memory, VkFormat format,
uint32_t width, bool linear) {
VkImageCreateInfo image_create_info = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.pNext = nullptr,
.flags = use_protected_memory ? VK_IMAGE_CREATE_PROTECTED_BIT : 0u,
.imageType = VK_IMAGE_TYPE_2D,
.format = format,
.extent = VkExtent3D{width, 64, 1},
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = linear ? VK_IMAGE_TILING_LINEAR : VK_IMAGE_TILING_OPTIMAL,
// Only use sampled, because on Mali some other usages (like color attachment) aren't
// supported for NV12, and some others (implementation-dependent) aren't supported with
// AFBC.
.usage = VK_IMAGE_USAGE_SAMPLED_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,
};
return image_create_info;
}
fuchsia::sysmem::ImageFormatConstraints GetDefaultSysmemImageFormatConstraints() {
fuchsia::sysmem::ImageFormatConstraints bgra_image_constraints;
bgra_image_constraints.required_min_coded_width = 1024;
bgra_image_constraints.required_min_coded_height = 1024;
bgra_image_constraints.required_max_coded_width = 1024;
bgra_image_constraints.required_max_coded_height = 1024;
bgra_image_constraints.max_coded_width = 8192;
bgra_image_constraints.max_coded_height = 8192;
bgra_image_constraints.max_bytes_per_row = 0xffffffff;
bgra_image_constraints.pixel_format = {fuchsia::sysmem::PixelFormatType::BGRA32, false};
bgra_image_constraints.color_spaces_count = 1;
bgra_image_constraints.color_space[0].type = fuchsia::sysmem::ColorSpaceType::SRGB;
return bgra_image_constraints;
}
class VulkanTest {
public:
bool Initialize();
bool Exec(VkFormat format, uint32_t width, bool direct, bool linear,
bool repeat_constraints_as_non_protected,
const std::vector<fuchsia::sysmem::ImageFormatConstraints>& format_constraints =
std::vector<fuchsia::sysmem::ImageFormatConstraints>());
bool ExecBuffer(uint32_t size);
void set_use_protected_memory(bool use) { use_protected_memory_ = use; }
bool device_supports_protected_memory() const { return device_supports_protected_memory_; }
private:
bool InitVulkan();
bool InitImage();
bool is_initialized_ = false;
bool use_protected_memory_ = false;
bool device_supports_protected_memory_ = false;
VkPhysicalDevice vk_physical_device_;
VkDevice vk_device_;
VkQueue vk_queue_;
VkImage vk_image_;
VkDeviceMemory vk_device_memory_;
VkCommandPool vk_command_pool_;
VkCommandBuffer vk_command_buffer_;
PFN_vkCreateBufferCollectionFUCHSIA vkCreateBufferCollectionFUCHSIA_;
PFN_vkSetBufferCollectionConstraintsFUCHSIA vkSetBufferCollectionConstraintsFUCHSIA_;
PFN_vkSetBufferCollectionBufferConstraintsFUCHSIA vkSetBufferCollectionBufferConstraintsFUCHSIA_;
PFN_vkDestroyBufferCollectionFUCHSIA vkDestroyBufferCollectionFUCHSIA_;
PFN_vkGetBufferCollectionPropertiesFUCHSIA vkGetBufferCollectionPropertiesFUCHSIA_;
};
bool VulkanTest::Initialize() {
if (is_initialized_)
return false;
if (!InitVulkan()) {
PRINT_STDERR("failed to initialize Vulkan");
return false;
}
is_initialized_ = true;
return true;
}
bool VulkanTest::InitVulkan() {
VkApplicationInfo app_info = {
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.pNext = nullptr,
.pApplicationName = "vkext",
.applicationVersion = 0,
.pEngineName = nullptr,
.engineVersion = 0,
.apiVersion = VK_API_VERSION_1_1,
};
std::vector<const char*> enabled_extensions{};
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;
static_cast<uint32_t>(enabled_extensions.size()),
enabled_extensions.data(),
};
VkAllocationCallbacks* allocation_callbacks = nullptr;
VkInstance instance;
VkResult result;
if ((result = vkCreateInstance(&create_info, allocation_callbacks, &instance)) != VK_SUCCESS) {
PRINT_STDERR("vkCreateInstance failed %d", result);
return false;
}
uint32_t physical_device_count;
if ((result = vkEnumeratePhysicalDevices(instance, &physical_device_count, nullptr)) !=
VK_SUCCESS) {
PRINT_STDERR("vkEnumeratePhysicalDevices failed %d", result);
return false;
}
if (physical_device_count < 1) {
PRINT_STDERR("unexpected physical_device_count %d", physical_device_count);
return false;
}
std::vector<VkPhysicalDevice> physical_devices(physical_device_count);
if ((result = vkEnumeratePhysicalDevices(instance, &physical_device_count,
physical_devices.data())) != VK_SUCCESS) {
PRINT_STDERR("vkEnumeratePhysicalDevices failed %d", result);
return false;
}
uint32_t queue_family_count;
vkGetPhysicalDeviceQueueFamilyProperties(physical_devices[0], &queue_family_count, nullptr);
if (queue_family_count < 1) {
PRINT_STDERR("invalid queue_family_count %d", queue_family_count);
return false;
}
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) {
PRINT_STDERR("couldn't find an appropriate queue");
return false;
}
float queue_priorities[1] = {0.0};
VkDeviceQueueCreateInfo queue_create_info = {.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.queueFamilyIndex = 0,
.queueCount = 1,
.pQueuePriorities = queue_priorities};
VkPhysicalDeviceProtectedMemoryFeatures protected_memory = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES,
.pNext = nullptr,
.protectedMemory = VK_TRUE,
};
{
VkPhysicalDeviceProperties properties;
vkGetPhysicalDeviceProperties(physical_devices[0], &properties);
if (VK_VERSION_MAJOR(properties.apiVersion) != 1 ||
VK_VERSION_MINOR(properties.apiVersion) > 0) {
VkPhysicalDeviceFeatures2 features = {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
.pNext = &protected_memory};
vkGetPhysicalDeviceFeatures2(physical_devices[0], &features);
if (protected_memory.protectedMemory) {
device_supports_protected_memory_ = true;
}
}
}
std::vector<const char*> enabled_device_extensions{VK_FUCHSIA_BUFFER_COLLECTION_EXTENSION_NAME};
VkDeviceCreateInfo createInfo = {
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.pNext = device_supports_protected_memory_ ? &protected_memory : nullptr,
.flags = 0,
.queueCreateInfoCount = 1,
.pQueueCreateInfos = &queue_create_info,
.enabledLayerCount = 0,
.ppEnabledLayerNames = nullptr,
.enabledExtensionCount = static_cast<uint32_t>(enabled_device_extensions.size()),
.ppEnabledExtensionNames = enabled_device_extensions.data(),
.pEnabledFeatures = nullptr};
VkDevice vkdevice;
if ((result = vkCreateDevice(physical_devices[0], &createInfo, nullptr /* allocationcallbacks */,
&vkdevice)) != VK_SUCCESS) {
PRINT_STDERR("vkCreateDevice failed: %d", result);
return false;
}
vk_physical_device_ = physical_devices[0];
vk_device_ = vkdevice;
vkGetDeviceQueue(vkdevice, queue_family_index, 0, &vk_queue_);
vkCreateBufferCollectionFUCHSIA_ = reinterpret_cast<PFN_vkCreateBufferCollectionFUCHSIA>(
vkGetDeviceProcAddr(vk_device_, "vkCreateBufferCollectionFUCHSIA"));
if (!vkCreateBufferCollectionFUCHSIA_) {
PRINT_STDERR("No vkCreateBufferCollectionFUCHSIA");
return false;
}
vkDestroyBufferCollectionFUCHSIA_ = reinterpret_cast<PFN_vkDestroyBufferCollectionFUCHSIA>(
vkGetDeviceProcAddr(vk_device_, "vkDestroyBufferCollectionFUCHSIA"));
if (!vkDestroyBufferCollectionFUCHSIA_) {
PRINT_STDERR("No vkDestroyBufferCollectionFUCHSIA");
return false;
}
vkSetBufferCollectionConstraintsFUCHSIA_ =
reinterpret_cast<PFN_vkSetBufferCollectionConstraintsFUCHSIA>(
vkGetDeviceProcAddr(vk_device_, "vkSetBufferCollectionConstraintsFUCHSIA"));
if (!vkSetBufferCollectionConstraintsFUCHSIA_) {
PRINT_STDERR("No vkSetBufferCollectionConstraintsFUCHSIA");
return false;
}
vkSetBufferCollectionBufferConstraintsFUCHSIA_ =
reinterpret_cast<PFN_vkSetBufferCollectionBufferConstraintsFUCHSIA>(
vkGetDeviceProcAddr(vk_device_, "vkSetBufferCollectionBufferConstraintsFUCHSIA"));
if (!vkSetBufferCollectionBufferConstraintsFUCHSIA_) {
PRINT_STDERR("No vkSetBufferCollectionBufferConstraintsFUCHSIA");
return false;
}
vkGetBufferCollectionPropertiesFUCHSIA_ =
reinterpret_cast<PFN_vkGetBufferCollectionPropertiesFUCHSIA>(
vkGetDeviceProcAddr(vk_device_, "vkGetBufferCollectionPropertiesFUCHSIA"));
if (!vkGetBufferCollectionPropertiesFUCHSIA_) {
PRINT_STDERR("No vkGetBufferCollectionPropertiesFUCHSIA_");
return false;
}
return true;
}
bool VulkanTest::Exec(
VkFormat format, uint32_t width, bool direct, bool linear,
bool repeat_constraints_as_non_protected,
const std::vector<fuchsia::sysmem::ImageFormatConstraints>& format_constraints) {
VkResult result;
fuchsia::sysmem::AllocatorSyncPtr sysmem_allocator;
zx_status_t status = fdio_service_connect("/svc/fuchsia.sysmem.Allocator",
sysmem_allocator.NewRequest().TakeChannel().release());
if (status != ZX_OK) {
PRINT_STDERR("fdio_service_connect failed: %d", status);
return false;
}
fuchsia::sysmem::BufferCollectionTokenSyncPtr vulkan_token;
status = sysmem_allocator->AllocateSharedCollection(vulkan_token.NewRequest());
if (status != ZX_OK) {
PRINT_STDERR("AllocateSharedCollection failed: %d", status);
return false;
}
fuchsia::sysmem::BufferCollectionTokenSyncPtr local_token;
status = vulkan_token->Duplicate(std::numeric_limits<uint32_t>::max(), local_token.NewRequest());
if (status != ZX_OK) {
PRINT_STDERR("Duplicate failed: %d", status);
return false;
}
status = local_token->Sync();
if (status != ZX_OK) {
PRINT_STDERR("Sync failed: %d", status);
return false;
}
// This bool suggests that we dup another token to set the same constraints, skipping protected
// memory requirements. This emulates another participant which does not require protected memory.
if (repeat_constraints_as_non_protected) {
fuchsia::sysmem::BufferCollectionTokenSyncPtr repeat_token;
status =
vulkan_token->Duplicate(std::numeric_limits<uint32_t>::max(), repeat_token.NewRequest());
if (status != ZX_OK) {
PRINT_STDERR("Duplicate failed: %d", status);
return false;
}
status = vulkan_token->Sync();
if (status != ZX_OK) {
PRINT_STDERR("Sync failed: %d", status);
return false;
}
VkImageCreateInfo image_create_info =
GetDefaultImageCreateInfo(/*use_protected_memory=*/false, format, width, linear);
VkBufferCollectionCreateInfoFUCHSIA import_info = {
.sType = VK_STRUCTURE_TYPE_BUFFER_COLLECTION_CREATE_INFO_FUCHSIA,
.pNext = nullptr,
.collectionToken = repeat_token.Unbind().TakeChannel().release(),
};
VkBufferCollectionFUCHSIA collection;
result = vkCreateBufferCollectionFUCHSIA_(vk_device_, &import_info, nullptr, &collection);
if (result != VK_SUCCESS) {
PRINT_STDERR("Failed to import buffer collection: %d", result);
return false;
}
result = vkSetBufferCollectionConstraintsFUCHSIA_(vk_device_, collection, &image_create_info);
if (result != VK_SUCCESS) {
PRINT_STDERR("Failed to set buffer constraints: %d", result);
return false;
}
}
VkImageCreateInfo image_create_info =
GetDefaultImageCreateInfo(use_protected_memory_, format, width, linear);
VkBufferCollectionCreateInfoFUCHSIA import_info = {
.sType = VK_STRUCTURE_TYPE_BUFFER_COLLECTION_CREATE_INFO_FUCHSIA,
.pNext = nullptr,
.collectionToken = vulkan_token.Unbind().TakeChannel().release(),
};
VkBufferCollectionFUCHSIA collection;
result = vkCreateBufferCollectionFUCHSIA_(vk_device_, &import_info, nullptr, &collection);
if (result != VK_SUCCESS) {
PRINT_STDERR("Failed to import buffer collection: %d", result);
return false;
}
result = vkSetBufferCollectionConstraintsFUCHSIA_(vk_device_, collection, &image_create_info);
if (result != VK_SUCCESS) {
PRINT_STDERR("Failed to set buffer constraints: %d", result);
return false;
}
fuchsia::sysmem::BufferCollectionSyncPtr sysmem_collection;
status = sysmem_allocator->BindSharedCollection(std::move(local_token),
sysmem_collection.NewRequest());
if (status != ZX_OK) {
PRINT_STDERR("BindSharedCollection failed: %d", status);
return false;
}
fuchsia::sysmem::BufferCollectionConstraints constraints{};
if (!format_constraints.empty()) {
// Use the other connection to specify the actual desired format and size,
// which should be compatible with what the vulkan driver can use.
assert(direct);
constraints.usage.vulkan = fuchsia::sysmem::vulkanUsageTransferDst;
// Try multiple format modifiers.
constraints.image_format_constraints_count = format_constraints.size();
for (uint32_t i = 0; i < constraints.image_format_constraints_count; i++) {
constraints.image_format_constraints[i] = format_constraints[i];
}
status = sysmem_collection->SetConstraints(true, constraints);
} else if (direct) {
status = sysmem_collection->SetConstraints(false, constraints);
} else {
constraints.usage.vulkan = fuchsia::sysmem::vulkanUsageTransferDst;
// The total buffer count should be 1 with or without this set (because
// the Vulkan driver sets a minimum of one buffer).
constraints.min_buffer_count_for_camping = 1;
status = sysmem_collection->SetConstraints(true, constraints);
}
if (status != ZX_OK) {
PRINT_STDERR("SetConstraints failed: %d", status);
return false;
}
zx_status_t allocation_status;
fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info{};
status = sysmem_collection->WaitForBuffersAllocated(&allocation_status, &buffer_collection_info);
if (status != ZX_OK) {
PRINT_STDERR("WaitForBuffersAllocated failed: %d", status);
return false;
}
if (allocation_status != ZX_OK) {
if (use_protected_memory_) {
PRINT_STDERR("WaitForBuffersAllocated failed: %d", allocation_status);
return false;
}
PRINT_STDERR("WaitForBuffersAllocated failed: %d", allocation_status);
return false;
}
status = sysmem_collection->Close();
if (status != ZX_OK) {
PRINT_STDERR("Close failed: %d", status);
return false;
}
EXPECT_EQ(1u, buffer_collection_info.buffer_count);
fuchsia::sysmem::PixelFormat pixel_format =
buffer_collection_info.settings.image_format_constraints.pixel_format;
if (!direct) {
fidl::Encoder encoder(fidl::Encoder::NO_HEADER);
encoder.Alloc(fidl::EncodingInlineSize<fuchsia::sysmem::SingleBufferSettings>(&encoder));
buffer_collection_info.settings.Encode(&encoder, 0);
std::vector<uint8_t> encoded_data = encoder.TakeBytes();
VkFuchsiaImageFormatFUCHSIA image_format_fuchsia = {
.sType = VK_STRUCTURE_TYPE_FUCHSIA_IMAGE_FORMAT_FUCHSIA,
.pNext = nullptr,
.imageFormat = encoded_data.data(),
.imageFormatSize = static_cast<uint32_t>(encoded_data.size())};
image_create_info.pNext = &image_format_fuchsia;
result = vkCreateImage(vk_device_, &image_create_info, nullptr, &vk_image_);
if (result != VK_SUCCESS) {
PRINT_STDERR("vkCreateImage failed: %d", result);
return false;
}
} else {
VkBufferCollectionImageCreateInfoFUCHSIA image_format_fuchsia = {
.sType = VK_STRUCTURE_TYPE_BUFFER_COLLECTION_IMAGE_CREATE_INFO_FUCHSIA,
.pNext = nullptr,
.collection = collection,
.index = 0};
if (format == VK_FORMAT_UNDEFINED) {
EXPECT_EQ(fuchsia::sysmem::PixelFormatType::BGRA32, pixel_format.type);
// Ensure that the image created matches what was asked for on
// sysmem_connection.
image_create_info.extent.width = 1024;
image_create_info.extent.height = 1024;
image_create_info.format = VK_FORMAT_B8G8R8A8_UNORM;
}
image_create_info.pNext = &image_format_fuchsia;
result = vkCreateImage(vk_device_, &image_create_info, nullptr, &vk_image_);
if (result != VK_SUCCESS) {
PRINT_STDERR("vkCreateImage failed: %d", result);
return false;
}
}
if (linear) {
bool is_yuv = (format == VK_FORMAT_G8_B8R8_2PLANE_420_UNORM_KHR) ||
(format == VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM_KHR);
VkImageSubresource subresource = {
.aspectMask = is_yuv ? VK_IMAGE_ASPECT_PLANE_0_BIT : VK_IMAGE_ASPECT_COLOR_BIT,
.mipLevel = 0,
.arrayLayer = 0};
VkSubresourceLayout layout;
vkGetImageSubresourceLayout(vk_device_, vk_image_, &subresource, &layout);
VkDeviceSize min_bytes_per_pixel = is_yuv ? 1 : 4;
EXPECT_LE(min_bytes_per_pixel * width, layout.rowPitch);
EXPECT_LE(min_bytes_per_pixel * width * 64, layout.size);
}
if (linear && (format == VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM_KHR)) {
VkImageSubresource subresource = {
.aspectMask = VK_IMAGE_ASPECT_PLANE_1_BIT, .mipLevel = 0, .arrayLayer = 0};
VkSubresourceLayout b_layout;
vkGetImageSubresourceLayout(vk_device_, vk_image_, &subresource, &b_layout);
subresource.aspectMask = VK_IMAGE_ASPECT_PLANE_2_BIT;
VkSubresourceLayout r_layout;
vkGetImageSubresourceLayout(vk_device_, vk_image_, &subresource, &r_layout);
// I420 has the U plane (mapped to B) before the V plane (mapped to R)
EXPECT_LT(b_layout.offset, r_layout.offset);
}
if (!direct) {
VkMemoryRequirements memory_reqs;
vkGetImageMemoryRequirements(vk_device_, vk_image_, &memory_reqs);
// Use first supported type
uint32_t memory_type = __builtin_ctz(memory_reqs.memoryTypeBits);
// The driver may not have the right information to choose the correct
// heap for protected memory.
EXPECT_FALSE(use_protected_memory_);
VkImportMemoryZirconHandleInfoFUCHSIA handle_info = {
.sType = VK_STRUCTURE_TYPE_TEMP_IMPORT_MEMORY_ZIRCON_HANDLE_INFO_FUCHSIA,
.pNext = nullptr,
VK_EXTERNAL_MEMORY_HANDLE_TYPE_TEMP_ZIRCON_VMO_BIT_FUCHSIA,
buffer_collection_info.buffers[0].vmo.release()};
VkMemoryAllocateInfo alloc_info = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.pNext = &handle_info,
.allocationSize = memory_reqs.size,
.memoryTypeIndex = memory_type,
};
if ((result = vkAllocateMemory(vk_device_, &alloc_info, nullptr, &vk_device_memory_)) !=
VK_SUCCESS) {
PRINT_STDERR("vkAllocateMemory failed");
return false;
}
result = vkBindImageMemory(vk_device_, vk_image_, vk_device_memory_, 0);
if (result != VK_SUCCESS) {
PRINT_STDERR("vkBindImageMemory failed");
return false;
}
} else {
VkMemoryRequirements requirements;
vkGetImageMemoryRequirements(vk_device_, vk_image_, &requirements);
VkBufferCollectionPropertiesFUCHSIA properties = {
.sType = VK_STRUCTURE_TYPE_BUFFER_COLLECTION_PROPERTIES_FUCHSIA};
result = vkGetBufferCollectionPropertiesFUCHSIA_(vk_device_, collection, &properties);
if (result != VK_SUCCESS) {
PRINT_STDERR("vkBindImageMemory failed");
return false;
}
EXPECT_EQ(1u, properties.count);
uint32_t viable_memory_types = properties.memoryTypeBits & requirements.memoryTypeBits;
EXPECT_NE(0u, viable_memory_types);
uint32_t memory_type = __builtin_ctz(viable_memory_types);
VkPhysicalDeviceMemoryProperties memory_properties;
vkGetPhysicalDeviceMemoryProperties(vk_physical_device_, &memory_properties);
EXPECT_LT(memory_type, memory_properties.memoryTypeCount);
if (use_protected_memory_) {
for (uint32_t i = 0; i < memory_properties.memoryTypeCount; ++i) {
if (properties.memoryTypeBits & (1 << i)) {
// Based only on the buffer collection it should be possible to
// determine that this is protected memory. viable_memory_types
// is a subset of these bits, so that should be true for it as
// well.
EXPECT_TRUE(memory_properties.memoryTypes[i].propertyFlags &
VK_MEMORY_PROPERTY_PROTECTED_BIT);
}
}
} else {
EXPECT_FALSE(memory_properties.memoryTypes[memory_type].propertyFlags &
VK_MEMORY_PROPERTY_PROTECTED_BIT);
}
VkImportMemoryBufferCollectionFUCHSIA import_info = {
.sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_BUFFER_COLLECTION_FUCHSIA};
import_info.collection = collection;
import_info.index = 0;
VkMemoryAllocateInfo alloc_info = {.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
alloc_info.pNext = &import_info;
alloc_info.allocationSize = requirements.size;
alloc_info.memoryTypeIndex = memory_type;
result = vkAllocateMemory(vk_device_, &alloc_info, nullptr, &vk_device_memory_);
if (result != VK_SUCCESS) {
PRINT_STDERR("vkCreateImage failed: %d", result);
return false;
}
result = vkBindImageMemory(vk_device_, vk_image_, vk_device_memory_, 0u);
if (result != VK_SUCCESS) {
PRINT_STDERR("vkCreateImage failed: %d", result);
return false;
}
}
vkDestroyImage(vk_device_, vk_image_, nullptr);
vkFreeMemory(vk_device_, vk_device_memory_, nullptr);
vkDestroyBufferCollectionFUCHSIA_(vk_device_, collection, nullptr);
return true;
}
bool VulkanTest::ExecBuffer(uint32_t size) {
VkResult result;
fuchsia::sysmem::AllocatorSyncPtr sysmem_allocator;
zx_status_t status = fdio_service_connect("/svc/fuchsia.sysmem.Allocator",
sysmem_allocator.NewRequest().TakeChannel().release());
if (status != ZX_OK) {
PRINT_STDERR("fdio_service_connect failed: %d", status);
return false;
}
fuchsia::sysmem::BufferCollectionTokenSyncPtr vulkan_token;
status = sysmem_allocator->AllocateSharedCollection(vulkan_token.NewRequest());
if (status != ZX_OK) {
PRINT_STDERR("AllocateSharedCollection failed: %d", status);
return false;
}
fuchsia::sysmem::BufferCollectionTokenSyncPtr local_token;
status = vulkan_token->Duplicate(std::numeric_limits<uint32_t>::max(), local_token.NewRequest());
if (status != ZX_OK) {
PRINT_STDERR("Duplicate failed: %d", status);
return false;
}
status = local_token->Sync();
if (status != ZX_OK) {
PRINT_STDERR("Sync failed: %d", status);
return false;
}
VkBufferCreateInfo buffer_create_info = {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = use_protected_memory_ ? VK_BUFFER_CREATE_PROTECTED_BIT : 0u,
.size = size,
.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
};
VkBufferCollectionCreateInfoFUCHSIA import_info = {
.sType = VK_STRUCTURE_TYPE_BUFFER_COLLECTION_CREATE_INFO_FUCHSIA,
.pNext = nullptr,
.collectionToken = vulkan_token.Unbind().TakeChannel().release(),
};
VkBufferCollectionFUCHSIA collection;
result = vkCreateBufferCollectionFUCHSIA_(vk_device_, &import_info, nullptr, &collection);
if (result != VK_SUCCESS) {
PRINT_STDERR("Failed to import buffer collection: %d", result);
return false;
}
VkBufferConstraintsInfoFUCHSIA constraints = {
.sType = VK_STRUCTURE_TYPE_BUFFER_CONSTRAINTS_INFO_FUCHSIA,
.pNext = nullptr,
.pBufferCreateInfo = &buffer_create_info,
.requiredFormatFeatures = VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT,
.minCount = 2,
};
result = vkSetBufferCollectionBufferConstraintsFUCHSIA_(vk_device_, collection, &constraints);
if (result != VK_SUCCESS) {
PRINT_STDERR("Failed to set buffer constraints: %d", result);
return false;
}
fuchsia::sysmem::BufferCollectionSyncPtr sysmem_collection;
status = sysmem_allocator->BindSharedCollection(std::move(local_token),
sysmem_collection.NewRequest());
if (status != ZX_OK) {
PRINT_STDERR("BindSharedCollection failed: %d", status);
return false;
}
fuchsia::sysmem::BufferCollectionConstraints sysmem_constraints{};
status = sysmem_collection->SetConstraints(false, sysmem_constraints);
if (status != ZX_OK) {
PRINT_STDERR("SetConstraints failed: %d", status);
return false;
}
zx_status_t allocation_status;
fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info{};
status = sysmem_collection->WaitForBuffersAllocated(&allocation_status, &buffer_collection_info);
if (status != ZX_OK) {
PRINT_STDERR("WaitForBuffersAllocated failed: %d", status);
return false;
}
if (allocation_status != ZX_OK) {
PRINT_STDERR("WaitForBuffersAllocated failed: %d", allocation_status);
return false;
}
status = sysmem_collection->Close();
if (status != ZX_OK) {
PRINT_STDERR("Close failed: %d", status);
return false;
}
VkBufferCollectionBufferCreateInfoFUCHSIA collection_buffer_create_info = {
.sType = VK_STRUCTURE_TYPE_BUFFER_COLLECTION_BUFFER_CREATE_INFO_FUCHSIA,
.pNext = nullptr,
.collection = collection,
.index = 1};
buffer_create_info.pNext = &collection_buffer_create_info;
VkBuffer buffer;
result = vkCreateBuffer(vk_device_, &buffer_create_info, nullptr, &buffer);
if (result != VK_SUCCESS) {
PRINT_STDERR("vkCreateBuffer failed: %d", result);
return false;
}
VkMemoryRequirements requirements;
vkGetBufferMemoryRequirements(vk_device_, buffer, &requirements);
VkBufferCollectionPropertiesFUCHSIA properties = {
.sType = VK_STRUCTURE_TYPE_BUFFER_COLLECTION_PROPERTIES_FUCHSIA};
result = vkGetBufferCollectionPropertiesFUCHSIA_(vk_device_, collection, &properties);
if (result != VK_SUCCESS) {
PRINT_STDERR("vkGetBufferCollectionProperties failed");
return false;
}
EXPECT_EQ(2u, properties.count);
uint32_t viable_memory_types = properties.memoryTypeBits & requirements.memoryTypeBits;
EXPECT_NE(0u, viable_memory_types);
uint32_t memory_type = __builtin_ctz(viable_memory_types);
VkPhysicalDeviceMemoryProperties memory_properties;
vkGetPhysicalDeviceMemoryProperties(vk_physical_device_, &memory_properties);
EXPECT_LT(memory_type, memory_properties.memoryTypeCount);
if (use_protected_memory_) {
for (uint32_t i = 0; i < memory_properties.memoryTypeCount; ++i) {
if (properties.memoryTypeBits & (1 << i)) {
// Based only on the buffer collection it should be possible to
// determine that this is protected memory. viable_memory_types
// is a subset of these bits, so that should be true for it as
// well.
EXPECT_TRUE(memory_properties.memoryTypes[i].propertyFlags &
VK_MEMORY_PROPERTY_PROTECTED_BIT);
}
}
} else {
EXPECT_FALSE(memory_properties.memoryTypes[memory_type].propertyFlags &
VK_MEMORY_PROPERTY_PROTECTED_BIT);
}
VkImportMemoryBufferCollectionFUCHSIA memory_import_info = {
.sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_BUFFER_COLLECTION_FUCHSIA,
.collection = collection,
.index = 1};
VkMemoryAllocateInfo alloc_info = {.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
alloc_info.pNext = &memory_import_info;
alloc_info.allocationSize = requirements.size;
alloc_info.memoryTypeIndex = memory_type;
result = vkAllocateMemory(vk_device_, &alloc_info, nullptr, &vk_device_memory_);
if (result != VK_SUCCESS) {
PRINT_STDERR("vkBindBufferMemory failed: %d", result);
return false;
}
result = vkBindBufferMemory(vk_device_, buffer, vk_device_memory_, 0u);
if (result != VK_SUCCESS) {
PRINT_STDERR("vkBindBufferMemory failed: %d", result);
return false;
}
vkDestroyBuffer(vk_device_, buffer, nullptr);
vkFreeMemory(vk_device_, vk_device_memory_, nullptr);
vkDestroyBufferCollectionFUCHSIA_(vk_device_, collection, nullptr);
return true;
}
// Parameter is true if the image should be linear.
class VulkanImageExtensionTest : public ::testing::TestWithParam<bool> {};
TEST_P(VulkanImageExtensionTest, BufferCollectionNV12) {
VulkanTest test;
ASSERT_TRUE(test.Initialize());
ASSERT_TRUE(test.Exec(VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, 64, false, GetParam(), false));
}
TEST_P(VulkanImageExtensionTest, BufferCollectionI420) {
VulkanTest test;
ASSERT_TRUE(test.Initialize());
ASSERT_TRUE(test.Exec(VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM, 64, false, GetParam(), false));
}
TEST_P(VulkanImageExtensionTest, BufferCollectionNV12_1025) {
VulkanTest test;
ASSERT_TRUE(test.Initialize());
ASSERT_TRUE(test.Exec(VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, 1025, false, GetParam(), false));
}
TEST_P(VulkanImageExtensionTest, BufferCollectionRGBA) {
VulkanTest test;
ASSERT_TRUE(test.Initialize());
ASSERT_TRUE(test.Exec(VK_FORMAT_R8G8B8A8_UNORM, 64, false, GetParam(), false));
}
TEST_P(VulkanImageExtensionTest, BufferCollectionRGBA_1025) {
VulkanTest test;
ASSERT_TRUE(test.Initialize());
ASSERT_TRUE(test.Exec(VK_FORMAT_R8G8B8A8_UNORM, 1025, false, GetParam(), false));
}
TEST_P(VulkanImageExtensionTest, BufferCollectionDirectNV12) {
VulkanTest test;
ASSERT_TRUE(test.Initialize());
ASSERT_TRUE(test.Exec(VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, 64, true, GetParam(), false));
}
TEST_P(VulkanImageExtensionTest, BufferCollectionDirectI420) {
VulkanTest test;
ASSERT_TRUE(test.Initialize());
ASSERT_TRUE(test.Exec(VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM, 64, true, GetParam(), false));
}
TEST_P(VulkanImageExtensionTest, BufferCollectionUndefined) {
VulkanTest test;
ASSERT_TRUE(test.Initialize());
fuchsia::sysmem::ImageFormatConstraints bgra_image_constraints =
GetDefaultSysmemImageFormatConstraints();
fuchsia::sysmem::ImageFormatConstraints bgra_tiled_image_constraints =
GetDefaultSysmemImageFormatConstraints();
bgra_tiled_image_constraints.pixel_format = {
fuchsia::sysmem::PixelFormatType::BGRA32,
true,
{fuchsia::sysmem::FORMAT_MODIFIER_INTEL_I915_X_TILED}};
std::vector<fuchsia::sysmem::ImageFormatConstraints> two_constraints{
bgra_image_constraints, bgra_tiled_image_constraints};
ASSERT_TRUE(test.Exec(VK_FORMAT_UNDEFINED, 64, true, GetParam(), false, two_constraints));
}
TEST_P(VulkanImageExtensionTest, BufferCollectionMultipleFormats) {
VulkanTest test;
ASSERT_TRUE(test.Initialize());
fuchsia::sysmem::ImageFormatConstraints nv12_image_constraints =
GetDefaultSysmemImageFormatConstraints();
nv12_image_constraints.pixel_format = {fuchsia::sysmem::PixelFormatType::NV12, false};
nv12_image_constraints.color_space[0].type = fuchsia::sysmem::ColorSpaceType::REC709;
fuchsia::sysmem::ImageFormatConstraints bgra_image_constraints =
GetDefaultSysmemImageFormatConstraints();
fuchsia::sysmem::ImageFormatConstraints bgra_tiled_image_constraints =
GetDefaultSysmemImageFormatConstraints();
bgra_tiled_image_constraints.pixel_format = {
fuchsia::sysmem::PixelFormatType::BGRA32,
true,
{fuchsia::sysmem::FORMAT_MODIFIER_INTEL_I915_X_TILED}};
std::vector<fuchsia::sysmem::ImageFormatConstraints> all_constraints{
nv12_image_constraints, bgra_image_constraints, bgra_tiled_image_constraints};
ASSERT_TRUE(
test.Exec(VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, 64, true, GetParam(), false, all_constraints));
ASSERT_TRUE(test.Exec(VK_FORMAT_B8G8R8A8_UNORM, 64, true, GetParam(), false, all_constraints));
}
TEST_P(VulkanImageExtensionTest, BufferCollectionProtectedRGBA) {
VulkanTest test;
test.set_use_protected_memory(true);
ASSERT_TRUE(test.Initialize());
ASSERT_TRUE(test.device_supports_protected_memory());
ASSERT_TRUE(test.Exec(VK_FORMAT_R8G8B8A8_UNORM, 64, true, GetParam(), false));
}
TEST_P(VulkanImageExtensionTest, ProtectedAndNonprotectedConstraints) {
VulkanTest test;
test.set_use_protected_memory(true);
ASSERT_TRUE(test.Initialize());
ASSERT_TRUE(test.device_supports_protected_memory());
ASSERT_TRUE(test.Exec(VK_FORMAT_R8G8B8A8_UNORM, 64, true, GetParam(), true));
}
INSTANTIATE_TEST_SUITE_P(, VulkanImageExtensionTest, ::testing::Bool());
TEST(VulkanExtensionTest, BufferCollectionBuffer1024) {
VulkanTest test;
ASSERT_TRUE(test.Initialize());
ASSERT_TRUE(test.ExecBuffer(1024));
}
TEST(VulkanExtensionTest, BufferCollectionBuffer16384) {
VulkanTest test;
ASSERT_TRUE(test.Initialize());
ASSERT_TRUE(test.ExecBuffer(16384));
}
TEST(VulkanExtensionTest, BufferCollectionProtectedBuffer) {
VulkanTest test;
test.set_use_protected_memory(true);
ASSERT_TRUE(test.Initialize());
ASSERT_TRUE(test.device_supports_protected_memory());
ASSERT_TRUE(test.ExecBuffer(16384));
}
} // namespace