//
// Copyright 2023 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// SecondaryCommandPool:
//    A class for allocating Command Buffers for VulkanSecondaryCommandBuffer.
//

#include "libANGLE/renderer/vulkan/SecondaryCommandPool.h"

#include "common/debug.h"
#include "libANGLE/renderer/vulkan/RendererVk.h"
#include "libANGLE/renderer/vulkan/vk_utils.h"

namespace rx
{
namespace vk
{

SecondaryCommandPool::SecondaryCommandPool() {}

SecondaryCommandPool::~SecondaryCommandPool()
{
    ASSERT(mCollectedBuffers.empty());
    ASSERT(mCollectedBuffersOverflow.empty());
}

angle::Result SecondaryCommandPool::init(Context *context,
                                         uint32_t queueFamilyIndex,
                                         ProtectionType protectionType)
{
    VkCommandPoolCreateInfo poolInfo = {};
    poolInfo.sType                   = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
    poolInfo.flags                   = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
    poolInfo.queueFamilyIndex        = queueFamilyIndex;
    if (context->getRenderer()->getFeatures().useResetCommandBufferBitForSecondaryPools.enabled)
    {
        poolInfo.flags |= VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
    }
    ASSERT(protectionType == ProtectionType::Unprotected ||
           protectionType == ProtectionType::Protected);
    if (protectionType == ProtectionType::Protected)
    {
        poolInfo.flags |= VK_COMMAND_POOL_CREATE_PROTECTED_BIT;
    }
    ANGLE_VK_TRY(context, mCommandPool.init(context->getDevice(), poolInfo));
    return angle::Result::Continue;
}

void SecondaryCommandPool::destroy(VkDevice device)
{
    // Command buffers will be destroyed with the Pool. Avoid possible slowdown during cleanup.
    mCollectedBuffers.clear();
    mCollectedBuffersOverflow.clear();
    mCommandPool.destroy(device);
}

angle::Result SecondaryCommandPool::allocate(Context *context, VulkanSecondaryCommandBuffer *buffer)
{
    ASSERT(valid());
    ASSERT(!buffer->valid());

    VkDevice device = context->getDevice();

    freeCollectedBuffers(device);

    VkCommandBufferAllocateInfo allocInfo = {};
    allocInfo.sType                       = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
    allocInfo.level                       = VK_COMMAND_BUFFER_LEVEL_SECONDARY;
    allocInfo.commandBufferCount          = 1;
    allocInfo.commandPool                 = mCommandPool.getHandle();

    ANGLE_VK_TRY(context, buffer->init(device, allocInfo));

    return angle::Result::Continue;
}

void SecondaryCommandPool::collect(VulkanSecondaryCommandBuffer *buffer)
{
    ASSERT(valid());
    ASSERT(buffer->valid());

    VkCommandBuffer bufferHandle = buffer->releaseHandle();

    if (!mCollectedBuffers.full())
    {
        mCollectedBuffers.push(bufferHandle);
    }
    else
    {
        std::lock_guard<std::mutex> lock(mOverflowMutex);
        mCollectedBuffersOverflow.emplace_back(bufferHandle);
        mHasOverflow.store(true, std::memory_order_relaxed);
    }
}

void SecondaryCommandPool::freeCollectedBuffers(VkDevice device)
{
    // Free Command Buffer for now. May later add recycling or reset/free pool at once.
    ANGLE_TRACE_EVENT0("gpu.angle", "SecondaryCommandPool::freeCollectedBuffers");

    while (!mCollectedBuffers.empty())
    {
        VkCommandBuffer bufferHandle = mCollectedBuffers.front();
        mCommandPool.freeCommandBuffers(device, 1, &bufferHandle);
        mCollectedBuffers.pop();
    }

    if (ANGLE_UNLIKELY(mHasOverflow.load(std::memory_order_relaxed)))
    {
        std::vector<VkCommandBuffer> buffers;
        {
            std::lock_guard<std::mutex> lock(mOverflowMutex);
            buffers = std::move(mCollectedBuffersOverflow);
            mHasOverflow.store(false, std::memory_order_relaxed);
        }
        for (VkCommandBuffer bufferHandle : buffers)
        {
            mCommandPool.freeCommandBuffers(device, 1, &bufferHandle);
        }
    }
}

}  // namespace vk
}  // namespace rx
