//
// Copyright 2016 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.
//
// RenderbufferVk.cpp:
//    Implements the class methods for RenderbufferVk.
//

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

#include "libANGLE/Context.h"
#include "libANGLE/Image.h"
#include "libANGLE/renderer/vulkan/ContextVk.h"
#include "libANGLE/renderer/vulkan/ImageVk.h"
#include "libANGLE/renderer/vulkan/RendererVk.h"

namespace rx
{
RenderbufferVk::RenderbufferVk(const gl::RenderbufferState &state)
    : RenderbufferImpl(state), mOwnsImage(false), mImage(nullptr)
{}

RenderbufferVk::~RenderbufferVk() {}

void RenderbufferVk::onDestroy(const gl::Context *context)
{
    ContextVk *contextVk = vk::GetImpl(context);
    releaseAndDeleteImage(contextVk);
}

angle::Result RenderbufferVk::setStorageImpl(const gl::Context *context,
                                             size_t samples,
                                             GLenum internalformat,
                                             size_t width,
                                             size_t height)
{
    ContextVk *contextVk       = vk::GetImpl(context);
    RendererVk *renderer       = contextVk->getRenderer();
    const vk::Format &vkFormat = renderer->getFormat(internalformat);

    if (!mOwnsImage)
    {
        releaseAndDeleteImage(contextVk);
    }

    if (mImage != nullptr && mImage->valid())
    {
        // Check against the state if we need to recreate the storage.
        if (internalformat != mState.getFormat().info->internalFormat ||
            static_cast<GLsizei>(width) != mState.getWidth() ||
            static_cast<GLsizei>(height) != mState.getHeight())
        {
            releaseImage(contextVk);
        }
    }

    if ((mImage == nullptr || !mImage->valid()) && (width != 0 && height != 0))
    {
        if (mImage == nullptr)
        {
            mImage     = new vk::ImageHelper();
            mOwnsImage = true;
        }

        const angle::Format &textureFormat = vkFormat.imageFormat();
        bool isDepthOrStencilFormat = textureFormat.depthBits > 0 || textureFormat.stencilBits > 0;
        const VkImageUsageFlags usage =
            VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
            VK_IMAGE_USAGE_SAMPLED_BIT |
            (textureFormat.redBits > 0 ? VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT : 0) |
            (isDepthOrStencilFormat ? VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT : 0);

        VkExtent3D extents = {static_cast<uint32_t>(width), static_cast<uint32_t>(height), 1u};
        ANGLE_TRY(mImage->init(contextVk, gl::TextureType::_2D, extents, vkFormat,
                               static_cast<uint32_t>(samples), usage, 0, 0, 1, 1));

        VkMemoryPropertyFlags flags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
        ANGLE_TRY(mImage->initMemory(contextVk, renderer->getMemoryProperties(), flags));

        VkImageAspectFlags aspect = vk::GetFormatAspectFlags(textureFormat);

        // Note that LUMA textures are not color-renderable, so a read-view with swizzle is not
        // needed.
        ANGLE_TRY(mImage->initImageView(contextVk, gl::TextureType::_2D, aspect, gl::SwizzleState(),
                                        &mImageView, 0, 1));

        // Clear the renderbuffer if it has emulated channels.
        mImage->stageClearIfEmulatedFormat(gl::ImageIndex::Make2D(0), vkFormat);

        mRenderTarget.init(mImage, &mImageView, nullptr, 0, 0);
    }

    return angle::Result::Continue;
}

angle::Result RenderbufferVk::setStorage(const gl::Context *context,
                                         GLenum internalformat,
                                         size_t width,
                                         size_t height)
{
    return setStorageImpl(context, 1, internalformat, width, height);
}

angle::Result RenderbufferVk::setStorageMultisample(const gl::Context *context,
                                                    size_t samples,
                                                    GLenum internalformat,
                                                    size_t width,
                                                    size_t height)
{
    // If the specific number of samples requested is not supported, the smallest number that's at
    // least that many needs to be selected.
    const RendererVk *renderer           = vk::GetImpl(context)->getRenderer();
    const angle::Format &format          = renderer->getFormat(internalformat).imageFormat();
    const VkPhysicalDeviceLimits &limits = renderer->getPhysicalDeviceProperties().limits;

    const uint32_t colorSampleCounts        = limits.framebufferColorSampleCounts;
    const uint32_t depthSampleCounts        = limits.framebufferDepthSampleCounts;
    const uint32_t stencilSampleCounts      = limits.framebufferStencilSampleCounts;
    const uint32_t depthStencilSampleCounts = depthSampleCounts & stencilSampleCounts;
    uint32_t formatSampleCounts             = colorSampleCounts;

    if (format.depthBits > 0)
    {
        if (format.stencilBits > 0)
        {
            formatSampleCounts = depthStencilSampleCounts;
        }
        else
        {
            formatSampleCounts = depthSampleCounts;
        }
    }
    else if (format.stencilBits > 0)
    {
        formatSampleCounts = stencilSampleCounts;
    }

    samples = vk_gl::GetSampleCount(formatSampleCounts, static_cast<uint32_t>(samples));

    return setStorageImpl(context, samples, internalformat, width, height);
}

angle::Result RenderbufferVk::setStorageEGLImageTarget(const gl::Context *context,
                                                       egl::Image *image)
{
    ContextVk *contextVk = vk::GetImpl(context);
    RendererVk *renderer = contextVk->getRenderer();

    releaseAndDeleteImage(contextVk);

    ImageVk *imageVk = vk::GetImpl(image);
    mImage           = imageVk->getImage();
    mOwnsImage       = false;

    const vk::Format &vkFormat = renderer->getFormat(image->getFormat().info->sizedInternalFormat);
    const angle::Format &textureFormat = vkFormat.imageFormat();

    VkImageAspectFlags aspect = vk::GetFormatAspectFlags(textureFormat);

    // Transfer the image to this queue if needed
    uint32_t rendererQueueFamilyIndex = contextVk->getRenderer()->getQueueFamilyIndex();
    if (mImage->isQueueChangeNeccesary(rendererQueueFamilyIndex))
    {
        vk::CommandBuffer *commandBuffer = nullptr;
        ANGLE_TRY(mImage->recordCommands(contextVk, &commandBuffer));
        mImage->changeLayoutAndQueue(aspect, vk::ImageLayout::ColorAttachment,
                                     rendererQueueFamilyIndex, commandBuffer);
    }

    ANGLE_TRY(mImage->initLayerImageView(contextVk, imageVk->getImageTextureType(), aspect,
                                         gl::SwizzleState(), &mImageView, imageVk->getImageLevel(),
                                         1, imageVk->getImageLayer(), 1));

    if (imageVk->getImageTextureType() == gl::TextureType::CubeMap)
    {
        gl::TextureType arrayType = vk::Get2DTextureType(imageVk->getImage()->getLayerCount(),
                                                         imageVk->getImage()->getSamples());
        ANGLE_TRY(mImage->initLayerImageView(contextVk, arrayType, aspect, gl::SwizzleState(),
                                             &mCubeImageFetchView, imageVk->getImageLevel(), 1,
                                             imageVk->getImageLayer(), 1));
    }

    mRenderTarget.init(mImage, &mImageView,
                       mCubeImageFetchView.valid() ? &mCubeImageFetchView : nullptr,
                       imageVk->getImageLevel(), imageVk->getImageLayer());

    return angle::Result::Continue;
}

angle::Result RenderbufferVk::getAttachmentRenderTarget(const gl::Context *context,
                                                        GLenum binding,
                                                        const gl::ImageIndex &imageIndex,
                                                        GLsizei samples,
                                                        FramebufferAttachmentRenderTarget **rtOut)
{
    ASSERT(mImage && mImage->valid());
    ANGLE_TRY(mRenderTarget.flushStagedUpdates(vk::GetImpl(context)));
    *rtOut = &mRenderTarget;
    return angle::Result::Continue;
}

angle::Result RenderbufferVk::initializeContents(const gl::Context *context,
                                                 const gl::ImageIndex &imageIndex)
{
    mImage->stageSubresourceRobustClear(imageIndex, mImage->getFormat().angleFormat());
    return mImage->flushAllStagedUpdates(vk::GetImpl(context));
}

void RenderbufferVk::releaseOwnershipOfImage(const gl::Context *context)
{
    ContextVk *contextVk = vk::GetImpl(context);

    mOwnsImage = false;
    releaseAndDeleteImage(contextVk);
}

void RenderbufferVk::releaseAndDeleteImage(ContextVk *contextVk)
{
    releaseImage(contextVk);
    SafeDelete(mImage);
}

void RenderbufferVk::releaseImage(ContextVk *contextVk)
{
    if (mImage && mOwnsImage)
    {
        RendererVk *renderer = contextVk->getRenderer();

        mImage->releaseImage(renderer);
        mImage->releaseStagingBuffer(renderer);
    }
    else
    {
        mImage = nullptr;
    }

    contextVk->addGarbage(&mImageView);
    contextVk->addGarbage(&mCubeImageFetchView);
}

}  // namespace rx
