| // 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 "gtest/gtest.h" |
| |
| #define MAGMA_DLOG_ENABLE 1 |
| #if defined(MAGMA_USE_SHIM) |
| #include "vulkan_shim.h" |
| #else |
| #include <vulkan/vulkan.h> |
| #endif |
| #include "fuchsia/sysmem/cpp/fidl.h" |
| #include <lib/fdio/directory.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <vector> |
| #include <lib/zx/channel.h> |
| |
| #include "magma_util/dlog.h" |
| #include "magma_util/macros.h" |
| |
| namespace { |
| |
| class VulkanTest { |
| public: |
| bool Initialize(); |
| bool Exec(VkFormat format, uint32_t width, bool linear); |
| |
| private: |
| bool InitVulkan(); |
| bool InitImage(); |
| |
| bool is_initialized_ = 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_vkDestroyBufferCollectionFUCHSIA vkDestroyBufferCollectionFUCHSIA_; |
| }; |
| |
| bool VulkanTest::Initialize() |
| { |
| if (is_initialized_) |
| return false; |
| |
| if (!InitVulkan()) |
| return DRETF(false, "failed to initialize Vulkan"); |
| |
| is_initialized_ = true; |
| |
| return true; |
| } |
| |
| bool VulkanTest::InitVulkan() |
| { |
| std::vector<const char*> enabled_extensions{}; |
| VkInstanceCreateInfo create_info{ |
| VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, // VkStructureType sType; |
| nullptr, // const void* pNext; |
| 0, // VkInstanceCreateFlags flags; |
| nullptr, // 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) |
| return DRETF(false, "vkCreateInstance failed %d", result); |
| |
| DLOG("vkCreateInstance succeeded"); |
| |
| uint32_t physical_device_count; |
| if ((result = vkEnumeratePhysicalDevices(instance, &physical_device_count, nullptr)) != |
| VK_SUCCESS) |
| return DRETF(false, "vkEnumeratePhysicalDevices failed %d", result); |
| |
| if (physical_device_count < 1) |
| return DRETF(false, "unexpected physical_device_count %d", physical_device_count); |
| |
| DLOG("vkEnumeratePhysicalDevices returned count %d", physical_device_count); |
| |
| std::vector<VkPhysicalDevice> physical_devices(physical_device_count); |
| if ((result = vkEnumeratePhysicalDevices(instance, &physical_device_count, |
| physical_devices.data())) != VK_SUCCESS) |
| return DRETF(false, "vkEnumeratePhysicalDevices failed %d", result); |
| |
| for (auto device : physical_devices) { |
| VkPhysicalDeviceProperties properties; |
| vkGetPhysicalDeviceProperties(device, &properties); |
| DLOG("PHYSICAL DEVICE: %s", properties.deviceName); |
| DLOG("apiVersion 0x%x", properties.apiVersion); |
| DLOG("driverVersion 0x%x", properties.driverVersion); |
| DLOG("vendorID 0x%x", properties.vendorID); |
| DLOG("deviceID 0x%x", properties.deviceID); |
| DLOG("deviceType 0x%x", properties.deviceType); |
| } |
| |
| uint32_t queue_family_count; |
| vkGetPhysicalDeviceQueueFamilyProperties(physical_devices[0], &queue_family_count, nullptr); |
| |
| if (queue_family_count < 1) |
| return DRETF(false, "invalid queue_family_count %d", 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) |
| return DRETF(false, "couldn't find an appropriate queue"); |
| |
| 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}; |
| std::vector<const char*> enabled_device_extensions{VK_FUCHSIA_BUFFER_COLLECTION_EXTENSION_NAME}; |
| 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_device_extensions.size()), |
| .ppEnabledExtensionNames = enabled_device_extensions.data(), |
| .pEnabledFeatures = nullptr}; |
| VkDevice vkdevice; |
| |
| if ((result = vkCreateDevice(physical_devices[0], &createInfo, |
| nullptr /* allocationcallbacks */, &vkdevice)) != VK_SUCCESS) |
| return DRETF(false, "vkCreateDevice failed: %d", result); |
| |
| 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_) { |
| return DRETF(false, "No vkCreateBufferCollectionFUCHSIA"); |
| } |
| |
| vkDestroyBufferCollectionFUCHSIA_ = reinterpret_cast<PFN_vkDestroyBufferCollectionFUCHSIA>( |
| vkGetDeviceProcAddr(vk_device_, "vkDestroyBufferCollectionFUCHSIA")); |
| if (!vkDestroyBufferCollectionFUCHSIA_) { |
| return DRETF(false, "No vkDestroyBufferCollectionFUCHSIA"); |
| } |
| |
| vkSetBufferCollectionConstraintsFUCHSIA_ = |
| reinterpret_cast<PFN_vkSetBufferCollectionConstraintsFUCHSIA>( |
| vkGetDeviceProcAddr(vk_device_, "vkSetBufferCollectionConstraintsFUCHSIA")); |
| if (!vkSetBufferCollectionConstraintsFUCHSIA_) { |
| return DRETF(false, "No vkSetBufferCollectionConstraintsFUCHSIA"); |
| } |
| |
| return true; |
| } |
| |
| bool VulkanTest::Exec(VkFormat format, uint32_t width, bool linear) |
| { |
| 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) { |
| return DRETF(false, "fdio_service_connect failed: %d", status); |
| } |
| fuchsia::sysmem::BufferCollectionTokenSyncPtr vulkan_token; |
| status = sysmem_allocator->AllocateSharedCollection(vulkan_token.NewRequest()); |
| if (status != ZX_OK) { |
| return DRETF(false, "AllocateSharedCollection failed: %d", status); |
| } |
| fuchsia::sysmem::BufferCollectionTokenSyncPtr local_token; |
| |
| status = |
| vulkan_token->Duplicate(std::numeric_limits<uint32_t>::max(), local_token.NewRequest()); |
| if (status != ZX_OK) { |
| return DRETF(false, "Duplicate failed: %d", status); |
| } |
| status = local_token->Sync(); |
| if (status != ZX_OK) { |
| return DRETF(false, "Sync failed: %d", status); |
| } |
| |
| VkImageCreateInfo image_create_info = { |
| .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, |
| .pNext = nullptr, |
| .flags = 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, |
| }; |
| |
| 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) { |
| fprintf(stderr, "Failed to import buffer collection: %d\n", result); |
| return false; |
| } |
| |
| result = vkSetBufferCollectionConstraintsFUCHSIA_(vk_device_, collection, &image_create_info); |
| |
| if (result != VK_SUCCESS) { |
| fprintf(stderr, "Failed to set buffer constraints: %d\n", result); |
| return false; |
| } |
| |
| fuchsia::sysmem::BufferCollectionSyncPtr sysmem_collection; |
| status = sysmem_allocator->BindSharedCollection(local_token, sysmem_collection.NewRequest()); |
| if (status != ZX_OK) { |
| return DRETF(false, "BindSharedCollection failed: %d", status); |
| } |
| fuchsia::sysmem::BufferCollectionConstraints constraints{}; |
| constraints.usage.vulkan = fuchsia::sysmem::vulkanUsageTransferDst; |
| status = sysmem_collection->SetConstraints(true, constraints); |
| if (status != ZX_OK) { |
| return DRETF(false, "SetConstraints failed: %d", status); |
| } |
| |
| 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 || allocation_status != ZX_OK) { |
| return DRETF(false, "WaitForBuffersAllocated failed: %d %d", status, allocation_status); |
| } |
| status = sysmem_collection->Close(); |
| if (status != ZX_OK) { |
| return DRETF(false, "Close failed: %d", status); |
| } |
| |
| fuchsia::sysmem::PixelFormat pixel_format = |
| buffer_collection_info.settings.image_format_constraints.pixel_format; |
| DLOG("Allocated format %d has_modifier %d modifier %lx\n", pixel_format.type, |
| pixel_format.has_format_modifier, pixel_format.format_modifier.value); |
| |
| fidl::Encoder encoder(fidl::Encoder::NO_HEADER); |
| encoder.Alloc(fidl::CodingTraits<fuchsia::sysmem::SingleBufferSettings>::encoded_size); |
| buffer_collection_info.settings.Encode(&encoder, 0); |
| std::vector<uint8_t> encoded_data = encoder.TakeBytes(); |
| |
| vkDestroyBufferCollectionFUCHSIA_(vk_device_, collection, nullptr); |
| |
| 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) |
| return DRETF(false, "vkCreateImage failed: %d", result); |
| |
| DLOG("image created"); |
| |
| if (linear) { |
| VkImageSubresource subresource = {.aspectMask = |
| format == VK_FORMAT_G8_B8R8_2PLANE_420_UNORM_KHR |
| ? 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 = format == VK_FORMAT_G8_B8R8_2PLANE_420_UNORM_KHR ? 1 : 4; |
| EXPECT_LE(min_bytes_per_pixel * width, layout.rowPitch); |
| EXPECT_LE(min_bytes_per_pixel * width * 64, layout.size); |
| } |
| |
| VkMemoryRequirements memory_reqs; |
| vkGetImageMemoryRequirements(vk_device_, vk_image_, &memory_reqs); |
| // Use first supported type |
| uint32_t memory_type = __builtin_ctz(memory_reqs.memoryTypeBits); |
| |
| VkImportMemoryFuchsiaHandleInfoKHR fuchsia_handle_info = { |
| .sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FUCHSIA_HANDLE_INFO_KHR, |
| .pNext = nullptr, |
| VK_EXTERNAL_MEMORY_HANDLE_TYPE_FUCHSIA_VMO_BIT_KHR, |
| buffer_collection_info.buffers[0].vmo.release()}; |
| |
| VkMemoryAllocateInfo alloc_info = { |
| .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, |
| .pNext = &fuchsia_handle_info, |
| .allocationSize = memory_reqs.size, |
| .memoryTypeIndex = memory_type, |
| }; |
| |
| VkDeviceMemory memory; |
| if ((result = vkAllocateMemory(vk_device_, &alloc_info, nullptr, &memory)) != VK_SUCCESS) { |
| return DRETF(false, "vkAllocateMemory failed"); |
| } |
| |
| result = vkBindImageMemory(vk_device_, vk_image_, memory, 0); |
| if (result != VK_SUCCESS) { |
| return DRETF(false, "vkBindImageMemory failed"); |
| } |
| |
| vkDestroyImage(vk_device_, vk_image_, nullptr); |
| |
| vkFreeMemory(vk_device_, memory, nullptr); |
| |
| DLOG("image destroyed"); |
| |
| return true; |
| } |
| |
| // Parameter is true if the image should be linear. |
| class VulkanExtensionTest : public ::testing::TestWithParam<bool> { |
| }; |
| |
| TEST_P(VulkanExtensionTest, BufferCollectionNV12) |
| { |
| VulkanTest test; |
| ASSERT_TRUE(test.Initialize()); |
| ASSERT_TRUE(test.Exec(VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, 64, GetParam())); |
| } |
| |
| TEST_P(VulkanExtensionTest, BufferCollectionNV12_1025) |
| { |
| VulkanTest test; |
| ASSERT_TRUE(test.Initialize()); |
| ASSERT_TRUE(test.Exec(VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, 1025, GetParam())); |
| } |
| |
| TEST_P(VulkanExtensionTest, BufferCollectionRGBA) |
| { |
| VulkanTest test; |
| ASSERT_TRUE(test.Initialize()); |
| ASSERT_TRUE(test.Exec(VK_FORMAT_R8G8B8A8_UNORM, 64, GetParam())); |
| } |
| |
| TEST_P(VulkanExtensionTest, BufferCollectionRGBA_1025) |
| { |
| VulkanTest test; |
| ASSERT_TRUE(test.Initialize()); |
| ASSERT_TRUE(test.Exec(VK_FORMAT_R8G8B8A8_UNORM, 1025, GetParam())); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(, VulkanExtensionTest, ::testing::Bool()); |
| |
| } // namespace |