//
// 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.
//
// SurfaceVk.h:
//    Defines the class interface for SurfaceVk, implementing SurfaceImpl.
//

#ifndef LIBANGLE_RENDERER_VULKAN_SURFACEVK_H_
#define LIBANGLE_RENDERER_VULKAN_SURFACEVK_H_

#include <optional>
#include "common/CircularBuffer.h"
#include "common/SimpleMutex.h"
#include "common/vulkan/vk_headers.h"
#include "libANGLE/renderer/SurfaceImpl.h"
#include "libANGLE/renderer/vulkan/RenderTargetVk.h"
#include "libANGLE/renderer/vulkan/vk_helpers.h"

namespace rx
{
class SurfaceVk : public SurfaceImpl, public angle::ObserverInterface, public vk::Resource
{
  public:
    angle::Result getAttachmentRenderTarget(const gl::Context *context,
                                            GLenum binding,
                                            const gl::ImageIndex &imageIndex,
                                            GLsizei samples,
                                            FramebufferAttachmentRenderTarget **rtOut) override;

  protected:
    SurfaceVk(const egl::SurfaceState &surfaceState);
    ~SurfaceVk() override;

    void destroy(const egl::Display *display) override;
    // We monitor the staging buffer for changes. This handles staged data from outside this class.
    void onSubjectStateChange(angle::SubjectIndex index, angle::SubjectMessage message) override;

    // size can change with client window resizing
    gl::Extents getSize() const override;

    EGLint mWidth;
    EGLint mHeight;

    RenderTargetVk mColorRenderTarget;
    RenderTargetVk mDepthStencilRenderTarget;
};

class OffscreenSurfaceVk : public SurfaceVk
{
  public:
    OffscreenSurfaceVk(const egl::SurfaceState &surfaceState, vk::Renderer *renderer);
    ~OffscreenSurfaceVk() override;

    egl::Error initialize(const egl::Display *display) override;
    void destroy(const egl::Display *display) override;

    egl::Error unMakeCurrent(const gl::Context *context) override;
    const vk::ImageHelper *getColorImage() const { return &mColorAttachment.image; }

    egl::Error swap(const gl::Context *context, SurfaceSwapFeedback *feedback) override;
    egl::Error postSubBuffer(const gl::Context *context,
                             EGLint x,
                             EGLint y,
                             EGLint width,
                             EGLint height) override;
    egl::Error querySurfacePointerANGLE(EGLint attribute, void **value) override;
    egl::Error bindTexImage(const gl::Context *context,
                            gl::Texture *texture,
                            EGLint buffer) override;
    egl::Error releaseTexImage(const gl::Context *context, EGLint buffer) override;
    egl::Error getSyncValues(EGLuint64KHR *ust, EGLuint64KHR *msc, EGLuint64KHR *sbc) override;
    egl::Error getMscRate(EGLint *numerator, EGLint *denominator) override;
    void setSwapInterval(const egl::Display *display, EGLint interval) override;

    EGLint isPostSubBufferSupported() const override;
    EGLint getSwapBehavior() const override;

    angle::Result initializeContents(const gl::Context *context,
                                     GLenum binding,
                                     const gl::ImageIndex &imageIndex) override;

    vk::ImageHelper *getColorAttachmentImage();

    egl::Error lockSurface(const egl::Display *display,
                           EGLint usageHint,
                           bool preservePixels,
                           uint8_t **bufferPtrOut,
                           EGLint *bufferPitchOut) override;
    egl::Error unlockSurface(const egl::Display *display, bool preservePixels) override;
    EGLint origin() const override;

    egl::Error attachToFramebuffer(const gl::Context *context,
                                   gl::Framebuffer *framebuffer) override;
    egl::Error detachFromFramebuffer(const gl::Context *context,
                                     gl::Framebuffer *framebuffer) override;

  protected:
    struct AttachmentImage final : angle::NonCopyable
    {
        AttachmentImage(SurfaceVk *surfaceVk);
        ~AttachmentImage();

        angle::Result initialize(DisplayVk *displayVk,
                                 EGLint width,
                                 EGLint height,
                                 const vk::Format &vkFormat,
                                 GLint samples,
                                 bool isRobustResourceInitEnabled,
                                 bool hasProtectedContent);

        void destroy(const egl::Display *display);

        vk::ImageHelper image;
        vk::ImageViewHelper imageViews;
        angle::ObserverBinding imageObserverBinding;
    };

    virtual angle::Result initializeImpl(DisplayVk *displayVk);

    AttachmentImage mColorAttachment;
    AttachmentImage mDepthStencilAttachment;

    // EGL_KHR_lock_surface3
    vk::BufferHelper mLockBufferHelper;
};

// Data structures used in WindowSurfaceVk
namespace impl
{
static constexpr size_t kSwapHistorySize = 2;

// Old swapchain and associated present fences/semaphores that need to be scheduled for
// recycling/destruction when appropriate.
struct SwapchainCleanupData : angle::NonCopyable
{
    SwapchainCleanupData();
    SwapchainCleanupData(SwapchainCleanupData &&other);
    ~SwapchainCleanupData();

    // Fences must not be empty (VK_EXT_swapchain_maintenance1 is supported).
    VkResult getFencesStatus(VkDevice device) const;
    // Waits fences if any. Use before force destroying the swapchain.
    void waitFences(VkDevice device, uint64_t timeout) const;
    void destroy(VkDevice device,
                 vk::Recycler<vk::Fence> *fenceRecycler,
                 vk::Recycler<vk::Semaphore> *semaphoreRecycler);

    // The swapchain to be destroyed.
    VkSwapchainKHR swapchain = VK_NULL_HANDLE;
    // Any present fences/semaphores that were pending recycle at the time the swapchain was
    // recreated will be scheduled for recycling at the same time as the swapchain's destruction.
    // fences must be in the present operation order.
    std::vector<vk::Fence> fences;
    std::vector<vk::Semaphore> semaphores;
};

// Each present operation is associated with a wait semaphore.  To know when that semaphore can be
// recycled, a swapSerial is used.  When that swapSerial is finished, the semaphore used in the
// previous present operation involving imageIndex can be recycled.  See doc/PresentSemaphores.md
// for details.
// When VK_EXT_swapchain_maintenance1 is supported, present fence is used instead of the swapSerial.
//
// Old swapchains are scheduled to be destroyed at the same time as the last wait semaphore used to
// present an image to the old swapchains can be recycled (only relevant when
// VK_EXT_swapchain_maintenance1 is not supported).
struct ImagePresentOperation : angle::NonCopyable
{
    ImagePresentOperation();
    ImagePresentOperation(ImagePresentOperation &&other);
    ImagePresentOperation &operator=(ImagePresentOperation &&other);
    ~ImagePresentOperation();

    void destroy(VkDevice device,
                 vk::Recycler<vk::Fence> *fenceRecycler,
                 vk::Recycler<vk::Semaphore> *semaphoreRecycler);

    // fence is only used when VK_EXT_swapchain_maintenance1 is supported.
    vk::Fence fence;
    vk::Semaphore semaphore;

    // Below members only relevant when VK_EXT_swapchain_maintenance1 is not supported.
    // Used to associate a swapSerial with the previous present operation of the image.
    uint32_t imageIndex;
    QueueSerial queueSerial;
    std::deque<SwapchainCleanupData> oldSwapchains;
};

// Swapchain images and their associated objects.
struct SwapchainImage : angle::NonCopyable
{
    SwapchainImage();
    SwapchainImage(SwapchainImage &&other);
    ~SwapchainImage();

    std::unique_ptr<vk::ImageHelper> image;
    vk::ImageViewHelper imageViews;
    vk::Framebuffer framebuffer;
    vk::Framebuffer fetchFramebuffer;

    uint64_t frameNumber = 0;
};

enum class ImageAcquireState
{
    Unacquired,
    NeedToProcessResult,
    Ready,
};

// Associated data for a call to vkAcquireNextImageKHR without necessarily holding the share group
// and global locks but ONLY from a thread where Surface is current.
struct UnlockedAcquireData : angle::NonCopyable
{
    // Given that the CPU is throttled after a number of swaps, there is an upper bound to the
    // number of semaphores that are used to acquire swapchain images, and that is
    // kSwapHistorySize+1:
    //
    //             Unrelated submission in     Submission as part of
    //               the middle of frame          buffer swap
    //                              |                 |
    //                              V                 V
    //     Frame i:     ... ANI ... QS (fence Fa) ... QS (Fence Fb) QP Wait(..)
    //     Frame i+1:   ... ANI ... QS (fence Fc) ... QS (Fence Fd) QP Wait(..) <--\
    //     Frame i+2:   ... ANI ... QS (fence Fe) ... QS (Fence Ff) QP Wait(Fb)    |
    //                                                                  ^          |
    //                                                                  |          |
    //                                                           CPU throttling    |
    //                                                                             |
    //                               Note: app should throttle itself here (equivalent of Wait(Fb))
    //
    // In frame i+2 (2 is kSwapHistorySize), ANGLE waits on fence Fb which means that the semaphore
    // used for Frame i's ANI can be reused (because Fb-is-signalled implies Fa-is-signalled).
    // Before this wait, there were three acquire semaphores in use corresponding to frames i, i+1
    // and i+2.  Frame i+3 can reuse the semaphore of frame i.
    angle::CircularBuffer<vk::Semaphore, impl::kSwapHistorySize + 1> acquireImageSemaphores;
};

struct UnlockedAcquireResult : angle::NonCopyable
{
    // The result of the call to vkAcquireNextImageKHR.
    VkResult result = VK_SUCCESS;

    // Semaphore to signal.
    VkSemaphore acquireSemaphore = VK_NULL_HANDLE;

    // Image index that was acquired
    uint32_t imageIndex = std::numeric_limits<uint32_t>::max();
};

struct ImageAcquireOperation : angle::NonCopyable
{
    // Initially image needs to be acquired.
    ImageAcquireState state = ImageAcquireState::Unacquired;

    // No synchronization is necessary when making the vkAcquireNextImageKHR call since it is ONLY
    // possible on a thread where Surface is current.
    UnlockedAcquireData unlockedAcquireData;
    UnlockedAcquireResult unlockedAcquireResult;
};

enum class SurfaceSizeState
{
    InvalidSwapchain,
    Unresolved,
    Resolved,
};
}  // namespace impl

class WindowSurfaceVk : public SurfaceVk
{
  public:
    WindowSurfaceVk(const egl::SurfaceState &surfaceState, EGLNativeWindowType window);
    ~WindowSurfaceVk() override;

    void destroy(const egl::Display *display) override;

    egl::Error initialize(const egl::Display *display) override;

    egl::Error unMakeCurrent(const gl::Context *context) override;

    angle::Result getAttachmentRenderTarget(const gl::Context *context,
                                            GLenum binding,
                                            const gl::ImageIndex &imageIndex,
                                            GLsizei samples,
                                            FramebufferAttachmentRenderTarget **rtOut) override;
    egl::Error prepareSwap(const gl::Context *context) override;
    egl::Error swap(const gl::Context *context, SurfaceSwapFeedback *feedback) override;
    egl::Error swapWithDamage(const gl::Context *context,
                              const EGLint *rects,
                              EGLint n_rects,
                              SurfaceSwapFeedback *feedback) override;
    egl::Error postSubBuffer(const gl::Context *context,
                             EGLint x,
                             EGLint y,
                             EGLint width,
                             EGLint height) override;
    egl::Error querySurfacePointerANGLE(EGLint attribute, void **value) override;
    egl::Error bindTexImage(const gl::Context *context,
                            gl::Texture *texture,
                            EGLint buffer) override;
    egl::Error releaseTexImage(const gl::Context *context, EGLint buffer) override;
    egl::Error getSyncValues(EGLuint64KHR *ust, EGLuint64KHR *msc, EGLuint64KHR *sbc) override;
    egl::Error getMscRate(EGLint *numerator, EGLint *denominator) override;
    void setSwapInterval(const egl::Display *display, EGLint interval) override;

    // Explicitly resolves surface size to use before state synchronization (e.g. validation).
    angle::Result ensureSizeResolved(const gl::Context *context) final;
    gl::Extents getSize() const final;

    // Unresolved Surface size until render target is first accessed (e.g. after draw).
    egl::Error getUserSize(const egl::Display *display, EGLint *width, EGLint *height) const final;

    EGLint isPostSubBufferSupported() const override;
    EGLint getSwapBehavior() const override;

    angle::Result initializeContents(const gl::Context *context,
                                     GLenum binding,
                                     const gl::ImageIndex &imageIndex) override;

    vk::Framebuffer &chooseFramebuffer();

    angle::Result getCurrentFramebuffer(ContextVk *context,
                                        vk::FramebufferFetchMode fetchMode,
                                        const vk::RenderPass &compatibleRenderPass,
                                        vk::Framebuffer *framebufferOut);

    VkSurfaceTransformFlagBitsKHR getPreTransform() const
    {
        if (mEmulatedPreTransform != VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)
        {
            return mEmulatedPreTransform;
        }
        return mPreTransform;
    }

    egl::Error setAutoRefreshEnabled(bool enabled) override;

    egl::Error getBufferAge(const gl::Context *context, EGLint *age) override;

    egl::Error setRenderBuffer(EGLint renderBuffer) override;
    bool supportsSingleRenderBuffer() const override;

    bool isSharedPresentMode() const { return IsSharedPresentMode(mSwapchainPresentMode); }

    bool isSharedPresentModeDesired() const
    {
        return IsSharedPresentMode(getDesiredSwapchainPresentMode());
    }

    egl::Error lockSurface(const egl::Display *display,
                           EGLint usageHint,
                           bool preservePixels,
                           uint8_t **bufferPtrOut,
                           EGLint *bufferPitchOut) override;
    egl::Error unlockSurface(const egl::Display *display, bool preservePixels) override;
    EGLint origin() const override;

    egl::Error attachToFramebuffer(const gl::Context *context,
                                   gl::Framebuffer *framebuffer) override;
    egl::Error detachFromFramebuffer(const gl::Context *context,
                                     gl::Framebuffer *framebuffer) override;

    angle::Result onSharedPresentContextFlush(ContextVk *contextVk);

    bool hasStagedUpdates() const;

    void setTimestampsEnabled(bool enabled) override;
    egl::Error setPresentationTime(EGLnsecsANDROID time) override;

    egl::Error getCompressionRate(const egl::Display *display,
                                  const gl::Context *context,
                                  EGLint *rate) override;

  protected:
    angle::Result swapImpl(ContextVk *contextVk,
                           const EGLint *rects,
                           EGLint n_rects,
                           const void *pNextChain,
                           SurfaceSwapFeedback *feedback);

    EGLNativeWindowType mNativeWindowType;
    VkSurfaceKHR mSurface;
    VkBool32 mSupportsProtectedSwapchain;

  private:
    // Present modes that are compatible with the current mode.  If mDesiredSwapchainPresentMode is
    // in this list, mode switch can happen without the need to recreate the swapchain.
    // There are currently only 6 possible present modes but vector is bigger for a workaround.
    static constexpr uint32_t kCompatiblePresentModesSize = 10;
    using CompatiblePresentModes =
        angle::FixedVector<VkPresentModeKHR, kCompatiblePresentModesSize>;

    static bool IsSharedPresentMode(vk::PresentMode presentMode)
    {
        return (presentMode == vk::PresentMode::SharedDemandRefreshKHR ||
                presentMode == vk::PresentMode::SharedContinuousRefreshKHR);
    }

    virtual angle::Result createSurfaceVk(vk::ErrorContext *context)          = 0;
    virtual angle::Result getCurrentWindowSize(vk::ErrorContext *context,
                                               gl::Extents *extentsOut) const = 0;
    virtual angle::Result getWindowVisibility(vk::ErrorContext *context, bool *isVisibleOut) const;

    impl::SurfaceSizeState getSizeState() const;
    void setSizeState(impl::SurfaceSizeState sizeState);
    angle::Result getUserExtentsImpl(vk::ErrorContext *context, VkExtent2D *extentOut) const;

    vk::PresentMode getDesiredSwapchainPresentMode() const;
    void setDesiredSwapchainPresentMode(vk::PresentMode presentMode);
    void setDesiredSwapInterval(EGLint interval);

    angle::Result initializeImpl(DisplayVk *displayVk, bool *anyMatchesOut);
    // Invalidates the swapchain pointer, releases images, and defers acquire next swapchain image.
    void invalidateSwapchain(vk::Renderer *renderer);
    angle::Result recreateSwapchain(vk::ErrorContext *context);
    angle::Result createSwapChain(vk::ErrorContext *context);
    angle::Result collectOldSwapchain(vk::ErrorContext *context, VkSwapchainKHR swapchain);
    angle::Result queryAndAdjustSurfaceCaps(
        vk::ErrorContext *context,
        vk::PresentMode presentMode,
        VkSurfaceCapabilitiesKHR *surfaceCapsOut,
        CompatiblePresentModes *compatiblePresentModesOut) const;
    void adjustSurfaceExtent(VkExtent2D *extent) const;
    // This method will invalidate the swapchain (if needed due to present returning OUT_OF_DATE, or
    // swap interval changing).  Updates the current swapchain present mode.
    angle::Result prepareSwapchainForAcquireNextImage(vk::ErrorContext *context);
    void createSwapchainImages(uint32_t imageCount);
    void releaseSwapchainImages(vk::Renderer *renderer);
    void destroySwapChainImages(DisplayVk *displayVk);
    // Called when a swapchain image whose acquisition was deferred must be acquired or unlocked
    // ANI result processed.  This method will recreate the swapchain (if needed due to surface size
    // changing etc, by calling prepareSwapchainForAcquireNextImage()) and call the
    // acquireNextSwapchainImage().  On some platforms, vkAcquireNextImageKHR() returns OUT_OF_DATE
    // instead of present, which is also recreates the swapchain.  It is scheduled to be called
    // later by deferAcquireNextImage().
    angle::Result doDeferredAcquireNextImage(vk::ErrorContext *context);
    // This method calls vkAcquireNextImageKHR() to acquire the next swapchain image or processes
    // unlocked ANI result, then sets up the acquired image.
    VkResult acquireNextSwapchainImage(vk::ErrorContext *context);
    // This method is called when a swapchain image is presented.  It schedules
    // doDeferredAcquireNextImage() to be called later.
    void deferAcquireNextImage();
    bool skipAcquireNextSwapchainImageForSharedPresentMode() const;

    angle::Result checkSwapchainOutOfDate(vk::ErrorContext *context, VkResult presentResult);
    angle::Result prePresentSubmit(ContextVk *contextVk, const vk::Semaphore &presentSemaphore);
    angle::Result recordPresentLayoutBarrierIfNecessary(ContextVk *contextVk);
    angle::Result present(ContextVk *contextVk,
                          const EGLint *rects,
                          EGLint n_rects,
                          const void *pNextChain,
                          SurfaceSwapFeedback *feedback);

    angle::Result cleanUpPresentHistory(vk::ErrorContext *context);
    angle::Result cleanUpOldSwapchains(vk::ErrorContext *context);

    // Throttle the CPU such that application's logic and command buffer recording doesn't get more
    // than two frame ahead of the frame being rendered (and three frames ahead of the one being
    // presented).  This is a failsafe, as the application should ensure command buffer recording is
    // not ahead of the frame being rendered by *one* frame.
    angle::Result throttleCPU(vk::ErrorContext *context, const QueueSerial &currentSubmitSerial);

    void mergeImageResourceUses();
    // Finish all GPU operations on the surface
    angle::Result finish(vk::ErrorContext *context);

    void updateOverlay(ContextVk *contextVk) const;
    bool overlayHasEnabledWidget(ContextVk *contextVk) const;
    angle::Result drawOverlay(ContextVk *contextVk, impl::SwapchainImage *image) const;

    bool isMultiSampled() const;

    bool supportsPresentMode(vk::PresentMode presentMode) const;

    bool updateColorSpace(DisplayVk *displayVk);

    angle::FormatID getIntendedFormatID(vk::Renderer *renderer);
    angle::FormatID getActualFormatID(vk::Renderer *renderer);

    bool mIsSurfaceSizedBySwapchain;

    // Atomic is to allow update state without necessarily locking the mSizeMutex.
    std::atomic<impl::SurfaceSizeState> mSizeState;
    // Protects mWidth and mHeight against getUserSize() call.
    mutable angle::SimpleMutex mSizeMutex;

    std::vector<vk::PresentMode> mPresentModes;

    VkSwapchainKHR mSwapchain;      // Current swapchain (same as last created or NULL)
    VkSwapchainKHR mLastSwapchain;  // Last created non retired swapchain (or NULL if retired)
    // Cached information used to recreate swapchains.
    vk::PresentMode mSwapchainPresentMode;                      // Current swapchain mode
    std::atomic<vk::PresentMode> mDesiredSwapchainPresentMode;  // Desired swapchain mode
    uint32_t mMinImageCount;
    VkSurfaceTransformFlagBitsKHR mPreTransform;
    VkSurfaceTransformFlagBitsKHR mEmulatedPreTransform;
    VkCompositeAlphaFlagBitsKHR mCompositeAlpha;
    VkColorSpaceKHR mSurfaceColorSpace;
    VkImageCompressionFlagBitsEXT mCompressionFlags;
    VkImageCompressionFixedRateFlagsEXT mFixedRateFlags;

    CompatiblePresentModes mCompatiblePresentModes;

    // A circular buffer that stores the serial of the submission fence of the context on every
    // swap. The CPU is throttled by waiting for the 2nd previous serial to finish.  This should
    // normally be a no-op, as the application should pace itself to avoid input lag, and is
    // implemented in ANGLE as a fail safe.  Removing this throttling requires untangling it from
    // acquire semaphore recycling (see mAcquireImageSemaphores above)
    angle::CircularBuffer<QueueSerial, impl::kSwapHistorySize> mSwapHistory;

    // The previous swapchain which needs to be scheduled for destruction when appropriate.  This
    // will be done when the first image of the current swapchain is presented or when fences are
    // signaled (when VK_EXT_swapchain_maintenance1 is supported).  If there were older swapchains
    // pending destruction when the swapchain is recreated, they will accumulate and be destroyed
    // with the previous swapchain.
    //
    // Note that if the user resizes the window such that the swapchain is recreated every frame,
    // this array can go grow indefinitely.
    std::deque<impl::SwapchainCleanupData> mOldSwapchains;

    std::vector<impl::SwapchainImage> mSwapchainImages;
    std::vector<angle::ObserverBinding> mSwapchainImageBindings;
    uint32_t mCurrentSwapchainImageIndex;

    // There is no direct signal from Vulkan regarding when a Present semaphore can be be reused.
    // During window resizing when swapchains are recreated every frame, the number of in-flight
    // present semaphores can grow indefinitely.  See doc/PresentSemaphores.md.
    vk::Recycler<vk::Semaphore> mPresentSemaphoreRecycler;
    // Fences are associated with present semaphores to know when they can be recycled.
    vk::Recycler<vk::Fence> mPresentFenceRecycler;

    // The presentation history, used to recycle semaphores and destroy old swapchains.
    std::deque<impl::ImagePresentOperation> mPresentHistory;

    // Depth/stencil image.  Possibly multisampled.
    vk::ImageHelper mDepthStencilImage;
    vk::ImageViewHelper mDepthStencilImageViews;
    angle::ObserverBinding mDepthStencilImageBinding;

    // Multisample color image, view and framebuffer, if multisampling enabled.
    vk::ImageHelper mColorImageMS;
    vk::ImageViewHelper mColorImageMSViews;
    angle::ObserverBinding mColorImageMSBinding;
    vk::Framebuffer mFramebufferMS;

    impl::ImageAcquireOperation mAcquireOperation;

    // EGL_EXT_buffer_age: Track frame count.
    uint64_t mFrameCount;
    // EGL_ANDROID_presentation_time: Next frame's id and presentation time
    // used for VK_GOOGLE_display_timing.
    uint32_t mPresentID;
    std::optional<EGLnsecsANDROID> mDesiredPresentTime;

    // EGL_KHR_lock_surface3
    vk::BufferHelper mLockBufferHelper;

    // EGL_KHR_partial_update
    uint64_t mBufferAgeQueryFrameNumber;

    // GL_EXT_shader_framebuffer_fetch
    vk::FramebufferFetchMode mFramebufferFetchMode = vk::FramebufferFetchMode::None;
};

}  // namespace rx

#endif  // LIBANGLE_RENDERER_VULKAN_SURFACEVK_H_
