// Copyright (c) 2016 The Chromium 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 "gpu/vulkan/vulkan_swap_chain.h"

#include "base/bind.h"
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "gpu/vulkan/vulkan_device_queue.h"
#include "gpu/vulkan/vulkan_fence_helper.h"
#include "gpu/vulkan/vulkan_function_pointers.h"

#if defined(USE_X11)
#include "ui/base/ui_base_features.h"  // nogncheck
#endif

namespace gpu {

namespace {

VkSemaphore CreateSemaphore(VkDevice vk_device) {
  // Generic semaphore creation structure.
  constexpr VkSemaphoreCreateInfo semaphore_create_info = {
      VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};

  VkSemaphore vk_semaphore = VK_NULL_HANDLE;
  auto result = vkCreateSemaphore(vk_device, &semaphore_create_info, nullptr,
                                  &vk_semaphore);
  LOG_IF(FATAL, VK_SUCCESS != result)
      << "vkCreateSemaphore() failed: " << result;
  return vk_semaphore;
}

}  // namespace

VulkanSwapChain::VulkanSwapChain() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
}

VulkanSwapChain::~VulkanSwapChain() {
#if DCHECK_IS_ON()
  base::AutoLock auto_lock(lock_);
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(images_.empty());
  DCHECK_EQ(static_cast<VkSwapchainKHR>(VK_NULL_HANDLE), swap_chain_);
#endif
}

bool VulkanSwapChain::Initialize(
    VulkanDeviceQueue* device_queue,
    VkSurfaceKHR surface,
    const VkSurfaceFormatKHR& surface_format,
    const gfx::Size& image_size,
    uint32_t min_image_count,
    VkImageUsageFlags image_usage_flags,
    VkSurfaceTransformFlagBitsKHR pre_transform,
    bool use_protected_memory,
    std::unique_ptr<VulkanSwapChain> old_swap_chain) {
  base::AutoLock auto_lock(lock_);
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(device_queue);
  DCHECK(!use_protected_memory || device_queue->allow_protected_memory());

  task_runner_ = base::ThreadTaskRunnerHandle::Get();
  use_protected_memory_ = use_protected_memory;
  device_queue_ = device_queue;
  is_incremental_present_supported_ =
      gfx::HasExtension(device_queue_->enabled_extensions(),
                        VK_KHR_INCREMENTAL_PRESENT_EXTENSION_NAME);
  device_queue_->GetFenceHelper()->ProcessCleanupTasks();
  return InitializeSwapChain(surface, surface_format, image_size,
                             min_image_count, image_usage_flags, pre_transform,
                             use_protected_memory, std::move(old_swap_chain)) &&
         InitializeSwapImages(surface_format) && AcquireNextImage();
}

void VulkanSwapChain::Destroy() {
  base::AutoLock auto_lock(lock_);
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  WaitUntilPostSubBufferAsyncFinished();

#if !defined(OS_FUCHSIA)
  if (UNLIKELY(!fence_and_semaphores_queue_.empty())) {
    VkDevice device = device_queue_->GetVulkanDevice();
    {
      // Make sure the last enqueued fence is passed, so we can release all
      // other fences and semaphores safely.
      base::ScopedBlockingCall scoped_blocking_call(
          FROM_HERE, base::BlockingType::MAY_BLOCK);
      // Use 1 second timeout for vkWaitForFences(), it should be long enough.
      constexpr auto kTimeout = base::TimeTicks::kNanosecondsPerSecond;
      auto result =
          vkWaitForFences(device, 1, &fence_and_semaphores_queue_.back().fence,
                          VK_TRUE, kTimeout);
      if (result != VK_SUCCESS)
        LOG(ERROR) << "vkWaitForFences() failed: " << result;
    }
    for (auto& fence_and_semaphores : fence_and_semaphores_queue_) {
      vkDestroyFence(device, fence_and_semaphores.fence,
                     nullptr /* pAllocator */);
      vkDestroySemaphore(device, fence_and_semaphores.semaphores[0],
                         nullptr /* pAllocator */);
      vkDestroySemaphore(device, fence_and_semaphores.semaphores[1],
                         nullptr /* pAllocator */);
    }
    fence_and_semaphores_queue_.clear();
  }
#endif  // !defined(OS_FUCHSIA)

  DCHECK(!is_writing_);
  DestroySwapImages();
  DestroySwapChain();
}

gfx::SwapResult VulkanSwapChain::PostSubBuffer(const gfx::Rect& rect) {
  base::AutoLock auto_lock(lock_);
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(!has_pending_post_sub_buffer_);

  if (UNLIKELY(!PresentBuffer(rect)))
    return gfx::SwapResult::SWAP_FAILED;

  if (UNLIKELY(!AcquireNextImage()))
    return gfx::SwapResult::SWAP_FAILED;

  return gfx::SwapResult::SWAP_ACK;
}

void VulkanSwapChain::PostSubBufferAsync(
    const gfx::Rect& rect,
    PostSubBufferCompletionCallback callback) {
  base::AutoLock auto_lock(lock_);
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(!has_pending_post_sub_buffer_);

  if (UNLIKELY(!PresentBuffer(rect))) {
    task_runner_->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(callback), gfx::SwapResult::SWAP_FAILED));
    return;
  }

  DCHECK_EQ(state_, VK_SUCCESS);

  has_pending_post_sub_buffer_ = true;
  post_sub_buffer_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(
          [](VulkanSwapChain* self, PostSubBufferCompletionCallback callback) {
            base::AutoLock auto_lock(self->lock_);
            DCHECK(self->has_pending_post_sub_buffer_);
            auto swap_result = self->AcquireNextImage()
                                   ? gfx::SwapResult::SWAP_ACK
                                   : gfx::SwapResult::SWAP_FAILED;
            self->task_runner_->PostTask(
                FROM_HERE, base::BindOnce(std::move(callback), swap_result));
            self->has_pending_post_sub_buffer_ = false;
            self->condition_variable_.Signal();
          },
          base::Unretained(this), std::move(callback)));
}

bool VulkanSwapChain::InitializeSwapChain(
    VkSurfaceKHR surface,
    const VkSurfaceFormatKHR& surface_format,
    const gfx::Size& image_size,
    uint32_t min_image_count,
    VkImageUsageFlags image_usage_flags,
    VkSurfaceTransformFlagBitsKHR pre_transform,
    bool use_protected_memory,
    std::unique_ptr<VulkanSwapChain> old_swap_chain) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  VkDevice device = device_queue_->GetVulkanDevice();
  VkResult result = VK_SUCCESS;

  VkSwapchainCreateInfoKHR swap_chain_create_info = { VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR};
  swap_chain_create_info.flags = use_protected_memory ? VK_SWAPCHAIN_CREATE_PROTECTED_BIT_KHR : 0;
  swap_chain_create_info.surface = surface;
  swap_chain_create_info.minImageCount = min_image_count;
  swap_chain_create_info.imageFormat = surface_format.format;
  swap_chain_create_info.imageColorSpace = surface_format.colorSpace;
  swap_chain_create_info.imageExtent = {(uint32_t)image_size.width(), (uint32_t)image_size.height()};
  swap_chain_create_info.imageArrayLayers = 1;
  swap_chain_create_info.imageUsage = image_usage_flags;
  swap_chain_create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
  swap_chain_create_info.preTransform = pre_transform;
  swap_chain_create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
  swap_chain_create_info.presentMode = VK_PRESENT_MODE_FIFO_KHR;
  swap_chain_create_info.clipped = VK_TRUE;
  swap_chain_create_info.oldSwapchain = VK_NULL_HANDLE;

  if (LIKELY(old_swap_chain)) {
    base::AutoLock auto_lock(old_swap_chain->lock_);
    old_swap_chain->WaitUntilPostSubBufferAsyncFinished();
    swap_chain_create_info.oldSwapchain = old_swap_chain->swap_chain_;
    // Reuse |post_sub_buffer_task_runner_| and |fence_and_semaphores_queue_|
    // from the |old_swap_chain|.
    post_sub_buffer_task_runner_ = old_swap_chain->post_sub_buffer_task_runner_;
#if !defined(OS_FUCHSIA)
    fence_and_semaphores_queue_ =
        std::move(old_swap_chain->fence_and_semaphores_queue_);
    old_swap_chain->fence_and_semaphores_queue_.clear();
#endif  // !defined(OS_FUCHSIA)
  }

  VkSwapchainKHR new_swap_chain = VK_NULL_HANDLE;
  result = vkCreateSwapchainKHR(device, &swap_chain_create_info, nullptr,
                                &new_swap_chain);

  if (LIKELY(old_swap_chain)) {
    auto* fence_helper = device_queue_->GetFenceHelper();
    fence_helper->EnqueueVulkanObjectCleanupForSubmittedWork(
        std::move(old_swap_chain));
  }

  if (UNLIKELY(VK_SUCCESS != result)) {
    LOG(FATAL) << "vkCreateSwapchainKHR() failed: " << result;
    return false;
  }

  swap_chain_ = new_swap_chain;
  size_ = gfx::Size(swap_chain_create_info.imageExtent.width,
                    swap_chain_create_info.imageExtent.height);

  if (UNLIKELY(!post_sub_buffer_task_runner_)) {
    post_sub_buffer_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
        {base::TaskPriority::USER_BLOCKING,
         base::TaskShutdownBehavior::BLOCK_SHUTDOWN, base::MayBlock()});
  }

  return true;
}

void VulkanSwapChain::DestroySwapChain() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  VkDevice device = device_queue_->GetVulkanDevice();
  vkDestroySwapchainKHR(device, swap_chain_, nullptr /* pAllocator */);
  swap_chain_ = VK_NULL_HANDLE;
}

bool VulkanSwapChain::InitializeSwapImages(
    const VkSurfaceFormatKHR& surface_format) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  VkDevice device = device_queue_->GetVulkanDevice();
  VkResult result = VK_SUCCESS;

  uint32_t image_count = 0;
  result = vkGetSwapchainImagesKHR(device, swap_chain_, &image_count, nullptr);
  if (UNLIKELY(VK_SUCCESS != result)) {
    LOG(FATAL) << "vkGetSwapchainImagesKHR(nullptr) failed: " << result;
    return false;
  }

  std::vector<VkImage> images(image_count);
  result =
      vkGetSwapchainImagesKHR(device, swap_chain_, &image_count, images.data());
  if (UNLIKELY(VK_SUCCESS != result)) {
    LOG(FATAL) << "vkGetSwapchainImagesKHR(images) failed: " << result;
    return false;
  }

  images_.resize(image_count);
  for (uint32_t i = 0; i < image_count; ++i) {
    auto& image_data = images_[i];
    image_data.image = images[i];
  }
  return true;
}

void VulkanSwapChain::DestroySwapImages() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  VkDevice device = device_queue_->GetVulkanDevice();
  for (auto& image : images_) {
    vkDestroySemaphore(device, image.acquire_semaphore,
                       nullptr /* pAllocator */);
    vkDestroySemaphore(device, image.present_semaphore,
                       nullptr /* pAllocator */);
  }
  images_.clear();
}

bool VulkanSwapChain::BeginWriteCurrentImage(VkImage* image,
                                             uint32_t* image_index,
                                             VkImageLayout* image_layout,
                                             VkSemaphore* begin_semaphore,
                                             VkSemaphore* end_semaphore) {
  base::AutoLock auto_lock(lock_);
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(image);
  DCHECK(image_index);
  DCHECK(image_layout);
  DCHECK(begin_semaphore);
  DCHECK(end_semaphore);
  DCHECK(!is_writing_);

  if (UNLIKELY(state_ != VK_SUCCESS))
    return false;

  if (UNLIKELY(!acquired_image_))
    return false;

  auto& current_image_data = images_[*acquired_image_];

  if (UNLIKELY(!new_acquired_)) {
    // In this case, {Begin,End}WriteCurrentImage has been called, but
    // PostSubBuffer() is not call, so |acquire_semaphore| has been wait on for
    // the previous write request, release it with FenceHelper.
    device_queue_->GetFenceHelper()->EnqueueSemaphoreCleanupForSubmittedWork(
        current_image_data.acquire_semaphore);
    // Use |end_semaphore| from previous write as |begin_semaphore| for the new
    // write request, and create a new semaphore for |end_semaphore|.
    current_image_data.acquire_semaphore = current_image_data.present_semaphore;
    current_image_data.present_semaphore =
        CreateSemaphore(device_queue_->GetVulkanDevice());
    if (UNLIKELY(current_image_data.present_semaphore == VK_NULL_HANDLE))
      return false;
  }

  *image = current_image_data.image;
  *image_index = *acquired_image_;
  *image_layout = current_image_data.image_layout;
  *begin_semaphore = current_image_data.acquire_semaphore;
  *end_semaphore = current_image_data.present_semaphore;
  is_writing_ = true;

  return true;
}

void VulkanSwapChain::EndWriteCurrentImage() {
  base::AutoLock auto_lock(lock_);
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(is_writing_);
  DCHECK(acquired_image_);

  auto& current_image_data = images_[*acquired_image_];
  current_image_data.image_layout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
  is_writing_ = false;
  new_acquired_ = false;
}

bool VulkanSwapChain::PresentBuffer(const gfx::Rect& rect) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK_EQ(state_, VK_SUCCESS);
  DCHECK(acquired_image_);

  auto& current_image_data = images_[*acquired_image_];
  DCHECK(current_image_data.present_semaphore != VK_NULL_HANDLE);

  VkRectLayerKHR rect_layer = {
      /* .offset = */ {rect.x(), rect.y()},
      /* .extent = */ {rect.width(), rect.height()},
      /* .layer = */ 0,
  };

  VkPresentRegionKHR present_region = {
      /* .rectangleCount = */ 1,
      /* .pRectangles = */ &rect_layer,
  };

  VkPresentRegionsKHR present_regions;
  present_regions.sType = VK_STRUCTURE_TYPE_PRESENT_REGIONS_KHR;
  present_regions.swapchainCount = 1;
  present_regions.pRegions = &present_region;

  VkPresentInfoKHR present_info;
  present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
  present_info.pNext = is_incremental_present_supported_ ? &present_regions : nullptr;
  present_info.waitSemaphoreCount = 1;
  present_info.pWaitSemaphores = &current_image_data.present_semaphore;
  present_info.swapchainCount = 1;
  present_info.pSwapchains = &swap_chain_;
  present_info.pImageIndices = &acquired_image_.value();

  VkQueue queue = device_queue_->GetVulkanQueue();
  auto result = vkQueuePresentKHR(queue, &present_info);
  if (UNLIKELY(result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR)) {
    LOG(DFATAL) << "vkQueuePresentKHR() failed: " << result;
    state_ = result;
    return false;
  }

  LOG_IF(ERROR, result == VK_SUBOPTIMAL_KHR) << "Swapchian is suboptimal.";
  acquired_image_.reset();

  return true;
}

bool VulkanSwapChain::AcquireNextImage() {
  DCHECK_EQ(state_, VK_SUCCESS);
  DCHECK(!acquired_image_);

#if defined(USE_X11)
  // The xserver should still composite windows with a 1Hz fake vblank when
  // screen is off or the window is offscreen. However there is an xserver
  // bug, the requested hardware vblanks are lost, when screen turns off, so
  // FIFO swapchain will hang.
  // Workaround the issue by using the 2 seconds timeout for
  // vkAcquireNextImageKHR(). When timeout happens, we consider the swapchain
  // hang happened, and then make the surface lost, so a new swapchain will
  // be recreated.
  //
  // TODO(https://crbug.com/1098237): set correct timeout for ozone/x11.
  static uint64_t kTimeout = features::IsUsingOzonePlatform()
                                 ? UINT64_MAX
                                 : base::Time::kNanosecondsPerSecond * 2;
#else
  static uint64_t kTimeout = UINT64_MAX;
#endif

  // VulkanDeviceQueue is not threadsafe for now, but |device_queue_| will not
  // be released, and device_queue_->device will never be changed after
  // initialization, so it is safe for now.
  // TODO(penghuang): make VulkanDeviceQueue threadsafe.
  VkDevice device = device_queue_->GetVulkanDevice();
  auto fence_and_semaphores = GetOrCreateFenceAndSemaphores();
  if (UNLIKELY(fence_and_semaphores.semaphores[0] == VK_NULL_HANDLE)) {
#if !defined(OS_FUCHSIA)
    DCHECK(fence_and_semaphores.fence == VK_NULL_HANDLE);
#endif  // !defined(OS_FUCHSIA)
    DCHECK(fence_and_semaphores.semaphores[1] == VK_NULL_HANDLE);
    return false;
  }

  DCHECK(fence_and_semaphores.semaphores[0] != VK_NULL_HANDLE);
  DCHECK(fence_and_semaphores.semaphores[1] != VK_NULL_HANDLE);

  VkFence acquire_fence = fence_and_semaphores.fence;
  VkSemaphore acquire_semaphore = fence_and_semaphores.semaphores[0];
  VkSemaphore present_semaphore = fence_and_semaphores.semaphores[1];
  uint32_t next_image;
  VkResult result;
  {
    base::ScopedBlockingCall scoped_blocking_call(
        FROM_HERE, base::BlockingType::MAY_BLOCK);
    result = vkAcquireNextImageKHR(device, swap_chain_, kTimeout,
                                   acquire_semaphore, acquire_fence, &next_image);
  }

  if (UNLIKELY(result == VK_TIMEOUT)) {
    LOG(ERROR) << "vkAcquireNextImageKHR() hangs.";
    vkDestroySemaphore(device, acquire_semaphore, nullptr);
    vkDestroySemaphore(device, present_semaphore, nullptr);
    vkDestroyFence(device, acquire_fence, nullptr);
    state_ = VK_ERROR_SURFACE_LOST_KHR;
    return false;
  }

  if (UNLIKELY(result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR)) {
    LOG(DFATAL) << "vkAcquireNextImageKHR() failed: " << result;
    vkDestroySemaphore(device, acquire_semaphore, nullptr);
    vkDestroySemaphore(device, present_semaphore, nullptr);
    vkDestroyFence(device, acquire_fence, nullptr);
    state_ = result;
    return false;
  }

  acquired_image_.emplace(next_image);
  new_acquired_ = true;

  // For the previous use of the image, |current_image_data.acquire_semaphore|
  // has been wait on for the compositing work last time,
  // and |current_image_data.present_semaphore| has been wait on by present
  // engine for presenting the image last time, so those two semaphores should
  // be free for reusing, when the |acquire_fence| is passed.
  auto& current_image_data = images_[next_image];
  ReturnFenceAndSemaphores({acquire_fence,
                            {current_image_data.acquire_semaphore,
                             current_image_data.present_semaphore}});
  current_image_data.acquire_semaphore = acquire_semaphore;
  current_image_data.present_semaphore = present_semaphore;

  return true;
}

void VulkanSwapChain::WaitUntilPostSubBufferAsyncFinished() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  while (has_pending_post_sub_buffer_) {
    base::ScopedBlockingCall scoped_blocking_call(
        FROM_HERE, base::BlockingType::WILL_BLOCK);
    condition_variable_.Wait();
  }
  DCHECK(acquired_image_ || state_ != VK_SUCCESS);
}

VulkanSwapChain::FenceAndSemaphores
VulkanSwapChain::GetOrCreateFenceAndSemaphores() {
  VkDevice device = device_queue_->GetVulkanDevice();
  FenceAndSemaphores fence_and_semaphores;
  do {
#if !defined(OS_FUCHSIA)
    if (LIKELY(!fence_and_semaphores_queue_.empty())) {
      fence_and_semaphores = fence_and_semaphores_queue_.front();
      auto result = vkGetFenceStatus(device, fence_and_semaphores.fence);
      if (LIKELY(result == VK_SUCCESS)) {
        fence_and_semaphores_queue_.pop_front();
        vkResetFences(device, 1, &fence_and_semaphores.fence);
      } else if (LIKELY(result == VK_NOT_READY)) {
        // If fence is not passed, new fence and semaphores will be created.
        fence_and_semaphores = {};
      } else {
        DLOG(ERROR) << "vkGetFenceStatus() failed: " << result;
        fence_and_semaphores = {};
        break;
      }
    }

    if (UNLIKELY(fence_and_semaphores.fence == VK_NULL_HANDLE)) {
      constexpr VkFenceCreateInfo fence_create_info = {
          /* .sType = */ VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
      };
      auto result =
          vkCreateFence(device, &fence_create_info,
                        /*pAllocator=*/nullptr, &fence_and_semaphores.fence);
      if (result != VK_SUCCESS) {
        DLOG(ERROR) << "vkCreateFence() failed: " << result;
        break;
      }
    }
#endif  // !defined(OS_FUCHSIA)

    if (UNLIKELY(fence_and_semaphores.semaphores[0] == VK_NULL_HANDLE))
      fence_and_semaphores.semaphores[0] = CreateSemaphore(device);
    if (UNLIKELY(fence_and_semaphores.semaphores[1] == VK_NULL_HANDLE))
      fence_and_semaphores.semaphores[1] = CreateSemaphore(device);

    if (UNLIKELY(fence_and_semaphores.semaphores[0] == VK_NULL_HANDLE ||
                 fence_and_semaphores.semaphores[1] == VK_NULL_HANDLE)) {
      break;
    }

    return fence_and_semaphores;
  } while (false);

  // Failed to get or create fence and semaphores, release resources.
  vkDestroyFence(device, fence_and_semaphores.fence, nullptr /* pAllocator */);
  vkDestroySemaphore(device, fence_and_semaphores.semaphores[0],
                     nullptr /* pAllocator */);
  vkDestroySemaphore(device, fence_and_semaphores.semaphores[1],
                     nullptr /* pAllocator */);
  return {};
}

void VulkanSwapChain::ReturnFenceAndSemaphores(
    const FenceAndSemaphores& fence_and_semaphores) {
#if defined(OS_FUCHSIA)
  VkDevice device = device_queue_->GetVulkanDevice();
  DCHECK(fence_and_semaphores.fence == VK_NULL_HANDLE);
  vkDestroySemaphore(device, fence_and_semaphores.semaphores[0],
                     nullptr /* pAllocator */);
  vkDestroySemaphore(device, fence_and_semaphores.semaphores[1],
                     nullptr /* pAllocator */);
#else
  DCHECK(fence_and_semaphores.fence != VK_NULL_HANDLE);
  fence_and_semaphores_queue_.push_back(fence_and_semaphores);
#endif
}

VulkanSwapChain::ScopedWrite::ScopedWrite(VulkanSwapChain* swap_chain)
    : swap_chain_(swap_chain) {
  success_ = swap_chain_->BeginWriteCurrentImage(
      &image_, &image_index_, &image_layout_, &begin_semaphore_,
      &end_semaphore_);
  if (LIKELY(success_)) {
    DCHECK(begin_semaphore_ != VK_NULL_HANDLE);
    DCHECK(end_semaphore_ != VK_NULL_HANDLE);
  } else {
    DCHECK(begin_semaphore_ == VK_NULL_HANDLE);
    DCHECK(end_semaphore_ == VK_NULL_HANDLE);
  }
}

VulkanSwapChain::ScopedWrite::~ScopedWrite() {
  if (LIKELY(success_)) {
    DCHECK(begin_semaphore_ != VK_NULL_HANDLE);
    DCHECK(end_semaphore_ != VK_NULL_HANDLE);
    swap_chain_->EndWriteCurrentImage();
  } else {
    DCHECK(begin_semaphore_ == VK_NULL_HANDLE);
    DCHECK(end_semaphore_ == VK_NULL_HANDLE);
  }
}

}  // namespace gpu
