// Copyright 2013 The Flutter 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 "impeller/renderer/backend/vulkan/descriptor_pool_vk.h"

#include "flutter/fml/trace_event.h"
#include "impeller/base/allocation.h"
#include "impeller/base/validation.h"

namespace impeller {

DescriptorPoolVK::DescriptorPoolVK(
    const std::weak_ptr<const DeviceHolder>& device_holder)
    : device_holder_(device_holder) {
  FML_DCHECK(device_holder.lock());
}

DescriptorPoolVK::~DescriptorPoolVK() = default;

static vk::UniqueDescriptorPool CreatePool(const vk::Device& device,
                                           uint32_t pool_count) {
  TRACE_EVENT0("impeller", "CreateDescriptorPool");
  std::vector<vk::DescriptorPoolSize> pools = {
      {vk::DescriptorType::eCombinedImageSampler, pool_count},
      {vk::DescriptorType::eUniformBuffer, pool_count},
      {vk::DescriptorType::eStorageBuffer, pool_count}};

  vk::DescriptorPoolCreateInfo pool_info;
  pool_info.setMaxSets(pools.size() * pool_count);
  pool_info.setPoolSizes(pools);

  auto [result, pool] = device.createDescriptorPoolUnique(pool_info);
  if (result != vk::Result::eSuccess) {
    VALIDATION_LOG << "Unable to create a descriptor pool";
  }
  return std::move(pool);
}

std::optional<vk::DescriptorSet> DescriptorPoolVK::AllocateDescriptorSet(
    const vk::DescriptorSetLayout& layout,
    size_t command_count) {
  if (pools_.empty()) {
    pool_size_ = command_count;
  }
  return AllocateDescriptorSet(layout);
}

std::optional<vk::DescriptorSet> DescriptorPoolVK::AllocateDescriptorSet(
    const vk::DescriptorSetLayout& layout) {
  auto pool = GetDescriptorPool();
  if (!pool) {
    return std::nullopt;
  }
  vk::DescriptorSetAllocateInfo set_info;
  set_info.setDescriptorPool(pool.value());
  set_info.setSetLayouts(layout);
  std::shared_ptr<const DeviceHolder> strong_device = device_holder_.lock();
  if (!strong_device) {
    return std::nullopt;
  }
  auto [result, sets] =
      strong_device->GetDevice().allocateDescriptorSets(set_info);
  if (result == vk::Result::eErrorOutOfPoolMemory) {
    return GrowPool() ? AllocateDescriptorSet(layout) : std::nullopt;
  }
  if (result != vk::Result::eSuccess) {
    VALIDATION_LOG << "Could not allocate descriptor sets: "
                   << vk::to_string(result);
    return std::nullopt;
  }
  return sets[0];
}

std::optional<vk::DescriptorPool> DescriptorPoolVK::GetDescriptorPool() {
  if (pools_.empty()) {
    return GrowPool() ? GetDescriptorPool() : std::nullopt;
  }
  return *pools_.back();
}

bool DescriptorPoolVK::GrowPool() {
  const auto new_pool_size = Allocation::NextPowerOfTwoSize(pool_size_ + 1u);
  std::shared_ptr<const DeviceHolder> strong_device = device_holder_.lock();
  if (!strong_device) {
    return false;
  }
  auto new_pool = CreatePool(strong_device->GetDevice(), new_pool_size);
  if (!new_pool) {
    return false;
  }
  pool_size_ = new_pool_size;
  pools_.push(std::move(new_pool));
  return true;
}

}  // namespace impeller
