Decouple CompositorVk from DisplayVk

... so that Cuttlefish running without native swapchain support
but with ANGLE in the guest can still use CompositorVk for host
composition.

Bug: b/229145718
Test: `launch_cvd --gpu_mode=gfxstream`
Test: `launch_cvd --gpu_mode=gfxstream` with WIP ANGLE
Test: `cd device/generic/vulkan-cereal/build && ./Vulkan_unittests`
Test: `cd device/generic/vulkan-cereal/build && ninja test`
Test: `ctest --test-dir device/generic/vulkan-cereal/build`
Test: `atest --host gfxstream_compositorvk_test`
Change-Id: Idd124257a2f6b84a4c72bec52c179c1f09252692
diff --git a/stream-servers/vulkan/VkCommonOperations.cpp b/stream-servers/vulkan/VkCommonOperations.cpp
index 6f5808a..0f5ca0c 100644
--- a/stream-servers/vulkan/VkCommonOperations.cpp
+++ b/stream-servers/vulkan/VkCommonOperations.cpp
@@ -1123,6 +1123,7 @@
     INFO("    useDeferredCommands: %s", features->deferredCommands ? "true" : "false");
     INFO("    createResourceWithRequirements: %s",
          features->createResourceWithRequirements ? "true" : "false");
+    INFO("    useVulkanComposition: %s", features->useVulkanComposition ? "true" : "false");
     INFO("    useVulkanNativeSwapchain: %s", features->useVulkanNativeSwapchain ? "true" : "false");
     INFO("    enable guestRenderDoc: %s", features->guestRenderDoc ? "true" : "false");
     sVkEmulation->deviceInfo.glInteropSupported = features->glInteropSupported;
@@ -1130,6 +1131,15 @@
     sVkEmulation->useCreateResourcesWithRequirements = features->createResourceWithRequirements;
     sVkEmulation->guestRenderDoc = std::move(features->guestRenderDoc);
 
+    if (features->useVulkanComposition) {
+        if (sVkEmulation->compositorVk) {
+            ERR("Reset VkEmulation::compositorVk.");
+        }
+        sVkEmulation->compositorVk = CompositorVk::create(
+            *sVkEmulation->ivk, sVkEmulation->device, sVkEmulation->physdev, sVkEmulation->queue,
+            sVkEmulation->queueLock, sVkEmulation->queueFamilyIndex, 3);
+    }
+
     if (features->useVulkanNativeSwapchain) {
         if (sVkEmulation->displayVk) {
             ERR("Reset VkEmulation::displayVk.");
@@ -1152,6 +1162,7 @@
     // Don't try to tear down something that did not set up completely; too risky
     if (!sVkEmulation->live) return;
 
+    sVkEmulation->compositorVk.reset();
     sVkEmulation->displayVk.reset();
 
     freeExternalMemoryLocked(sVkEmulation->dvk, &sVkEmulation->staging.memory);
@@ -1666,6 +1677,8 @@
     }
 
     res.imageCreateInfoShallow = vk_make_orphan_copy(*imageCi);
+    res.currentLayout = res.imageCreateInfoShallow.initialLayout;
+    res.currentQueueFamilyIndex = sVkEmulation->queueFamilyIndex;
 
     vk->vkGetImageMemoryRequirements(sVkEmulation->device, res.image, &res.memReqs);
 
@@ -1716,6 +1729,36 @@
         return false;
     }
 
+    const VkImageViewCreateInfo imageViewCi = {
+        .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+        .pNext = nullptr,
+        .flags = 0,
+        .image = res.image,
+        .viewType = VK_IMAGE_VIEW_TYPE_2D,
+        .format = res.imageCreateInfoShallow.format,
+        .components =
+            {
+                .r = VK_COMPONENT_SWIZZLE_IDENTITY,
+                .g = VK_COMPONENT_SWIZZLE_IDENTITY,
+                .b = VK_COMPONENT_SWIZZLE_IDENTITY,
+                .a = VK_COMPONENT_SWIZZLE_IDENTITY,
+            },
+        .subresourceRange =
+            {
+                .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+                .baseMipLevel = 0,
+                .levelCount = 1,
+                .baseArrayLayer = 0,
+                .layerCount = 1,
+            },
+    };
+    createRes = vk->vkCreateImageView(sVkEmulation->device, &imageViewCi, nullptr, &res.imageView);
+    if (createRes != VK_SUCCESS) {
+        // LOG(VERBOSE) << "Failed to create Vulkan image for ColorBuffer "
+        //              << colorBufferHandle;
+        return false;
+    }
+
     if (sVkEmulation->instanceSupportsMoltenVK) {
         sVkEmulation->getMTLTextureFunc(res.image, &res.mtlTexture);
         if (!res.mtlTexture) {
@@ -1740,8 +1783,6 @@
     if (typeIndex) *typeIndex = res.memory.typeIndex;
     if (mappedPtr) *mappedPtr = res.memory.mappedPtr;
 
-    res.ownedByHost = std::make_shared<std::atomic_bool>(true);
-
     sVkEmulation->colorBuffers[colorBufferHandle] = res;
     return true;
 }
@@ -1762,6 +1803,7 @@
         android::base::AutoLock lock(*sVkEmulation->queueLock);
         VK_CHECK(vk->vkQueueWaitIdle(sVkEmulation->queue));
     }
+    vk->vkDestroyImageView(sVkEmulation->device, info.imageView, nullptr);
     vk->vkDestroyImage(sVkEmulation->device, info.image, nullptr);
     freeExternalMemoryLocked(vk, &info.memory);
 
@@ -2467,6 +2509,17 @@
     return res;
 }
 
+void setColorBufferCurrentLayout(uint32_t colorBufferHandle, VkImageLayout layout) {
+    AutoLock lock(sVkEmulationLock);
+
+    auto infoPtr = android::base::find(sVkEmulation->colorBuffers, colorBufferHandle);
+    if (!infoPtr) {
+        VK_COMMON_ERROR("Invalid ColorBuffer handle %d.", static_cast<int>(colorBufferHandle));
+        return;
+    }
+    infoPtr->currentLayout = layout;
+}
+
 // Allocate a ready to use VkCommandBuffer for queue transfer. The caller needs
 // to signal the returned VkFence when the VkCommandBuffer completes.
 static std::tuple<VkCommandBuffer, VkFence> allocateQueueTransferCommandBuffer_locked() {
@@ -2521,179 +2574,31 @@
     return std::make_tuple(commandBuffer, fence);
 }
 
-void acquireColorBuffersForHostComposing(const std::vector<uint32_t>& layerColorBuffers,
-                                         uint32_t renderTargetColorBuffer) {
-    if (!sVkEmulation || !sVkEmulation->live) {
-        GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "Host Vulkan device lost";
-    }
+const VkImageLayout kGuestUseDefaultImageLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
 
-    std::vector<std::tuple<uint32_t, VkImageLayout>> colorBuffersAndLayouts;
-    for (uint32_t layerColorBuffer : layerColorBuffers) {
-        colorBuffersAndLayouts.emplace_back(
-            layerColorBuffer, FrameBuffer::getFB()->getVkImageLayoutForComposeLayer());
-    }
-    colorBuffersAndLayouts.emplace_back(renderTargetColorBuffer,
-                                        VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
-    AutoLock lock(sVkEmulationLock);
-    auto vk = sVkEmulation->dvk;
-
-    std::vector<std::tuple<VkEmulation::ColorBufferInfo*, VkImageLayout>>
-        colorBufferInfosAndLayouts;
-    for (auto [colorBufferHandle, newLayout] : colorBuffersAndLayouts) {
-        VkEmulation::ColorBufferInfo* infoPtr =
-            android::base::find(sVkEmulation->colorBuffers, colorBufferHandle);
-        if (!infoPtr) {
-            VK_COMMON_ERROR("Invalid ColorBuffer handle %d.", static_cast<int>(colorBufferHandle));
-            continue;
-        }
-        colorBufferInfosAndLayouts.emplace_back(infoPtr, newLayout);
-    }
-
-    std::vector<VkImageMemoryBarrier> queueTransferBarriers;
-    std::stringstream transferredColorBuffers;
-    for (auto [infoPtr, _] : colorBufferInfosAndLayouts) {
-        if (infoPtr->ownedByHost->load()) {
-            VK_COMMON_VERBOSE("Skipping queue transfer from guest to host for ColorBuffer(id = %d)",
-                              static_cast<int>(infoPtr->handle));
-            continue;
-        }
-        VkImageMemoryBarrier queueTransferBarrier = {
-            .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
-            .pNext = nullptr,
-            .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT | VK_ACCESS_MEMORY_READ_BIT,
-            // VK_ACCESS_SHADER_READ_BIT for the compose layers, and VK_ACCESS_TRANSFER_READ_BIT for
-            // the render target/post source.
-            .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT,
-            .oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
-            .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
-            .srcQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL,
-            .dstQueueFamilyIndex = sVkEmulation->queueFamilyIndex,
-            .image = infoPtr->image,
-            .subresourceRange =
-                {
-                    .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
-                    .baseMipLevel = 0,
-                    .levelCount = 1,
-                    .baseArrayLayer = 0,
-                    .layerCount = 1,
-                },
-        };
-        queueTransferBarriers.emplace_back(queueTransferBarrier);
-        transferredColorBuffers << infoPtr->handle << " ";
-        infoPtr->ownedByHost->store(true);
-        infoPtr->currentLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
-    }
-
-    std::vector<VkImageMemoryBarrier> layoutTransitionBarriers;
-    for (auto [infoPtr, newLayout] : colorBufferInfosAndLayouts) {
-        if (newLayout == VK_IMAGE_LAYOUT_UNDEFINED || infoPtr->currentLayout == newLayout) {
-            continue;
-        }
-        VkImageMemoryBarrier layoutTransitionBarrier = {
-            .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
-            .pNext = nullptr,
-            .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT | VK_ACCESS_MEMORY_READ_BIT,
-            // VK_ACCESS_SHADER_READ_BIT for the compose layers, and VK_ACCESS_TRANSFER_READ_BIT for
-            // the render target/post source.
-            .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT,
-            .oldLayout = infoPtr->currentLayout,
-            .newLayout = newLayout,
-            .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
-            .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
-            .image = infoPtr->image,
-            .subresourceRange =
-                {
-                    .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
-                    .baseMipLevel = 0,
-                    .levelCount = 1,
-                    .baseArrayLayer = 0,
-                    .layerCount = 1,
-                },
-        };
-        layoutTransitionBarriers.emplace_back(layoutTransitionBarrier);
-        infoPtr->currentLayout = newLayout;
-    }
-
-    auto [commandBuffer, fence] = allocateQueueTransferCommandBuffer_locked();
-
-    VkCommandBufferBeginInfo beginInfo = {
-        .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
-        .pNext = nullptr,
-        .flags = 0,
-        .pInheritanceInfo = nullptr,
-    };
-    VK_CHECK(vk->vkBeginCommandBuffer(commandBuffer, &beginInfo));
-    if (!queueTransferBarriers.empty()) {
-        vk->vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
-                                 VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr,
-                                 static_cast<uint32_t>(queueTransferBarriers.size()),
-                                 queueTransferBarriers.data());
-    }
-    if (!layoutTransitionBarriers.empty()) {
-        vk->vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
-                                 VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr,
-                                 static_cast<uint32_t>(layoutTransitionBarriers.size()),
-                                 layoutTransitionBarriers.data());
-    }
-    VK_CHECK(vk->vkEndCommandBuffer(commandBuffer));
-
-    // We assume the host Vulkan compositor lives on the same queue, so we don't
-    // need to use semaphore to synchronize with the host compositor.
-    VkSubmitInfo submitInfo = {
-        .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
-        .pNext = nullptr,
-        .waitSemaphoreCount = 0,
-        .pWaitSemaphores = nullptr,
-        .pWaitDstStageMask = nullptr,
-        .commandBufferCount = 1,
-        .pCommandBuffers = &commandBuffer,
-        .signalSemaphoreCount = 0,
-        .pSignalSemaphores = nullptr,
-    };
-    {
-        std::stringstream ss;
-        ss << __func__
-           << ": submitting commands to issue acquire queue transfer from "
-              "guest to host for ColorBuffer("
-           << transferredColorBuffers.str() << ")";
-        AEMU_SCOPED_TRACE(ss.str().c_str());
-        android::base::AutoLock lock(*sVkEmulation->queueLock);
-        VK_CHECK(vk->vkQueueSubmit(sVkEmulation->queue, 1, &submitInfo, fence));
-    }
-}
-
-static VkFence doReleaseColorBufferForGuestRendering(
-    const std::vector<uint32_t>& colorBufferHandles) {
+void releaseColorBufferForGuestUse(uint32_t colorBufferHandle) {
     if (!sVkEmulation || !sVkEmulation->live) {
         GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "Host Vulkan device lost";
     }
 
     AutoLock lock(sVkEmulationLock);
-    auto vk = sVkEmulation->dvk;
 
-    std::stringstream transferredColorBuffers;
-    std::vector<VkImageMemoryBarrier> layoutTransitionBarriers;
-    std::vector<VkImageMemoryBarrier> queueTransferBarriers;
-    for (uint32_t colorBufferHandle : colorBufferHandles) {
-        auto infoPtr = android::base::find(sVkEmulation->colorBuffers, colorBufferHandle);
-        if (!infoPtr) {
-            VK_COMMON_ERROR("Invalid ColorBuffer handle %d.", static_cast<int>(colorBufferHandle));
-            continue;
-        }
-        if (!infoPtr->ownedByHost->load()) {
-            VK_COMMON_VERBOSE(
-                "Skipping queue transfer from host to guest for "
-                "ColorBuffer(id = %d)",
-                static_cast<int>(colorBufferHandle));
-            continue;
-        }
-        VkImageMemoryBarrier layoutTransitionBarrier = {
+    auto infoPtr = android::base::find(sVkEmulation->colorBuffers, colorBufferHandle);
+    if (!infoPtr) {
+        VK_COMMON_ERROR("Failed to find ColorBuffer handle %d.",
+                        static_cast<int>(colorBufferHandle));
+        return;
+    }
+
+    std::optional<VkImageMemoryBarrier> layoutTransitionBarrier;
+    if (infoPtr->currentLayout != kGuestUseDefaultImageLayout) {
+        layoutTransitionBarrier = VkImageMemoryBarrier{
             .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
             .pNext = nullptr,
             .srcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
             .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
             .oldLayout = infoPtr->currentLayout,
-            .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+            .newLayout = kGuestUseDefaultImageLayout,
             .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
             .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
             .image = infoPtr->image,
@@ -2706,17 +2611,19 @@
                     .layerCount = 1,
                 },
         };
-        layoutTransitionBarriers.emplace_back(layoutTransitionBarrier);
-        infoPtr->currentLayout = layoutTransitionBarrier.newLayout;
+        infoPtr->currentLayout = kGuestUseDefaultImageLayout;
+    }
 
-        VkImageMemoryBarrier queueTransferBarrier = {
+    std::optional<VkImageMemoryBarrier> queueTransferBarrier;
+    if (infoPtr->currentQueueFamilyIndex != VK_QUEUE_FAMILY_EXTERNAL) {
+        queueTransferBarrier = VkImageMemoryBarrier{
             .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
             .pNext = nullptr,
             .srcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
             .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
             .oldLayout = infoPtr->currentLayout,
             .newLayout = infoPtr->currentLayout,
-            .srcQueueFamilyIndex = sVkEmulation->queueFamilyIndex,
+            .srcQueueFamilyIndex = infoPtr->currentQueueFamilyIndex,
             .dstQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL,
             .image = infoPtr->image,
             .subresourceRange =
@@ -2728,33 +2635,40 @@
                     .layerCount = 1,
                 },
         };
-        queueTransferBarriers.emplace_back(queueTransferBarrier);
-        transferredColorBuffers << colorBufferHandle << " ";
-        infoPtr->ownedByHost->store(false);
+        infoPtr->currentQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL;
     }
 
+    if (!layoutTransitionBarrier && !queueTransferBarrier) {
+        return;
+    }
+
+    auto vk = sVkEmulation->dvk;
     auto [commandBuffer, fence] = allocateQueueTransferCommandBuffer_locked();
 
     VK_CHECK(vk->vkResetCommandBuffer(commandBuffer, 0));
-    VkCommandBufferBeginInfo beginInfo = {
+
+    const VkCommandBufferBeginInfo beginInfo = {
         .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
         .pNext = nullptr,
         .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
         .pInheritanceInfo = nullptr,
     };
     VK_CHECK(vk->vkBeginCommandBuffer(commandBuffer, &beginInfo));
-    vk->vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
-                             VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr,
-                             static_cast<uint32_t>(layoutTransitionBarriers.size()),
-                             layoutTransitionBarriers.data());
-    vk->vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
-                             VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr,
-                             static_cast<uint32_t>(queueTransferBarriers.size()),
-                             queueTransferBarriers.data());
+
+    if (layoutTransitionBarrier) {
+        vk->vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
+                                 VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1,
+                                 &layoutTransitionBarrier.value());
+    }
+    if (queueTransferBarrier) {
+        vk->vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
+                                 VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1,
+                                 &queueTransferBarrier.value());
+    }
+
     VK_CHECK(vk->vkEndCommandBuffer(commandBuffer));
-    // We assume the host Vulkan compositor lives on the same queue, so we don't
-    // need to use semaphore to synchronize with the host compositor.
-    VkSubmitInfo submitInfo = {
+
+    const VkSubmitInfo submitInfo = {
         .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
         .pNext = nullptr,
         .waitSemaphoreCount = 0,
@@ -2765,45 +2679,84 @@
         .signalSemaphoreCount = 0,
         .pSignalSemaphores = nullptr,
     };
-
     {
-        std::stringstream ss;
-        ss << __func__
-           << ": submitting commands to issue release queue transfer from host "
-              "to guest for ColorBuffer("
-           << transferredColorBuffers.str() << ")";
-        AEMU_SCOPED_TRACE(ss.str().c_str());
         android::base::AutoLock lock(*sVkEmulation->queueLock);
         VK_CHECK(vk->vkQueueSubmit(sVkEmulation->queue, 1, &submitInfo, fence));
     }
-    return fence;
-}
 
-void releaseColorBufferFromHostComposing(const std::vector<uint32_t>& colorBufferHandles) {
-    doReleaseColorBufferForGuestRendering(colorBufferHandles);
-}
-
-void releaseColorBufferFromHostComposingSync(const std::vector<uint32_t>& colorBufferHandles) {
-    VkFence fence = doReleaseColorBufferForGuestRendering(colorBufferHandles);
-    if (!sVkEmulation || !sVkEmulation->live) {
-        GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "Host Vulkan device lost";
-    }
-
-    AutoLock lock(sVkEmulationLock);
-    auto vk = sVkEmulation->dvk;
     static constexpr uint64_t ANB_MAX_WAIT_NS = 5ULL * 1000ULL * 1000ULL * 1000ULL;
     VK_CHECK(vk->vkWaitForFences(sVkEmulation->device, 1, &fence, VK_TRUE, ANB_MAX_WAIT_NS));
 }
 
-void setColorBufferCurrentLayout(uint32_t colorBufferHandle, VkImageLayout layout) {
+std::unique_ptr<BorrowedImageInfoVk> borrowColorBufferForComposition(uint32_t colorBufferHandle,
+                                                                     bool colorBufferIsTarget) {
     AutoLock lock(sVkEmulationLock);
 
-    auto infoPtr = android::base::find(sVkEmulation->colorBuffers, colorBufferHandle);
-    if (!infoPtr) {
+    auto colorBufferInfo = android::base::find(sVkEmulation->colorBuffers, colorBufferHandle);
+    if (!colorBufferInfo) {
         VK_COMMON_ERROR("Invalid ColorBuffer handle %d.", static_cast<int>(colorBufferHandle));
-        return;
+        return nullptr;
     }
-    infoPtr->currentLayout = layout;
+
+    auto compositorInfo = std::make_unique<BorrowedImageInfoVk>();
+    compositorInfo->id = colorBufferInfo->handle;
+    compositorInfo->width = colorBufferInfo->imageCreateInfoShallow.extent.width;
+    compositorInfo->height = colorBufferInfo->imageCreateInfoShallow.extent.height;
+    compositorInfo->image = colorBufferInfo->image;
+    compositorInfo->imageView = colorBufferInfo->imageView;
+    compositorInfo->imageCreateInfo = colorBufferInfo->imageCreateInfoShallow;
+    compositorInfo->preBorrowLayout = colorBufferInfo->currentLayout;
+    compositorInfo->preBorrowQueueFamilyIndex = colorBufferInfo->currentQueueFamilyIndex;
+    if (colorBufferIsTarget && sVkEmulation->displayVk) {
+        // Instruct the compositor to perform the layout transition after use so
+        // that it is ready to be blitted to the display.
+        compositorInfo->postBorrowQueueFamilyIndex = sVkEmulation->queueFamilyIndex;
+        compositorInfo->postBorrowLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+    } else {
+        // Instruct the compositor to perform the queue transfer release after use
+        // so that the color buffer can be acquired by the guest.
+        compositorInfo->postBorrowQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL;
+        compositorInfo->postBorrowLayout = colorBufferInfo->currentLayout;
+
+        if (compositorInfo->postBorrowLayout == VK_IMAGE_LAYOUT_UNDEFINED) {
+            compositorInfo->postBorrowLayout = kGuestUseDefaultImageLayout;
+        }
+    }
+
+    colorBufferInfo->currentLayout = compositorInfo->postBorrowLayout;
+    colorBufferInfo->currentQueueFamilyIndex = compositorInfo->postBorrowQueueFamilyIndex;
+
+    return compositorInfo;
+}
+
+std::unique_ptr<BorrowedImageInfoVk> borrowColorBufferForDisplay(uint32_t colorBufferHandle) {
+    AutoLock lock(sVkEmulationLock);
+
+    auto colorBufferInfo = android::base::find(sVkEmulation->colorBuffers, colorBufferHandle);
+    if (!colorBufferInfo) {
+        VK_COMMON_ERROR("Invalid ColorBuffer handle %d.", static_cast<int>(colorBufferHandle));
+        return nullptr;
+    }
+
+    auto compositorInfo = std::make_unique<BorrowedImageInfoVk>();
+    compositorInfo->id = colorBufferInfo->handle;
+    compositorInfo->width = colorBufferInfo->imageCreateInfoShallow.extent.width;
+    compositorInfo->height = colorBufferInfo->imageCreateInfoShallow.extent.height;
+    compositorInfo->image = colorBufferInfo->image;
+    compositorInfo->imageView = colorBufferInfo->imageView;
+    compositorInfo->imageCreateInfo = colorBufferInfo->imageCreateInfoShallow;
+    compositorInfo->preBorrowLayout = colorBufferInfo->currentLayout;
+    compositorInfo->preBorrowQueueFamilyIndex = sVkEmulation->queueFamilyIndex;
+
+    // Instruct the display to perform the queue transfer release after use so
+    // that the color buffer can be acquired by the guest.
+    compositorInfo->postBorrowQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL;
+    compositorInfo->postBorrowLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+
+    colorBufferInfo->currentLayout = compositorInfo->postBorrowLayout;
+    colorBufferInfo->currentQueueFamilyIndex = compositorInfo->postBorrowQueueFamilyIndex;
+
+    return compositorInfo;
 }
 
 }  // namespace goldfish_vk