|  | // Copyright 2020 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> | 
|  | #include <vulkan/vulkan.h> | 
|  |  | 
|  | static const char* kLayerName = "VK_LAYER_FUCHSIA_compact_image"; | 
|  |  | 
|  | // Note: the loader returns results based on the layer's manifest file, not the | 
|  | // implementation of the vkEnumerateInstanceExtensionProperties and | 
|  | // vkEnumerateDeviceExtensionProperties apis inside the layer. | 
|  |  | 
|  | static const std::vector<const char*> kLayers = {kLayerName}; | 
|  |  | 
|  | static const std::vector<const char*> kExpectedDeviceExtensions = { | 
|  | VK_FUCHSIA_COMPACT_IMAGE_EXTENSION_NAME}; | 
|  |  | 
|  | TEST(CompactImage, LayerApiVersion) { | 
|  | uint32_t prop_count = 0; | 
|  | EXPECT_EQ(VK_SUCCESS, vkEnumerateInstanceLayerProperties(&prop_count, nullptr)); | 
|  | EXPECT_GE(prop_count, kLayers.size()); | 
|  |  | 
|  | std::vector<VkLayerProperties> props(prop_count); | 
|  | EXPECT_EQ(VK_SUCCESS, vkEnumerateInstanceLayerProperties(&prop_count, props.data())); | 
|  | bool layer_found = false; | 
|  | const uint32_t kExpectedVersion = VK_MAKE_VERSION(1, 1, VK_HEADER_VERSION); | 
|  | for (uint32_t i = 0; i < prop_count; i++) { | 
|  | if (strcmp(props[i].layerName, kLayerName) == 0) { | 
|  | EXPECT_GE(kExpectedVersion, props[i].specVersion); | 
|  | layer_found = true; | 
|  | break; | 
|  | } | 
|  | } | 
|  | EXPECT_TRUE(layer_found); | 
|  | } | 
|  |  | 
|  | TEST(CompactImage, DeviceExtensions) { | 
|  | VkInstanceCreateInfo instance_info = { | 
|  | .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, | 
|  | .pNext = nullptr, | 
|  | .pApplicationInfo = nullptr, | 
|  | .enabledLayerCount = static_cast<uint32_t>(kLayers.size()), | 
|  | .ppEnabledLayerNames = kLayers.data(), | 
|  | .enabledExtensionCount = 0, | 
|  | .ppEnabledExtensionNames = nullptr, | 
|  | }; | 
|  | VkInstance instance; | 
|  |  | 
|  | ASSERT_EQ(VK_SUCCESS, vkCreateInstance(&instance_info, nullptr, &instance)); | 
|  |  | 
|  | uint32_t gpu_count; | 
|  | ASSERT_EQ(VK_SUCCESS, vkEnumeratePhysicalDevices(instance, &gpu_count, nullptr)); | 
|  | EXPECT_GE(gpu_count, 1u); | 
|  |  | 
|  | std::vector<VkPhysicalDevice> physical_devices(gpu_count); | 
|  | ASSERT_EQ(VK_SUCCESS, vkEnumeratePhysicalDevices(instance, &gpu_count, physical_devices.data())); | 
|  |  | 
|  | uint32_t prop_count; | 
|  | EXPECT_EQ(VK_SUCCESS, vkEnumerateDeviceExtensionProperties(physical_devices[0], kLayerName, | 
|  | &prop_count, nullptr)); | 
|  | EXPECT_EQ(prop_count, kExpectedDeviceExtensions.size()); | 
|  |  | 
|  | std::vector<VkExtensionProperties> props(prop_count); | 
|  | EXPECT_EQ(VK_SUCCESS, vkEnumerateDeviceExtensionProperties(physical_devices[0], kLayerName, | 
|  | &prop_count, props.data())); | 
|  | for (uint32_t i = 0; i < prop_count; i++) { | 
|  | EXPECT_STREQ(kExpectedDeviceExtensions[i], props[i].extensionName); | 
|  | } | 
|  |  | 
|  | 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, | 
|  | }; | 
|  | VkDeviceCreateInfo device_create_info = { | 
|  | .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, | 
|  | .pNext = nullptr, | 
|  | .queueCreateInfoCount = 1, | 
|  | .pQueueCreateInfos = &queue_create_info, | 
|  | .enabledLayerCount = 0, | 
|  | .ppEnabledLayerNames = nullptr, | 
|  | .enabledExtensionCount = static_cast<uint32_t>(kExpectedDeviceExtensions.size()), | 
|  | .ppEnabledExtensionNames = kExpectedDeviceExtensions.data(), | 
|  | .pEnabledFeatures = nullptr, | 
|  | }; | 
|  | VkDevice device; | 
|  | EXPECT_EQ(VK_SUCCESS, vkCreateDevice(physical_devices[0], &device_create_info, nullptr, &device)); | 
|  | } | 
|  |  | 
|  | TEST(CompactImage, TrimCompactImageDeviceMemoryFUCHSIA) { | 
|  | VkInstanceCreateInfo instance_info = { | 
|  | .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, | 
|  | .pNext = nullptr, | 
|  | .pApplicationInfo = nullptr, | 
|  | .enabledLayerCount = static_cast<uint32_t>(kLayers.size()), | 
|  | .ppEnabledLayerNames = kLayers.data(), | 
|  | .enabledExtensionCount = 0, | 
|  | .ppEnabledExtensionNames = nullptr, | 
|  | }; | 
|  | VkInstance instance; | 
|  |  | 
|  | ASSERT_EQ(VK_SUCCESS, vkCreateInstance(&instance_info, nullptr, &instance)); | 
|  |  | 
|  | uint32_t gpu_count; | 
|  | ASSERT_EQ(VK_SUCCESS, vkEnumeratePhysicalDevices(instance, &gpu_count, nullptr)); | 
|  | EXPECT_GE(gpu_count, 1u); | 
|  |  | 
|  | std::vector<VkPhysicalDevice> physical_devices(gpu_count); | 
|  | ASSERT_EQ(VK_SUCCESS, vkEnumeratePhysicalDevices(instance, &gpu_count, physical_devices.data())); | 
|  |  | 
|  | VkImageFormatProperties image_format_properties; | 
|  | VkResult result = vkGetPhysicalDeviceImageFormatProperties( | 
|  | physical_devices[0], VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, | 
|  | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, | 
|  | VK_IMAGE_CREATE_COMPACT_BIT_FUCHSIA, &image_format_properties); | 
|  | // End test if compact images are not supported by physical device. | 
|  | if (result == VK_ERROR_FORMAT_NOT_SUPPORTED) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | float queue_priorities[1] = {0.0}; | 
|  | uint32_t queue_family_index = 0; | 
|  | VkDeviceQueueCreateInfo queue_create_info = { | 
|  | .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, | 
|  | .pNext = nullptr, | 
|  | .flags = 0, | 
|  | .queueFamilyIndex = queue_family_index, | 
|  | .queueCount = 1, | 
|  | .pQueuePriorities = queue_priorities, | 
|  | }; | 
|  | VkDeviceCreateInfo device_create_info = { | 
|  | .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, | 
|  | .pNext = nullptr, | 
|  | .queueCreateInfoCount = 1, | 
|  | .pQueueCreateInfos = &queue_create_info, | 
|  | .enabledLayerCount = 0, | 
|  | .ppEnabledLayerNames = nullptr, | 
|  | .enabledExtensionCount = static_cast<uint32_t>(kExpectedDeviceExtensions.size()), | 
|  | .ppEnabledExtensionNames = kExpectedDeviceExtensions.data(), | 
|  | .pEnabledFeatures = nullptr, | 
|  | }; | 
|  | VkDevice device; | 
|  | EXPECT_EQ(VK_SUCCESS, vkCreateDevice(physical_devices[0], &device_create_info, nullptr, &device)); | 
|  |  | 
|  | PFN_vkTrimCompactImageDeviceMemoryFUCHSIA f_vkTrimCompactImageDeviceMemoryFUCHSIA = | 
|  | reinterpret_cast<PFN_vkTrimCompactImageDeviceMemoryFUCHSIA>( | 
|  | vkGetDeviceProcAddr(device, "vkTrimCompactImageDeviceMemoryFUCHSIA")); | 
|  | EXPECT_TRUE(f_vkTrimCompactImageDeviceMemoryFUCHSIA); | 
|  |  | 
|  | uint32_t width = 600; | 
|  | uint32_t height = 1024; | 
|  | VkImageCreateInfo image_create_info = { | 
|  | .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, | 
|  | .pNext = nullptr, | 
|  | .flags = VK_IMAGE_CREATE_COMPACT_BIT_FUCHSIA, | 
|  | .imageType = VK_IMAGE_TYPE_2D, | 
|  | .format = VK_FORMAT_R8G8B8A8_UNORM, | 
|  | .extent = VkExtent3D{width, height, 1}, | 
|  | .mipLevels = 1, | 
|  | .arrayLayers = 1, | 
|  | .samples = VK_SAMPLE_COUNT_1_BIT, | 
|  | .tiling = VK_IMAGE_TILING_OPTIMAL, | 
|  | .usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, | 
|  | .sharingMode = VK_SHARING_MODE_EXCLUSIVE, | 
|  | .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, | 
|  | }; | 
|  | VkImage image; | 
|  | EXPECT_EQ(VK_SUCCESS, vkCreateImage(device, &image_create_info, nullptr, &image)); | 
|  |  | 
|  | VkImageMemoryRequirementsInfo2 memory_requirements_info = { | 
|  | .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_REQUIREMENTS_INFO_2, | 
|  | .image = image, | 
|  | }; | 
|  | VkMemoryDedicatedRequirements memory_dedicated_requirements = { | 
|  | .sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS, | 
|  | .pNext = nullptr, | 
|  | }; | 
|  | VkMemoryRequirements2 memory_requirements = { | 
|  | .sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2, | 
|  | .pNext = &memory_dedicated_requirements, | 
|  | }; | 
|  | vkGetImageMemoryRequirements2(device, &memory_requirements_info, &memory_requirements); | 
|  | EXPECT_TRUE(memory_dedicated_requirements.prefersDedicatedAllocation); | 
|  |  | 
|  | VkPhysicalDeviceMemoryProperties memory_properties; | 
|  | vkGetPhysicalDeviceMemoryProperties(physical_devices[0], &memory_properties); | 
|  |  | 
|  | uint32_t image_memory_type_index = VK_MAX_MEMORY_TYPES; | 
|  | for (uint32_t i = 0; i < memory_properties.memoryTypeCount; i++) { | 
|  | if (memory_requirements.memoryRequirements.memoryTypeBits & (1 << i)) { | 
|  | image_memory_type_index = i; | 
|  | break; | 
|  | } | 
|  | } | 
|  | EXPECT_TRUE(image_memory_type_index != VK_MAX_MEMORY_TYPES); | 
|  |  | 
|  | VkMemoryDedicatedAllocateInfo image_memory_dedicated_allocate_info = { | 
|  | .sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO, | 
|  | .pNext = nullptr, | 
|  | .image = image, | 
|  | .buffer = VK_NULL_HANDLE, | 
|  | }; | 
|  | VkMemoryAllocateInfo image_memory_allocate_info = { | 
|  | .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, | 
|  | .pNext = &image_memory_dedicated_allocate_info, | 
|  | .allocationSize = memory_requirements.memoryRequirements.size, | 
|  | .memoryTypeIndex = image_memory_type_index, | 
|  | }; | 
|  |  | 
|  | VkDeviceMemory image_memory; | 
|  | EXPECT_EQ(VK_SUCCESS, vkAllocateMemory(device, &image_memory_allocate_info, 0, &image_memory)); | 
|  | EXPECT_EQ(VK_SUCCESS, vkBindImageMemory(device, image, image_memory, 0)); | 
|  |  | 
|  | // Buffer is used for both image upload and results. | 
|  | uint32_t buffer_size = width * height * 4; | 
|  | VkBufferCreateInfo buffer_create_info = { | 
|  | .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, | 
|  | .pNext = nullptr, | 
|  | .flags = 0, | 
|  | .size = buffer_size, | 
|  | .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT, | 
|  | .sharingMode = VK_SHARING_MODE_EXCLUSIVE, | 
|  | .queueFamilyIndexCount = 0, | 
|  | .pQueueFamilyIndices = nullptr, | 
|  | }; | 
|  |  | 
|  | VkBuffer buffer; | 
|  | EXPECT_EQ(VK_SUCCESS, vkCreateBuffer(device, &buffer_create_info, 0, &buffer)); | 
|  |  | 
|  | uint32_t buffer_memory_type_index = VK_MAX_MEMORY_TYPES; | 
|  | for (uint32_t i = 0; i < memory_properties.memoryTypeCount; i++) { | 
|  | VkMemoryPropertyFlags required_properties = | 
|  | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; | 
|  | if (required_properties & memory_properties.memoryTypes[i].propertyFlags) { | 
|  | buffer_memory_type_index = i; | 
|  | break; | 
|  | } | 
|  | } | 
|  | EXPECT_TRUE(buffer_memory_type_index != VK_MAX_MEMORY_TYPES); | 
|  |  | 
|  | VkMemoryAllocateInfo buffer_memory_allocate_info = { | 
|  | .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, | 
|  | .pNext = nullptr, | 
|  | .allocationSize = buffer_size, | 
|  | .memoryTypeIndex = buffer_memory_type_index, | 
|  | }; | 
|  |  | 
|  | VkDeviceMemory buffer_memory; | 
|  | EXPECT_EQ(VK_SUCCESS, vkAllocateMemory(device, &buffer_memory_allocate_info, 0, &buffer_memory)); | 
|  | EXPECT_EQ(VK_SUCCESS, vkBindBufferMemory(device, buffer, buffer_memory, 0)); | 
|  |  | 
|  | void* pData = nullptr; | 
|  | EXPECT_EQ(VK_SUCCESS, vkMapMemory(device, buffer_memory, 0, buffer_size, 0, &pData)); | 
|  |  | 
|  | VkCommandPoolCreateInfo command_pool_create_info = { | 
|  | .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, | 
|  | .pNext = nullptr, | 
|  | .flags = 0, | 
|  | .queueFamilyIndex = queue_family_index, | 
|  | }; | 
|  |  | 
|  | VkCommandPool command_pool; | 
|  | EXPECT_EQ(VK_SUCCESS, vkCreateCommandPool(device, &command_pool_create_info, 0, &command_pool)); | 
|  |  | 
|  | VkCommandBufferAllocateInfo command_buffer_allocate_info = { | 
|  | .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, | 
|  | .pNext = nullptr, | 
|  | .commandPool = command_pool, | 
|  | .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, | 
|  | .commandBufferCount = 1, | 
|  | }; | 
|  |  | 
|  | VkCommandBuffer command_buffer; | 
|  | EXPECT_EQ(VK_SUCCESS, | 
|  | vkAllocateCommandBuffers(device, &command_buffer_allocate_info, &command_buffer)); | 
|  |  | 
|  | VkCommandBufferBeginInfo command_buffer_begin_info = { | 
|  | .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, | 
|  | .pNext = nullptr, | 
|  | .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, | 
|  | .pInheritanceInfo = nullptr, | 
|  | }; | 
|  | EXPECT_EQ(VK_SUCCESS, vkBeginCommandBuffer(command_buffer, &command_buffer_begin_info)); | 
|  |  | 
|  | VkImageSubresourceRange subresource_range = { | 
|  | .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, | 
|  | .baseMipLevel = 0, | 
|  | .levelCount = 1, | 
|  | .baseArrayLayer = 0, | 
|  | .layerCount = 1, | 
|  | }; | 
|  | VkImageMemoryBarrier undefined_transfer_dst_image_memory_barrier = { | 
|  | .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, | 
|  | .pNext = nullptr, | 
|  | .srcAccessMask = 0, | 
|  | .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, | 
|  | .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, | 
|  | .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, | 
|  | .image = image, | 
|  | .subresourceRange = subresource_range, | 
|  | }; | 
|  | vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, | 
|  | VK_PIPELINE_STAGE_TRANSFER_BIT, VK_DEPENDENCY_BY_REGION_BIT, 0, nullptr, 0, | 
|  | nullptr, 1, &undefined_transfer_dst_image_memory_barrier); | 
|  |  | 
|  | // Linear gradient. | 
|  | for (uint32_t y = 0; y < height; ++y) { | 
|  | for (uint32_t x = 0; x < width; ++x) { | 
|  | reinterpret_cast<uint32_t*>(pData)[y * width + x] = 0xff0000ff | (x << 8); | 
|  | } | 
|  | } | 
|  |  | 
|  | VkImageSubresourceLayers subresource_layers = { | 
|  | .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, | 
|  | .mipLevel = 0, | 
|  | .baseArrayLayer = 0, | 
|  | .layerCount = 1, | 
|  | }; | 
|  | VkBufferImageCopy region = { | 
|  | .bufferOffset = 0, | 
|  | .bufferRowLength = width, | 
|  | .bufferImageHeight = height, | 
|  | .imageSubresource = subresource_layers, | 
|  | .imageOffset = VkOffset3D{0, 0, 0}, | 
|  | .imageExtent = VkExtent3D{width, height, 1}, | 
|  | }; | 
|  | vkCmdCopyBufferToImage(command_buffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, | 
|  | ®ion); | 
|  |  | 
|  | VkImageMemoryBarrier transfer_dst_transfer_src_image_memory_barrier = { | 
|  | .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, | 
|  | .pNext = nullptr, | 
|  | .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, | 
|  | .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, | 
|  | .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, | 
|  | .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, | 
|  | .image = image, | 
|  | .subresourceRange = subresource_range, | 
|  | }; | 
|  | vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, | 
|  | VK_PIPELINE_STAGE_TRANSFER_BIT, VK_DEPENDENCY_BY_REGION_BIT, 0, nullptr, 0, | 
|  | nullptr, 1, &transfer_dst_transfer_src_image_memory_barrier); | 
|  |  | 
|  | EXPECT_EQ(VK_SUCCESS, vkEndCommandBuffer(command_buffer)); | 
|  |  | 
|  | VkQueue queue; | 
|  | vkGetDeviceQueue(device, queue_family_index, 0, &queue); | 
|  |  | 
|  | VkSubmitInfo submit_info = { | 
|  | .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, | 
|  | .pNext = nullptr, | 
|  | .waitSemaphoreCount = 0, | 
|  | .pWaitSemaphores = nullptr, | 
|  | .pWaitDstStageMask = nullptr, | 
|  | .commandBufferCount = 1, | 
|  | .pCommandBuffers = &command_buffer, | 
|  | .signalSemaphoreCount = 0, | 
|  | .pSignalSemaphores = nullptr, | 
|  | }; | 
|  | EXPECT_EQ(VK_SUCCESS, vkQueueSubmit(queue, 1, &submit_info, 0)); | 
|  | EXPECT_EQ(VK_SUCCESS, vkQueueWaitIdle(queue)); | 
|  |  | 
|  | // Trim device memory to compact size. This will reduce the memory | 
|  | // committment to what is required for the current image layout. | 
|  | f_vkTrimCompactImageDeviceMemoryFUCHSIA(device, image, image_memory, 0); | 
|  | // TODO(reveman): Querying commitment for memory when | 
|  | // VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT is supported. | 
|  | } |