//
// 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/TextureVk.h"
#include "libANGLE/renderer/vulkan/vk_renderer.h"

namespace rx
{
namespace
{
angle::SubjectIndex kRenderbufferImageSubjectIndex = 0;
}  // namespace

RenderbufferVk::RenderbufferVk(const gl::RenderbufferState &state)
    : RenderbufferImpl(state),
      mOwnsImage(false),
      mImage(nullptr),
      mImageObserverBinding(this, kRenderbufferImageSubjectIndex),
      mRenderer(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,
                                             GLsizei samples,
                                             GLenum internalformat,
                                             GLsizei width,
                                             GLsizei height,
                                             gl::MultisamplingMode mode)
{
    ContextVk *contextVk            = vk::GetImpl(context);
    mRenderer                       = contextVk->getRenderer();
    const vk::Format &format        = mRenderer->getFormat(internalformat);
    angle::FormatID textureFormatID = format.getActualRenderableImageFormatID();

    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 ||
            width != mState.getWidth() || height != mState.getHeight() ||
            samples != mState.getSamples() || mode != mState.getMultisamplingMode())
        {
            releaseImage(contextVk);
        }
    }

    if ((mImage != nullptr && mImage->valid()) || width == 0 || height == 0)
    {
        return angle::Result::Continue;
    }

    if (mImage == nullptr)
    {
        mImage              = new vk::ImageHelper();
        mOwnsImage          = true;
        mImageObserverBinding.bind(mImage);
        mImageViews.init(mRenderer);
    }

    const angle::Format &textureFormat = format.getActualRenderableImageFormat();
    const bool isDepthStencilFormat    = textureFormat.hasDepthOrStencilBits();
    ASSERT(textureFormat.redBits > 0 || isDepthStencilFormat);

    const bool isRenderToTexture = mode == gl::MultisamplingMode::MultisampledRenderToTexture;
    const bool hasRenderToTextureEXT =
        mRenderer->getFeatures().supportsMultisampledRenderToSingleSampled.enabled;

    // Transfer and sampled usage are used for various utilities such as readback or blit.
    VkImageUsageFlags usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
                              VK_IMAGE_USAGE_SAMPLED_BIT;

    // Renderbuffer's normal usage is as framebuffer attachment.
    usage |= isDepthStencilFormat ? VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT
                                  : VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;

    // When used to emulate multisampled render to texture, it can be read as input attachment.
    if (isRenderToTexture && !hasRenderToTextureEXT)
    {
        usage |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
    }

    // For framebuffer fetch and advanced blend emulation, color will be read as input attachment.
    // For depth/stencil framebuffer fetch, depth/stencil will also be read as input attachment.
    if (!isDepthStencilFormat ||
        mRenderer->getFeatures().supportsShaderFramebufferFetchDepthStencil.enabled)
    {
        usage |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
    }

    VkImageCreateFlags createFlags = vk::kVkImageCreateFlagsNone;
    if (isRenderToTexture &&
        mRenderer->getFeatures().supportsMultisampledRenderToSingleSampled.enabled)
    {
        createFlags |= VK_IMAGE_CREATE_MULTISAMPLED_RENDER_TO_SINGLE_SAMPLED_BIT_EXT;
    }

    if (contextVk->getFeatures().limitSampleCountTo2.enabled)
    {
        samples = std::min(samples, 2);
    }

    const uint32_t imageSamples = isRenderToTexture ? 1 : samples;

    bool robustInit = contextVk->isRobustResourceInitEnabled();
    vk::TileMemory tileMemoryPreference =
        isDepthStencilFormat ? vk::TileMemory::Preferred : vk::TileMemory::Prohibited;

    VkExtent3D extents = {static_cast<uint32_t>(width), static_cast<uint32_t>(height), 1u};
    ANGLE_TRY(mImage->initExternal(
        contextVk, gl::TextureType::_2D, extents, format.getIntendedFormatID(), textureFormatID,
        imageSamples, usage, createFlags, vk::ImageAccess::Undefined, nullptr, gl::LevelIndex(0), 1,
        1, robustInit, false, tileMemoryPreference, vk::YcbcrConversionDesc{}, nullptr,
        vk::ImageFormatReinterpretability::ColorspaceOverrides));

    VkMemoryPropertyFlags flags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
    ANGLE_TRY(contextVk->initImageAllocation(mImage, false, flags,
                                             vk::MemoryAllocationType::RenderBufferStorageImage));

    // If multisampled render to texture, an implicit multisampled image is created which is used as
    // the color or depth/stencil attachment.  At the end of the render pass, this image is
    // automatically resolved into |mImage| and its contents are discarded.
    if (isRenderToTexture && !hasRenderToTextureEXT)
    {
        mMultisampledImageViews.init(mRenderer);

        ANGLE_TRY(mMultisampledImage.initImplicitMultisampledRenderToTexture(
            contextVk, false, samples, *mImage, mImage->getExtents(), robustInit));

        mRenderTarget.init(&mMultisampledImage, &mMultisampledImageViews, mImage, &mImageViews,
                           gl::LevelIndex(0), 0, 1, RenderTargetTransience::MultisampledTransient);
    }
    else
    {
        mRenderTarget.init(mImage, &mImageViews, nullptr, nullptr, gl::LevelIndex(0), 0, 1,
                           RenderTargetTransience::Default);
    }

    return angle::Result::Continue;
}

angle::Result RenderbufferVk::setStorage(const gl::Context *context,
                                         GLenum internalformat,
                                         GLsizei width,
                                         GLsizei height)
{
    // The ES 3.0 spec(section 4.4.2.1) states that RenderbufferStorage is equivalent to calling
    // RenderbufferStorageMultisample with samples equal to zero.
    return setStorageImpl(context, 0, internalformat, width, height,
                          gl::MultisamplingMode::Regular);
}

angle::Result RenderbufferVk::setStorageMultisample(const gl::Context *context,
                                                    GLsizei samples,
                                                    GLenum internalformat,
                                                    GLsizei width,
                                                    GLsizei height,
                                                    gl::MultisamplingMode mode)
{
    return setStorageImpl(context, samples, internalformat, width, height, mode);
}

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

    ANGLE_TRY(contextVk->getShareGroup()->lockDefaultContextsPriority(contextVk));

    releaseAndDeleteImage(contextVk);

    ImageVk *imageVk    = vk::GetImpl(image);
    mImage              = imageVk->getImage();
    mOwnsImage          = false;
    mImageObserverBinding.bind(mImage);
    mImageViews.init(mRenderer);

    // Update ImageViewHelper's colorspace related state
    EGLenum imageColorspaceAttribute = image->getColorspaceAttribute();
    if (imageColorspaceAttribute != EGL_GL_COLORSPACE_DEFAULT_EXT)
    {
        egl::ImageColorspace imageColorspace =
            (imageColorspaceAttribute == EGL_GL_COLORSPACE_SRGB_KHR) ? egl::ImageColorspace::SRGB
                                                                     : egl::ImageColorspace::Linear;
        ASSERT(mImage != nullptr);
        mImageViews.updateEglImageColorspace(mImage->getActualFormat(), imageColorspace);
    }

    const uint32_t sourceLevel = image->getSourceImageIndex().getLevelIndex();
    const uint32_t layerOffset =
        image->getSourceImageIndex().hasLayer() ? image->getSourceImageIndex().getLayerIndex() : 0;

    mRenderTarget.init(mImage, &mImageViews, nullptr, nullptr, gl::LevelIndex(sourceLevel),
                       layerOffset, 1, RenderTargetTransience::Default);

    return angle::Result::Continue;
}

angle::Result RenderbufferVk::copyRenderbufferSubData(const gl::Context *context,
                                                      const gl::Renderbuffer *srcBuffer,
                                                      GLint srcX,
                                                      GLint srcY,
                                                      GLint dstX,
                                                      GLint dstY,
                                                      GLsizei srcWidth,
                                                      GLsizei srcHeight)
{
    RenderbufferVk *sourceVk = vk::GetImpl(srcBuffer);

    // Make sure the source/destination targets are initialized and all staged updates are flushed.
    ANGLE_TRY(sourceVk->ensureImageInitialized(context));
    ANGLE_TRY(ensureImageInitialized(context));

    const gl::SourceLevel srcLevel = srcBuffer->getState().toSourceLevel(gl::OwnLevel(0));
    const gl::SourceLayer srcZ     = srcBuffer->getState().toSourceLayer(gl::OwnLayer(0));

    const gl::SourceLevel dstLevel = mState.toSourceLevel(gl::OwnLevel(0));
    const gl::SourceLayer dstZ     = mState.toSourceLayer(gl::OwnLayer(0));

    return vk::ImageHelper::CopyImageSubData(context, sourceVk->getImage(), srcLevel.get(), srcX,
                                             srcY, srcZ.get(), mImage, dstLevel.get(), dstX, dstY,
                                             dstZ.get(), srcWidth, srcHeight, 1);
}

angle::Result RenderbufferVk::copyTextureSubData(const gl::Context *context,
                                                 const gl::Texture *srcTexture,
                                                 gl::OwnLevel ownSrcLevel,
                                                 GLint srcX,
                                                 GLint srcY,
                                                 gl::OwnLayer ownSrcZ,
                                                 GLint dstX,
                                                 GLint dstY,
                                                 GLsizei srcWidth,
                                                 GLsizei srcHeight)
{
    ContextVk *contextVk = vk::GetImpl(context);
    TextureVk *sourceVk  = vk::GetImpl(srcTexture);

    // Make sure the source/destination targets are initialized and all staged updates are flushed.
    ANGLE_TRY(sourceVk->ensureImageInitialized(contextVk, ImageMipLevels::EnabledLevels));
    ANGLE_TRY(ensureImageInitialized(context));

    const gl::SourceLevel srcLevel = srcTexture->getState().toSourceLevel(ownSrcLevel);
    const gl::SourceLayer srcZ     = srcTexture->getState().toSourceLayer(ownSrcZ);

    const gl::SourceLevel dstLevel = mState.toSourceLevel(gl::OwnLevel(0));
    const gl::SourceLayer dstZ     = mState.toSourceLayer(gl::OwnLayer(0));

    return vk::ImageHelper::CopyImageSubData(context, &sourceVk->getImage(), srcLevel.get(), srcX,
                                             srcY, srcZ.get(), mImage, dstLevel.get(), dstX, dstY,
                                             dstZ.get(), srcWidth, srcHeight, 1);
}

angle::Result RenderbufferVk::getAttachmentRenderTarget(const gl::Context *context,
                                                        GLenum binding,
                                                        const gl::OwnImageIndex &ownImageIndex,
                                                        GLsizei samples,
                                                        FramebufferAttachmentRenderTarget **rtOut)
{
    ASSERT(mImage && mImage->valid());
    *rtOut = &mRenderTarget;
    return angle::Result::Continue;
}

angle::Result RenderbufferVk::initializeContents(const gl::Context *context,
                                                 GLenum binding,
                                                 const gl::OwnImageIndex &imageIndex)
{
    // Note: stageSubresourceRobustClear only uses the intended format to count channels.
    mImage->stageRobustResourceClear(mState.toSourceIndex(imageIndex).get(),
                                     mImage->getAspectFlags());
    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);
    mImageObserverBinding.bind(nullptr);
}

void RenderbufferVk::releaseImage(ContextVk *contextVk)
{
    vk::Renderer *renderer = contextVk->getRenderer();
    ShareGroupVk *shareGroupVk = contextVk->getShareGroup();

    if (mImage == nullptr)
    {
        ASSERT(mImageViews.isImageViewGarbageEmpty() &&
               mMultisampledImageViews.isImageViewGarbageEmpty());
    }
    else
    {
        mRenderTarget.release(contextVk);
        mImageViews.release(renderer, mImage->getResourceUse());
        mMultisampledImageViews.release(renderer, mImage->getResourceUse());
    }

    if (mImage)
    {
        shareGroupVk->finalizeImageLayoutInAllSharedContexts(mImage);
        if (mOwnsImage)
        {
            mImage->releaseImage(contextVk);
            mImage->releaseStagedUpdates(renderer);
        }
        else
        {
            mImageObserverBinding.bind(nullptr);
            mImage = nullptr;
        }
    }

    if (mMultisampledImage.valid())
    {
        shareGroupVk->finalizeImageLayoutInAllSharedContexts(&mMultisampledImage);
        mMultisampledImage.releaseImage(contextVk);
    }
}

const gl::InternalFormat &RenderbufferVk::getImplementationSizedFormat() const
{
    GLenum internalFormat = mImage->getActualFormat().glInternalFormat;
    return gl::GetSizedInternalFormatInfo(internalFormat);
}

GLenum RenderbufferVk::getColorReadFormat(const gl::Context *context)
{
    const gl::InternalFormat &sizedFormat = getImplementationSizedFormat();
    return sizedFormat.format;
}

GLenum RenderbufferVk::getColorReadType(const gl::Context *context)
{
    const gl::InternalFormat &sizedFormat = getImplementationSizedFormat();
    return sizedFormat.type;
}

angle::Result RenderbufferVk::getRenderbufferImage(const gl::Context *context,
                                                   const gl::PixelPackState &packState,
                                                   gl::Buffer *packBuffer,
                                                   GLenum format,
                                                   GLenum type,
                                                   void *pixels)
{
    // Storage not defined.
    if (!mImage || !mImage->valid())
    {
        return angle::Result::Continue;
    }

    ContextVk *contextVk = vk::GetImpl(context);
    ANGLE_TRY(mImage->flushAllStagedUpdates(contextVk));

    gl::MaybeOverrideLuminance(format, type, getColorReadFormat(context),
                               getColorReadType(context));

    return mImage->readPixelsForGetImage(contextVk, packState, packBuffer, gl::LevelIndex(0), 0, 0,
                                         format, type, pixels);
}

angle::Result RenderbufferVk::ensureImageInitialized(const gl::Context *context)
{
    // The image must have been already created
    ASSERT(mImage != nullptr && mImage->valid());
    return mImage->flushAllStagedUpdates(vk::GetImpl(context));
}

void RenderbufferVk::onSubjectStateChange(angle::SubjectIndex index, angle::SubjectMessage message)
{
    ASSERT(index == kRenderbufferImageSubjectIndex &&
           (message == angle::SubjectMessage::SubjectChanged ||
            message == angle::SubjectMessage::VkImageChanged));

    if (message == angle::SubjectMessage::VkImageChanged)
    {
        mImageViews.release(mRenderer, mImage->getResourceUse());
    }

    // Forward the notification to the parent class that the staging buffer changed.
    if (message == angle::SubjectMessage::SubjectChanged ||
        message == angle::SubjectMessage::VkImageChanged)
    {
        onStateChange(angle::SubjectMessage::SubjectChanged);
    }
}
}  // namespace rx
