//
// 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.
//
// RenderTargetVk:
//   Wrapper around a Vulkan renderable resource, using an ImageView.
//

#ifndef LIBANGLE_RENDERER_VULKAN_RENDERTARGETVK_H_
#define LIBANGLE_RENDERER_VULKAN_RENDERTARGETVK_H_

#include "common/vulkan/vk_headers.h"
#include "libANGLE/FramebufferAttachment.h"
#include "libANGLE/renderer/renderer_utils.h"
#include "libANGLE/renderer/vulkan/vk_helpers.h"

namespace rx
{
namespace vk
{
class ImageHelper;
class ImageView;
class Resource;
class RenderPassDesc;
}  // namespace vk

class ContextVk;
class TextureVk;

enum class RenderTargetTransience
{
    // Regular render targets that load and store from the image.
    Default,
    // Multisampled-render-to-texture textures, where the implicit multisampled image is transient,
    // but the resolved image is persistent.
    MultisampledTransient,
    // Renderable YUV textures, where the color attachment (if it exists at all) is transient,
    // but the resolved image is persistent.
    YuvResolveTransient,
    // Multisampled-render-to-texture depth/stencil textures.
    EntirelyTransient,
};

// This is a very light-weight class that does not own to the resources it points to.
// It's meant only to copy across some information from a FramebufferAttachment to the
// business rendering logic. It stores Images and ImageViews by pointer for performance.
class RenderTargetVk final : public FramebufferAttachmentRenderTarget
{
  public:
    RenderTargetVk();
    ~RenderTargetVk() override;

    // Used in std::vector initialization.
    RenderTargetVk(RenderTargetVk &&other);

    void init(vk::ImageHelper *image,
              vk::ImageViewHelper *imageViews,
              vk::ImageHelper *resolveImage,
              vk::ImageViewHelper *resolveImageViews,
              gl::LevelIndex levelIndexGL,
              uint32_t layerIndex,
              uint32_t layerCount,
              RenderTargetTransience transience);

    vk::ImageOrBufferViewSubresourceSerial getDrawSubresourceSerial() const;
    vk::ImageOrBufferViewSubresourceSerial getResolveSubresourceSerial() const;

    // Note: RenderTargets should be called in order, with the depth/stencil onRender last.
    void onColorDraw(ContextVk *contextVk,
                     uint32_t framebufferLayerCount,
                     vk::PackedAttachmentIndex index);
    void onColorResolve(ContextVk *contextVk,
                        uint32_t framebufferLayerCount,
                        size_t readColorIndexGL,
                        const vk::ImageView &view);
    void onDepthStencilDraw(ContextVk *contextVk, uint32_t framebufferLayerCount);
    void onDepthStencilResolve(ContextVk *contextVk,
                               uint32_t framebufferLayerCount,
                               VkImageAspectFlags aspects,
                               const vk::ImageView &view);

    vk::ImageHelper &getImageForRenderPass();
    const vk::ImageHelper &getImageForRenderPass() const;

    vk::ImageHelper &getResolveImageForRenderPass();
    const vk::ImageHelper &getResolveImageForRenderPass() const;

    vk::ImageHelper &getImageForCopy() const;
    vk::ImageHelper &getImageForWrite() const;

    // For cube maps we use single-level single-layer 2D array views.
    angle::Result getImageView(ContextVk *contextVk, const vk::ImageView **imageViewOut) const;
    angle::Result getImageViewWithColorspace(ContextVk *contextVk,
                                             gl::SrgbWriteControlMode srgbWriteContrlMode,
                                             const vk::ImageView **imageViewOut) const;
    angle::Result getResolveImageView(ContextVk *contextVk,
                                      const vk::ImageView **imageViewOut) const;
    angle::Result getDepthOrStencilImageView(ContextVk *contextVk,
                                             VkImageAspectFlagBits aspect,
                                             const vk::ImageView **imageViewOut) const;
    angle::Result getDepthOrStencilImageViewForCopy(ContextVk *contextVk,
                                                    VkImageAspectFlagBits aspect,
                                                    const vk::ImageView **imageViewOut) const;
    angle::Result getResolveDepthOrStencilImageView(ContextVk *contextVk,
                                                    VkImageAspectFlagBits aspect,
                                                    const vk::ImageView **imageViewOut) const;

    // For 3D textures, the 2D view created for render target is invalid to read from.  The
    // following will return a view to the whole image (for all types, including 3D and 2DArray).
    angle::Result getCopyImageView(ContextVk *contextVk, const vk::ImageView **imageViewOut) const;

    angle::FormatID getImageActualFormatID() const;
    const angle::Format &getImageActualFormat() const;
    angle::FormatID getImageIntendedFormatID() const;
    const angle::Format &getImageIntendedFormat() const;

    gl::Extents getExtents() const;
    gl::Extents getRotatedExtents() const;
    gl::LevelIndex getLevelIndex() const { return mLevelIndexGL; }
    gl::LevelIndex getLevelIndexForImage(const vk::ImageHelper &image) const;
    uint32_t getLayerIndex() const { return mLayerIndex; }
    uint32_t getLayerCount() const { return mLayerCount; }
    bool is3DImage() const { return getOwnerOfData()->getType() == VK_IMAGE_TYPE_3D; }

    gl::ImageIndex getImageIndexForClear(uint32_t layerCount) const;

    // Special mutator for Surface RenderTargets. Allows the Framebuffer to keep a single
    // RenderTargetVk pointer.
    void updateSwapchainImage(vk::ImageHelper *image,
                              vk::ImageViewHelper *imageViews,
                              vk::ImageHelper *resolveImage,
                              vk::ImageViewHelper *resolveImageViews);

    angle::Result flushStagedUpdates(ContextVk *contextVk,
                                     vk::ClearValuesArray *deferredClears,
                                     uint32_t deferredClearIndex,
                                     uint32_t framebufferLayerCount);

    bool hasDefinedContent() const;
    bool hasDefinedStencilContent() const;
    // Mark content as undefined so that certain optimizations are possible such as using DONT_CARE
    // as loadOp of the render target in the next renderpass.  If |preferToKeepContentsDefinedOut|
    // is set to true, it's preferred to ignore the invalidation due to image format and device
    // architecture properties.
    void invalidateEntireContent(ContextVk *contextVk, bool *preferToKeepContentsDefinedOut);
    void invalidateEntireStencilContent(ContextVk *contextVk, bool *preferToKeepContentsDefinedOut);

    // See the description of mTransience for details of how the following two can interact.
    bool hasResolveAttachment() const { return mResolveImage != nullptr && !isEntirelyTransient(); }
    bool isImageTransient() const { return mTransience != RenderTargetTransience::Default; }
    bool isEntirelyTransient() const
    {
        return mTransience == RenderTargetTransience::EntirelyTransient;
    }
    bool isYuvResolve() const
    {
        return mResolveImage != nullptr ? mResolveImage->isYuvExternalFormat() : false;
    }

    void onNewFramebuffer(const vk::SharedFramebufferCacheKey &sharedFramebufferCacheKey)
    {
        mFramebufferCacheManager.addKey(sharedFramebufferCacheKey);
    }
    void releaseFramebuffers(ContextVk *contextVk)
    {
        mFramebufferCacheManager.releaseKeys(contextVk);
    }
    // Resets all members to the initial state without releasing framebuffers since Window Surface
    // framebuffers are not managed by the cache.
    void releaseSwapchainImage() { reset(); }
    // Releases framebuffers and resets all members to the initial state.
    void release(ContextVk *contextVk)
    {
        releaseFramebuffers(contextVk);
        reset();
    }
    // Destroys framebuffers and resets all members to the initial state.
    void destroy(vk::Renderer *renderer)
    {
        mFramebufferCacheManager.destroyKeys(renderer);
        reset();
    }

    // Helpers to update rendertarget colorspace
    void updateWriteColorspace(gl::SrgbWriteControlMode srgbWriteControlMode)
    {
        ASSERT(mImage && mImage->valid() && mImageViews);
        mImageViews->updateSrgbWriteControlMode(mImage->getActualFormat(), srgbWriteControlMode);
        if (hasResolveAttachment() && !isYuvResolve())
        {
            ASSERT(mResolveImage && mResolveImage->valid() && mResolveImageViews);
            mResolveImageViews->updateSrgbWriteControlMode(mResolveImage->getActualFormat(),
                                                           srgbWriteControlMode);
        }
    }
    bool hasColorspaceOverrideForRead() const
    {
        ASSERT(mImage && mImage->valid() && mImageViews);
        return mImageViews->hasColorspaceOverrideForRead(*mImage);
    }
    bool hasColorspaceOverrideForWrite() const
    {
        ASSERT(mImage && mImage->valid() && mImageViews);
        return mImageViews->hasColorspaceOverrideForWrite(*mImage);
    }
    angle::FormatID getColorspaceOverrideFormatForWrite(angle::FormatID format) const
    {
        ASSERT(mImage && mImage->valid() && mImageViews);
        return mImageViews->getColorspaceOverrideFormatForWrite(format);
    }

  private:
    void reset();

    angle::Result getImageViewImpl(ContextVk *contextVk,
                                   const vk::ImageHelper &image,
                                   vk::ImageViewHelper *imageViews,
                                   const vk::ImageView **imageViewOut) const;
    angle::Result getDepthOrStencilImageViewImpl(ContextVk *contextVk,
                                                 const vk::ImageHelper &image,
                                                 vk::ImageViewHelper *imageViews,
                                                 VkImageAspectFlagBits aspect,
                                                 const vk::ImageView **imageViewOut) const;

    vk::ImageOrBufferViewSubresourceSerial getSubresourceSerialImpl(
        vk::ImageViewHelper *imageViews) const;

    bool isResolveImageOwnerOfData() const;
    vk::ImageHelper *getOwnerOfData() const;

    // The color or depth/stencil attachment of the framebuffer and its view.
    vk::ImageHelper *mImage;
    vk::ImageViewHelper *mImageViews;

    // If present, this is the corresponding resolve attachment and its view.  This is used to
    // implement GL_EXT_multisampled_render_to_texture, so while the rendering is done on mImage
    // during the renderpass, the resolved image is the one that actually holds the data.  This
    // means that data uploads and blit are done on this image, copies are done out of this image
    // etc.  This means that if there is no clear, and hasDefined*Content(), the contents of
    // mResolveImage must be copied to mImage since the loadOp of the attachment must be set to
    // LOAD.
    vk::ImageHelper *mResolveImage;
    vk::ImageViewHelper *mResolveImageViews;

    // Which subresource of the image is used as render target.
    //
    // |mLevelIndexGL| applies to the level index of mImage unless there is a resolve attachment,
    // in which case |mLevelIndexGL| applies to the mResolveImage since mImage is always
    // single-level.
    //
    // For single-layer render targets, |mLayerIndex| will contain the layer index and |mLayerCount|
    // will be 1.  For layered render targets, |mLayerIndex| will be 0 and |mLayerCount| will be the
    // number of layers in the image (or level depth, if image is 3D).  Note that blit and other
    // functions that read or write to the render target always use layer 0, so this works out for
    // users of |getLayerIndex()|.
    gl::LevelIndex mLevelIndexGL;
    uint32_t mLayerIndex;
    uint32_t mLayerCount;

    // If resolve attachment exists, |mTransience| could be *Transient if the multisampled results
    // need to be discarded.
    //
    // - GL_EXT_multisampled_render_to_texture[2]: this is |MultisampledTransient| for render
    //   targets created from color textures, as well as color or depth/stencil renderbuffers.
    // - GL_EXT_multisampled_render_to_texture2: this is |EntirelyTransient| for depth/stencil
    //   textures per this extension, even though a resolve attachment is not even provided.
    //
    // Based on the above, we have:
    //
    //                     mResolveImage == nullptr
    //                        Normal rendering
    // Default                   No resolve
    //                         storeOp = STORE
    //                      Owner of data: mImage
    //
    //      ---------------------------------------------
    //
    //                     mResolveImage != nullptr
    //               GL_EXT_multisampled_render_to_texture
    // Multisampled               Resolve
    // Transient             storeOp = DONT_CARE
    //                     resolve storeOp = STORE
    //                   Owner of data: mResolveImage
    //
    //      ---------------------------------------------
    //
    //                     mResolveImage != nullptr
    //               GL_EXT_multisampled_render_to_texture2
    // Entirely                  No Resolve
    // Transient             storeOp = DONT_CARE
    //                   Owner of data: mResolveImage
    //
    // In the above, storeOp of the resolve attachment is always STORE.  If |Default|, storeOp is
    // affected by a framebuffer invalidate call.  Note that even though |EntirelyTransient| has a
    // resolve attachment, it is not used.  The only purpose of |mResolveImage| is to store deferred
    // clears.
    RenderTargetTransience mTransience;

    // Track references to the cached Framebuffer object that created out of this object
    vk::FramebufferCacheManager mFramebufferCacheManager;
};

// A vector of rendertargets
using RenderTargetVector = std::vector<RenderTargetVk>;
}  // namespace rx

#endif  // LIBANGLE_RENDERER_VULKAN_RENDERTARGETVK_H_
