// Copyright 2019 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.
//
// SemaphoreVk.cpp: Defines the class interface for SemaphoreVk, implementing
// SemaphoreImpl.

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

#include "common/debug.h"
#include "libANGLE/Context.h"
#include "libANGLE/renderer/vulkan/BufferVk.h"
#include "libANGLE/renderer/vulkan/ContextVk.h"
#include "libANGLE/renderer/vulkan/RendererVk.h"
#include "libANGLE/renderer/vulkan/TextureVk.h"

namespace rx
{

namespace
{
vk::ImageLayout GetVulkanImageLayout(GLenum layout)
{
    switch (layout)
    {
        case GL_NONE:
            return vk::ImageLayout::Undefined;
        case GL_LAYOUT_GENERAL_EXT:
            return vk::ImageLayout::ExternalShadersWrite;
        case GL_LAYOUT_COLOR_ATTACHMENT_EXT:
            return vk::ImageLayout::ColorAttachment;
        case GL_LAYOUT_DEPTH_STENCIL_ATTACHMENT_EXT:
        case GL_LAYOUT_DEPTH_STENCIL_READ_ONLY_EXT:
        case GL_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_EXT:
        case GL_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_EXT:
            // Note: once VK_KHR_separate_depth_stencil_layouts becomes core or ubiquitous, we
            // should optimize depth/stencil image layout transitions to only be performed on the
            // aspect that needs transition.  In that case, these four layouts can be distinguished
            // and optimized.  Note that the exact equivalent of these layouts are specified in
            // VK_KHR_maintenance2, which are also usable, granted we transition the pair of
            // depth/stencil layouts accordingly elsewhere in ANGLE.
            return vk::ImageLayout::DepthStencilAttachment;
        case GL_LAYOUT_SHADER_READ_ONLY_EXT:
            return vk::ImageLayout::ExternalShadersReadOnly;
        case GL_LAYOUT_TRANSFER_SRC_EXT:
            return vk::ImageLayout::TransferSrc;
        case GL_LAYOUT_TRANSFER_DST_EXT:
            return vk::ImageLayout::TransferDst;
        default:
            UNREACHABLE();
            return vk::ImageLayout::Undefined;
    }
}
}  // anonymous namespace

SemaphoreVk::SemaphoreVk() = default;

SemaphoreVk::~SemaphoreVk() = default;

void SemaphoreVk::onDestroy(const gl::Context *context)
{
    ContextVk *contextVk = vk::GetImpl(context);
    contextVk->addGarbage(&mSemaphore);
}

angle::Result SemaphoreVk::importFd(gl::Context *context, gl::HandleType handleType, GLint fd)
{
    switch (handleType)
    {
        case gl::HandleType::OpaqueFd:
            return importOpaqueFd(context, fd);

        default:
            ANGLE_VK_UNREACHABLE(vk::GetImpl(context));
            return angle::Result::Stop;
    }
}

angle::Result SemaphoreVk::wait(gl::Context *context,
                                const gl::BufferBarrierVector &bufferBarriers,
                                const gl::TextureBarrierVector &textureBarriers)
{
    ContextVk *contextVk = vk::GetImpl(context);

    if (!bufferBarriers.empty() || !textureBarriers.empty())
    {
        // Create one global memory barrier to cover all barriers.
        if (contextVk->commandGraphEnabled())
        {
            contextVk->getCommandGraph()->syncExternalMemory();
        }
        else
        {
            ANGLE_TRY(contextVk->syncExternalMemory());
        }
    }

    uint32_t rendererQueueFamilyIndex = contextVk->getRenderer()->getQueueFamilyIndex();

    if (!bufferBarriers.empty())
    {
        // Perform a queue ownership transfer for each buffer.
        for (gl::Buffer *buffer : bufferBarriers)
        {
            BufferVk *bufferVk             = vk::GetImpl(buffer);
            vk::BufferHelper &bufferHelper = bufferVk->getBuffer();

            vk::CommandBuffer *commandBuffer;
            if (contextVk->commandGraphEnabled())
            {
                // If there were GL commands using this buffer prior to this call, that's a
                // synchronization error on behalf of the program.
                ASSERT(!bufferHelper.hasRecordedCommands());

                ANGLE_TRY(bufferHelper.recordCommands(contextVk, &commandBuffer));
            }
            else
            {
                ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer(&commandBuffer));
            }

            // Queue ownership transfer.
            bufferHelper.changeQueue(rendererQueueFamilyIndex, commandBuffer);
        }
    }

    if (!textureBarriers.empty())
    {
        // Perform a queue ownership transfer for each texture.  Additionally, we are being
        // informed that the layout of the image has been externally transitioned, so we need to
        // update our internal state tracking.
        for (const gl::TextureAndLayout &textureAndLayout : textureBarriers)
        {
            TextureVk *textureVk   = vk::GetImpl(textureAndLayout.texture);
            vk::ImageHelper &image = textureVk->getImage();
            vk::ImageLayout layout = GetVulkanImageLayout(textureAndLayout.layout);

            // Inform the image that the layout has been externally changed.
            image.onExternalLayoutChange(layout);

            vk::CommandBuffer *commandBuffer;
            if (contextVk->commandGraphEnabled())
            {
                // If there were GL commands using this image prior to this call, that's a
                // synchronization error on behalf of the program.
                ASSERT(!image.hasRecordedCommands());
                ANGLE_TRY(image.recordCommands(contextVk, &commandBuffer));
            }
            else
            {
                ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer(&commandBuffer));
            }
            // Queue ownership transfer.
            image.changeLayoutAndQueue(image.getAspectFlags(), layout, rendererQueueFamilyIndex,
                                       commandBuffer);
        }
    }

    contextVk->insertWaitSemaphore(&mSemaphore);
    return angle::Result::Continue;
}

angle::Result SemaphoreVk::signal(gl::Context *context,
                                  const gl::BufferBarrierVector &bufferBarriers,
                                  const gl::TextureBarrierVector &textureBarriers)
{
    ContextVk *contextVk = vk::GetImpl(context);

    if (!bufferBarriers.empty())
    {
        // Perform a queue ownership transfer for each buffer.
        for (gl::Buffer *buffer : bufferBarriers)
        {
            BufferVk *bufferVk             = vk::GetImpl(buffer);
            vk::BufferHelper &bufferHelper = bufferVk->getBuffer();

            vk::CommandBuffer *commandBuffer;
            if (contextVk->commandGraphEnabled())
            {
                ANGLE_TRY(bufferHelper.recordCommands(contextVk, &commandBuffer));
            }
            else
            {
                ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer(&commandBuffer));
            }

            // Queue ownership transfer.
            bufferHelper.changeQueue(VK_QUEUE_FAMILY_EXTERNAL, commandBuffer);
        }
    }

    if (!textureBarriers.empty())
    {
        // Perform a queue ownership transfer for each texture.  Additionally, transition the image
        // to the requested layout.
        for (const gl::TextureAndLayout &textureAndLayout : textureBarriers)
        {
            TextureVk *textureVk   = vk::GetImpl(textureAndLayout.texture);
            vk::ImageHelper &image = textureVk->getImage();
            vk::ImageLayout layout = GetVulkanImageLayout(textureAndLayout.layout);

            // Don't transition to Undefined layout.  If external wants to transition the image away
            // from Undefined after this operation, it's perfectly fine to keep the layout as is in
            // ANGLE.  Note that vk::ImageHelper doesn't expect transitions to Undefined.
            if (layout == vk::ImageLayout::Undefined)
            {
                layout = image.getCurrentImageLayout();
            }

            vk::CommandBuffer *commandBuffer;
            if (contextVk->commandGraphEnabled())
            {
                ANGLE_TRY(image.recordCommands(contextVk, &commandBuffer));
            }
            else
            {
                ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer(&commandBuffer));
            }

            // Queue ownership transfer and layout transition.
            image.changeLayoutAndQueue(image.getAspectFlags(), layout, VK_QUEUE_FAMILY_EXTERNAL,
                                       commandBuffer);
        }
    }

    if (!bufferBarriers.empty() || !textureBarriers.empty())
    {
        // Create one global memory barrier to cover all barriers.
        if (contextVk->commandGraphEnabled())
        {
            contextVk->getCommandGraph()->syncExternalMemory();
        }
        else
        {
            ANGLE_TRY(contextVk->syncExternalMemory());
        }
    }

    return contextVk->flushImpl(&mSemaphore);
}

angle::Result SemaphoreVk::importOpaqueFd(gl::Context *context, GLint fd)
{
    ContextVk *contextVk = vk::GetImpl(context);
    RendererVk *renderer = contextVk->getRenderer();

    if (!mSemaphore.valid())
    {
        mSemaphore.init(renderer->getDevice());
    }

    ASSERT(mSemaphore.valid());

    VkImportSemaphoreFdInfoKHR importSemaphoreFdInfo = {};
    importSemaphoreFdInfo.sType      = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR;
    importSemaphoreFdInfo.semaphore  = mSemaphore.getHandle();
    importSemaphoreFdInfo.flags      = 0;
    importSemaphoreFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT;
    importSemaphoreFdInfo.fd         = fd;

    ANGLE_VK_TRY(contextVk, vkImportSemaphoreFdKHR(renderer->getDevice(), &importSemaphoreFdInfo));

    return angle::Result::Continue;
}

}  // namespace rx
