| // 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; | 
 | } |