//
// Copyright 2024 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.
//
// RefCountedEvent:
//    Manages reference count of VkEvent and its associated functions.
//

#include "libANGLE/renderer/vulkan/vk_ref_counted_event.h"
#include "libANGLE/renderer/vulkan/vk_helpers.h"
#include "libANGLE/renderer/vulkan/vk_renderer.h"

namespace rx
{
namespace vk
{
namespace
{
void DestroyRefCountedEvents(VkDevice device, RefCountedEventCollector &events)
{
    while (!events.empty())
    {
        events.back().destroy(device);
        events.pop_back();
    }
}
}  // namespace

bool RefCountedEvent::init(Context *context, EventStage eventStage)
{
    ASSERT(mHandle == nullptr);
    ASSERT(eventStage != EventStage::InvalidEnum);

    // First try with recycler. We must issue VkCmdResetEvent before VkCmdSetEvent
    if (context->getRefCountedEventsGarbageRecycler()->fetch(context->getRenderer(), this))
    {
        assertValidAndNoReference();
    }
    else
    {
        // If failed to fetch from recycler, then create a new event.
        mHandle                      = new RefCounted<EventAndStage>;
        VkEventCreateInfo createInfo = {};
        createInfo.sType             = VK_STRUCTURE_TYPE_EVENT_CREATE_INFO;
        // Use device only for performance reasons.
        createInfo.flags = context->getFeatures().supportsSynchronization2.enabled
                               ? VK_EVENT_CREATE_DEVICE_ONLY_BIT_KHR
                               : 0;
        VkResult result  = mHandle->get().event.init(context->getDevice(), createInfo);
        if (result != VK_SUCCESS)
        {
            WARN() << "event.init failed. Clean up garbage and retry again";
            // Proactively clean up garbage and retry
            context->getRefCountedEventsGarbageRecycler()->cleanup(context->getRenderer());
            result = mHandle->get().event.init(context->getDevice(), createInfo);
            if (result != VK_SUCCESS)
            {
                // Drivers usually can allocate huge amount of VkEvents, and we should never use
                // that many VkEvents under normal situation. If we failed to allocate, there is a
                // high chance that we may have a leak somewhere. This macro should help us catch
                // such potential bugs in the bots if that happens.
                ASSERT(false);
                // Ensure memory is freed and pointer is nulled
                SafeDelete(mHandle);
                // If still fail to create, we just return. An invalid event will trigger
                // pipelineBarrier code path
                return false;
            }
        }
    }

    mHandle->addRef();
    mHandle->get().eventStage = eventStage;
    return true;
}

void RefCountedEvent::release(Context *context)
{
    if (mHandle != nullptr)
    {
        releaseImpl(context->getRenderer(), context->getRefCountedEventsGarbageRecycler());
    }
}

void RefCountedEvent::release(Renderer *renderer)
{
    if (mHandle != nullptr)
    {
        releaseImpl(renderer, renderer->getRefCountedEventRecycler());
    }
}

template <typename RecyclerT>
void RefCountedEvent::releaseImpl(Renderer *renderer, RecyclerT *recycler)
{
    ASSERT(mHandle != nullptr);
    // This should never be called from async clean up thread since the refcount is not atomic. It
    // is expected only called under context share lock.
    ASSERT(std::this_thread::get_id() != renderer->getCleanUpThreadId());

    const bool isLastReference = mHandle->getAndReleaseRef() == 1;
    if (isLastReference)
    {
        ASSERT(recycler != nullptr);
        if (renderer->getFeatures().recycleVkEvent.enabled)
        {
            recycler->recycle(std::move(*this), renderer->getDevice());
        }
        else
        {
            destroy(renderer->getDevice());
        }
        ASSERT(mHandle == nullptr);
    }
    else
    {
        mHandle = nullptr;
    }
}

void RefCountedEvent::destroy(VkDevice device)
{
    assertValidAndNoReference();
    mHandle->get().event.destroy(device);
    SafeDelete(mHandle);
}

VkPipelineStageFlags RefCountedEvent::getPipelineStageMask(Renderer *renderer) const
{
    return renderer->getPipelineStageMask(getEventStage());
}

// RefCountedEventArray implementation.
void RefCountedEventArray::release(Renderer *renderer)
{
    for (EventStage eventStage : mBitMask)
    {
        ASSERT(mEvents[eventStage].valid());
        mEvents[eventStage].release(renderer);
    }
    mBitMask.reset();
}

void RefCountedEventArray::release(Context *context)
{
    for (EventStage eventStage : mBitMask)
    {
        ASSERT(mEvents[eventStage].valid());
        mEvents[eventStage].release(context);
    }
    mBitMask.reset();
}

void RefCountedEventArray::releaseToEventCollector(RefCountedEventCollector *eventCollector)
{
    for (EventStage eventStage : mBitMask)
    {
        eventCollector->emplace_back(std::move(mEvents[eventStage]));
    }
    mBitMask.reset();
}

bool RefCountedEventArray::initEventAtStage(Context *context, EventStage eventStage)
{
    if (mBitMask[eventStage])
    {
        return true;
    }

    // Create the event if we have not yet so. Otherwise just use the already created event.
    if (!mEvents[eventStage].init(context, eventStage))
    {
        return false;
    }
    mBitMask.set(eventStage);
    return true;
}

template <typename CommandBufferT>
void RefCountedEventArray::flushSetEvents(Renderer *renderer, CommandBufferT *commandBuffer) const
{
    for (EventStage eventStage : mBitMask)
    {
        VkPipelineStageFlags pipelineStageFlags = renderer->getPipelineStageMask(eventStage);
        commandBuffer->setEvent(mEvents[eventStage].getEvent().getHandle(), pipelineStageFlags);
    }
}

template void RefCountedEventArray::flushSetEvents<VulkanSecondaryCommandBuffer>(
    Renderer *renderer,
    VulkanSecondaryCommandBuffer *commandBuffer) const;
template void RefCountedEventArray::flushSetEvents<priv::SecondaryCommandBuffer>(
    Renderer *renderer,
    priv::SecondaryCommandBuffer *commandBuffer) const;
template void RefCountedEventArray::flushSetEvents<priv::CommandBuffer>(
    Renderer *renderer,
    priv::CommandBuffer *commandBuffer) const;

// EventArray implementation.
void EventArray::init(Renderer *renderer, const RefCountedEventArray &refCountedEventArray)
{
    mBitMask = refCountedEventArray.getBitMask();
    for (EventStage eventStage : mBitMask)
    {
        ASSERT(refCountedEventArray.getEvent(eventStage).valid());
        mEvents[eventStage] = refCountedEventArray.getEvent(eventStage).getEvent().getHandle();
        mPipelineStageFlags[eventStage] = renderer->getPipelineStageMask(eventStage);
    }
}

void EventArray::flushSetEvents(PrimaryCommandBuffer *primary)
{
    for (EventStage eventStage : mBitMask)
    {
        ASSERT(mEvents[eventStage] != VK_NULL_HANDLE);
        primary->setEvent(mEvents[eventStage], mPipelineStageFlags[eventStage]);
        mEvents[eventStage] = VK_NULL_HANDLE;
    }
    mBitMask.reset();
}

// RefCountedEventsGarbage implementation.
void RefCountedEventsGarbage::destroy(Renderer *renderer)
{
    ASSERT(renderer->hasQueueSerialFinished(mQueueSerial));
    while (!mRefCountedEvents.empty())
    {
        ASSERT(mRefCountedEvents.back().valid());
        mRefCountedEvents.back().release(renderer);
        mRefCountedEvents.pop_back();
    }
}

bool RefCountedEventsGarbage::releaseIfComplete(Renderer *renderer,
                                                RefCountedEventsGarbageRecycler *recycler)
{
    if (!renderer->hasQueueSerialFinished(mQueueSerial))
    {
        return false;
    }

    while (!mRefCountedEvents.empty())
    {
        ASSERT(mRefCountedEvents.back().valid());
        mRefCountedEvents.back().releaseImpl(renderer, recycler);
        ASSERT(!mRefCountedEvents.back().valid());
        mRefCountedEvents.pop_back();
    }
    return true;
}

bool RefCountedEventsGarbage::moveIfComplete(Renderer *renderer,
                                             std::deque<RefCountedEventCollector> *releasedBucket)
{
    if (!renderer->hasQueueSerialFinished(mQueueSerial))
    {
        return false;
    }

    releasedBucket->emplace_back(std::move(mRefCountedEvents));
    return true;
}

// RefCountedEventRecycler implementation.
void RefCountedEventRecycler::destroy(VkDevice device)
{
    std::lock_guard<angle::SimpleMutex> lock(mMutex);

    while (!mEventsToReset.empty())
    {
        DestroyRefCountedEvents(device, mEventsToReset.back());
        mEventsToReset.pop_back();
    }

    ASSERT(mResettingQueue.empty());

    while (!mEventsToReuse.empty())
    {
        DestroyRefCountedEvents(device, mEventsToReuse.back());
        mEventsToReuse.pop_back();
    }
}

void RefCountedEventRecycler::resetEvents(ErrorContext *context,
                                          const QueueSerial queueSerial,
                                          PrimaryCommandBuffer *commandbuffer)
{
    std::lock_guard<angle::SimpleMutex> lock(mMutex);

    if (mEventsToReset.empty())
    {
        return;
    }

    Renderer *renderer = context->getRenderer();
    while (!mEventsToReset.empty())
    {
        RefCountedEventCollector &events = mEventsToReset.back();
        ASSERT(!events.empty());
        for (const RefCountedEvent &refCountedEvent : events)
        {
            VkPipelineStageFlags stageMask = refCountedEvent.getPipelineStageMask(renderer);
            commandbuffer->resetEvent(refCountedEvent.getEvent().getHandle(), stageMask);
        }
        mResettingQueue.emplace(queueSerial, std::move(events));
        mEventsToReset.pop_back();
    }
}

size_t RefCountedEventRecycler::cleanupResettingEvents(Renderer *renderer)
{
    size_t eventsReleased = 0;
    std::lock_guard<angle::SimpleMutex> lock(mMutex);
    while (!mResettingQueue.empty())
    {
        bool released = mResettingQueue.front().moveIfComplete(renderer, &mEventsToReuse);
        if (released)
        {
            mResettingQueue.pop();
            ++eventsReleased;
        }
        else
        {
            break;
        }
    }
    return eventsReleased;
}

bool RefCountedEventRecycler::fetchEventsToReuse(RefCountedEventCollector *eventsToReuseOut)
{
    ASSERT(eventsToReuseOut != nullptr);
    ASSERT(eventsToReuseOut->empty());
    std::lock_guard<angle::SimpleMutex> lock(mMutex);
    if (mEventsToReuse.empty())
    {
        return false;
    }
    eventsToReuseOut->swap(mEventsToReuse.back());
    mEventsToReuse.pop_back();
    return true;
}

void RefCountedEventRecycler::recycle(RefCountedEventCollector &&garbageObjects, VkDevice device)
{
    ASSERT(!garbageObjects.empty());
    for (const RefCountedEvent &event : garbageObjects)
    {
        event.assertValidAndNoReference();
    }
    std::lock_guard<angle::SimpleMutex> lock(mMutex);
    if (mEventsToReset.size() >= kMaxEventToKeepCount)
    {
        DestroyRefCountedEvents(device, garbageObjects);
    }
    else
    {
        mEventsToReset.emplace_back(std::move(garbageObjects));
    }
}

// RefCountedEventsGarbageRecycler implementation.
RefCountedEventsGarbageRecycler::~RefCountedEventsGarbageRecycler()
{
    ASSERT(mEventsToReset.empty());
    ASSERT(mGarbageQueue.empty());
    ASSERT(mEventsToReuse.empty());
    ASSERT(mGarbageCount == 0);
}

void RefCountedEventsGarbageRecycler::destroy(Renderer *renderer)
{
    VkDevice device = renderer->getDevice();
    DestroyRefCountedEvents(device, mEventsToReset);
    ASSERT(mGarbageQueue.empty());
    ASSERT(mGarbageCount == 0);
    mEventsToReuse.destroy(device);
}

void RefCountedEventsGarbageRecycler::cleanup(Renderer *renderer)
{
    // First cleanup already completed events and add to mEventsToReset
    while (!mGarbageQueue.empty())
    {
        size_t count  = mGarbageQueue.front().size();
        bool released = mGarbageQueue.front().releaseIfComplete(renderer, this);
        if (released)
        {
            mGarbageCount -= count;
            mGarbageQueue.pop();
        }
        else
        {
            break;
        }
    }

    // Move mEventsToReset to the renderer so that it can be reset.
    if (!mEventsToReset.empty())
    {
        renderer->getRefCountedEventRecycler()->recycle(std::move(mEventsToReset),
                                                        renderer->getDevice());
    }
}

bool RefCountedEventsGarbageRecycler::fetch(Renderer *renderer, RefCountedEvent *outObject)
{
    if (mEventsToReuse.empty())
    {
        // Retrieve a list of ready to reuse events from renderer.
        RefCountedEventCollector events;
        if (!renderer->getRefCountedEventRecycler()->fetchEventsToReuse(&events))
        {
            return false;
        }
        mEventsToReuse.refill(std::move(events));
        ASSERT(!mEventsToReuse.empty());
    }
    mEventsToReuse.fetch(outObject);
    return true;
}

// EventBarrier implementation.
void EventBarrier::addDiagnosticsString(std::ostringstream &out) const
{
    if (mMemoryBarrierSrcAccess != 0 || mMemoryBarrierDstAccess != 0)
    {
        out << "Src: 0x" << std::hex << mMemoryBarrierSrcAccess << " &rarr; Dst: 0x" << std::hex
            << mMemoryBarrierDstAccess << std::endl;
    }
}

void EventBarrier::execute(PrimaryCommandBuffer *primary)
{
    if (isEmpty())
    {
        return;
    }
    ASSERT(mEvent != VK_NULL_HANDLE);
    ASSERT(mImageMemoryBarrierCount == 0 ||
           (mImageMemoryBarrierCount == 1 && mImageMemoryBarrier.image != VK_NULL_HANDLE));

    // Issue vkCmdWaitEvents call
    VkMemoryBarrier memoryBarrier = {};
    memoryBarrier.sType           = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
    memoryBarrier.srcAccessMask   = mMemoryBarrierSrcAccess;
    memoryBarrier.dstAccessMask   = mMemoryBarrierDstAccess;

    primary->waitEvents(1, &mEvent, mSrcStageMask, mDstStageMask, 1, &memoryBarrier, 0, nullptr,
                        mImageMemoryBarrierCount,
                        mImageMemoryBarrierCount == 0 ? nullptr : &mImageMemoryBarrier);
}

// EventBarrierArray implementation.
void EventBarrierArray::addAdditionalStageAccess(const RefCountedEvent &waitEvent,
                                                 VkPipelineStageFlags dstStageMask,
                                                 VkAccessFlags dstAccess)
{
    for (EventBarrier &barrier : mBarriers)
    {
        if (barrier.hasEvent(waitEvent.getEvent().getHandle()))
        {
            barrier.addAdditionalStageAccess(dstStageMask, dstAccess);
            return;
        }
    }
    UNREACHABLE();
}

void EventBarrierArray::addEventMemoryBarrier(Renderer *renderer,
                                              const RefCountedEvent &waitEvent,
                                              VkAccessFlags srcAccess,
                                              VkPipelineStageFlags dstStageMask,
                                              VkAccessFlags dstAccess)
{
    ASSERT(waitEvent.valid());
    VkPipelineStageFlags srcStageFlags = waitEvent.getPipelineStageMask(renderer);
    mBarriers.emplace_back(srcStageFlags, dstStageMask, srcAccess, dstAccess,
                           waitEvent.getEvent().getHandle());
}

void EventBarrierArray::addEventImageBarrier(Renderer *renderer,
                                             const RefCountedEvent &waitEvent,
                                             VkPipelineStageFlags dstStageMask,
                                             const VkImageMemoryBarrier &imageMemoryBarrier)
{
    ASSERT(waitEvent.valid());
    VkPipelineStageFlags srcStageFlags = waitEvent.getPipelineStageMask(renderer);
    mBarriers.emplace_back(srcStageFlags, dstStageMask, waitEvent.getEvent().getHandle(),
                           imageMemoryBarrier);
}

void EventBarrierArray::execute(Renderer *renderer, PrimaryCommandBuffer *primary)
{
    while (!mBarriers.empty())
    {
        mBarriers.back().execute(primary);
        mBarriers.pop_back();
    }
    reset();
}

void EventBarrierArray::addDiagnosticsString(std::ostringstream &out) const
{
    std::ostringstream eventStream;
    eventStream << "Event Barrier: ";
    for (const EventBarrier &barrier : mBarriers)
    {
        barrier.addDiagnosticsString(eventStream);
    }
    if (!mBarriers.empty())
    {
        out << eventStream.str() << "\\l";
    }
}
}  // namespace vk
}  // namespace rx
