//
// Copyright 2002 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.
//

// Framebuffer.h: Defines the gl::Framebuffer class. Implements GL framebuffer
// objects and related functionality. [OpenGL ES 2.0.24] section 4.4 page 105.

#ifndef LIBANGLE_FRAMEBUFFER_H_
#define LIBANGLE_FRAMEBUFFER_H_

#include <vector>

#include "common/FixedVector.h"
#include "common/Optional.h"
#include "common/angleutils.h"
#include "libANGLE/Constants.h"
#include "libANGLE/Debug.h"
#include "libANGLE/Error.h"
#include "libANGLE/FramebufferAttachment.h"
#include "libANGLE/Observer.h"
#include "libANGLE/RefCountObject.h"

namespace rx
{
class GLImplFactory;
class FramebufferImpl;
class RenderbufferImpl;
class SurfaceImpl;
}  // namespace rx

namespace egl
{
class Display;
class Surface;
}  // namespace egl

namespace gl
{
struct Caps;
class Context;
struct Extensions;
class Framebuffer;
class ImageIndex;
struct Rectangle;
class Renderbuffer;
class State;
class Texture;
class TextureCapsMap;

enum class AttachmentSampleType
{
    // The sample count of the actual resource
    Resource,
    // If render_to_texture is used, this is the sample count of the multisampled
    // texture that is created behind the scenes.
    Emulated
};

class FramebufferState final : angle::NonCopyable
{
  public:
    explicit FramebufferState(ContextID owningContextID);
    FramebufferState(const Caps &caps, FramebufferID id, ContextID owningContextID);
    ~FramebufferState();

    const std::string &getLabel() const;
    size_t getReadIndex() const;

    const FramebufferAttachment *getAttachment(const Context *context, GLenum attachment) const;
    const FramebufferAttachment *getReadAttachment() const;
    const FramebufferAttachment *getFirstNonNullAttachment() const;
    const FramebufferAttachment *getFirstColorAttachment() const;
    const FramebufferAttachment *getDepthOrStencilAttachment() const;
    const FramebufferAttachment *getStencilOrDepthStencilAttachment() const;
    const FramebufferAttachment *getColorAttachment(size_t colorAttachment) const;
    const FramebufferAttachment *getDepthAttachment() const;
    const FramebufferAttachment *getStencilAttachment() const;
    const FramebufferAttachment *getDepthStencilAttachment() const;
    const FramebufferAttachment *getReadPixelsAttachment(GLenum readFormat) const;

    const std::vector<GLenum> &getDrawBufferStates() const { return mDrawBufferStates; }
    DrawBufferMask getEnabledDrawBuffers() const { return mEnabledDrawBuffers; }
    GLenum getReadBufferState() const { return mReadBufferState; }
    const std::vector<FramebufferAttachment> &getColorAttachments() const
    {
        return mColorAttachments;
    }

    bool attachmentsHaveSameDimensions() const;
    bool hasSeparateDepthAndStencilAttachments() const;
    bool colorAttachmentsAreUniqueImages() const;
    Box getDimensions() const;
    Extents getExtents() const;

    const FramebufferAttachment *getDrawBuffer(size_t drawBufferIdx) const;
    size_t getDrawBufferCount() const;

    GLint getDefaultWidth() const { return mDefaultWidth; }
    GLint getDefaultHeight() const { return mDefaultHeight; }
    GLint getDefaultSamples() const { return mDefaultSamples; }
    bool getDefaultFixedSampleLocations() const { return mDefaultFixedSampleLocations; }
    GLint getDefaultLayers() const { return mDefaultLayers; }

    bool hasDepth() const;
    bool hasStencil() const;

    bool isMultiview() const;

    ANGLE_INLINE GLsizei getNumViews() const
    {
        const FramebufferAttachment *attachment = getFirstNonNullAttachment();
        if (attachment == nullptr)
        {
            return FramebufferAttachment::kDefaultNumViews;
        }
        return attachment->getNumViews();
    }

    GLint getBaseViewIndex() const;

    FramebufferID id() const { return mId; }

    bool isDefault() const;

    bool hasDepthStencilFeedbackLoop() const
    {
        return mDepthBufferFeedbackLoop || mStencilBufferFeedbackLoop;
    }

    const gl::Offset &getSurfaceTextureOffset() const { return mSurfaceTextureOffset; }

  private:
    const FramebufferAttachment *getWebGLDepthStencilAttachment() const;
    const FramebufferAttachment *getWebGLDepthAttachment() const;
    const FramebufferAttachment *getWebGLStencilAttachment() const;

    // Returns true if there was a change in this attachments feedback-loop-ness.
    bool updateAttachmentFeedbackLoopAndReturnIfChanged(size_t dirtyBit);
    void updateHasRenderingFeedbackLoop();

    friend class Framebuffer;

    FramebufferID mId;
    ContextID mOwningContextID;
    std::string mLabel;

    std::vector<FramebufferAttachment> mColorAttachments;
    FramebufferAttachment mDepthAttachment;
    FramebufferAttachment mStencilAttachment;

    std::vector<GLenum> mDrawBufferStates;
    GLenum mReadBufferState;
    DrawBufferMask mEnabledDrawBuffers;
    ComponentTypeMask mDrawBufferTypeMask;

    GLint mDefaultWidth;
    GLint mDefaultHeight;
    GLint mDefaultSamples;
    bool mDefaultFixedSampleLocations;
    GLint mDefaultLayers;

    // It's necessary to store all this extra state so we can restore attachments
    // when DEPTH_STENCIL/DEPTH/STENCIL is unbound in WebGL 1.
    FramebufferAttachment mWebGLDepthStencilAttachment;
    FramebufferAttachment mWebGLDepthAttachment;
    FramebufferAttachment mWebGLStencilAttachment;
    bool mWebGLDepthStencilConsistent;

    // Tracks rendering feedback loops.
    DrawBufferMask mDrawBufferFeedbackLoops;
    bool mDepthBufferFeedbackLoop;
    bool mStencilBufferFeedbackLoop;
    bool mHasRenderingFeedbackLoop;

    // Tracks if we need to initialize the resources for each attachment.
    angle::BitSet<IMPLEMENTATION_MAX_FRAMEBUFFER_ATTACHMENTS + 2> mResourceNeedsInit;

    bool mDefaultFramebufferReadAttachmentInitialized;
    FramebufferAttachment mDefaultFramebufferReadAttachment;

    gl::Offset mSurfaceTextureOffset;
};

class Framebuffer final : public angle::ObserverInterface,
                          public LabeledObject,
                          public angle::Subject
{
  public:
    // Constructor to build application-defined framebuffers
    Framebuffer(const Caps &caps,
                rx::GLImplFactory *factory,
                FramebufferID id,
                ContextID owningContextID);
    // Constructor to build default framebuffers for a surface and context pair
    Framebuffer(const Context *context, egl::Surface *surface, egl::Surface *readSurface);
    // Constructor to build a fake default framebuffer when surfaceless
    Framebuffer(const Context *context, rx::GLImplFactory *factory, egl::Surface *readSurface);

    ~Framebuffer() override;
    void onDestroy(const Context *context);

    void setReadSurface(const Context *context, egl::Surface *readSurface);
    void setLabel(const Context *context, const std::string &label) override;
    const std::string &getLabel() const override;

    rx::FramebufferImpl *getImplementation() const { return mImpl; }

    FramebufferID id() const { return mState.mId; }

    void setAttachment(const Context *context,
                       GLenum type,
                       GLenum binding,
                       const ImageIndex &textureIndex,
                       FramebufferAttachmentObject *resource);
    void setAttachmentMultisample(const Context *context,
                                  GLenum type,
                                  GLenum binding,
                                  const ImageIndex &textureIndex,
                                  FramebufferAttachmentObject *resource,
                                  GLsizei samples);
    void setAttachmentMultiview(const Context *context,
                                GLenum type,
                                GLenum binding,
                                const ImageIndex &textureIndex,
                                FramebufferAttachmentObject *resource,
                                GLsizei numViews,
                                GLint baseViewIndex);
    void resetAttachment(const Context *context, GLenum binding);

    bool detachTexture(const Context *context, TextureID texture);
    bool detachRenderbuffer(const Context *context, RenderbufferID renderbuffer);

    const FramebufferAttachment *getColorAttachment(size_t colorAttachment) const;
    const FramebufferAttachment *getDepthAttachment() const;
    const FramebufferAttachment *getStencilAttachment() const;
    const FramebufferAttachment *getDepthStencilAttachment() const;
    const FramebufferAttachment *getDepthOrStencilAttachment() const;
    const FramebufferAttachment *getStencilOrDepthStencilAttachment() const;
    const FramebufferAttachment *getReadColorAttachment() const;
    GLenum getReadColorAttachmentType() const;
    const FramebufferAttachment *getFirstColorAttachment() const;
    const FramebufferAttachment *getFirstNonNullAttachment() const;

    const std::vector<FramebufferAttachment> &getColorAttachments() const
    {
        return mState.mColorAttachments;
    }

    const FramebufferState &getState() const { return mState; }

    const FramebufferAttachment *getAttachment(const Context *context, GLenum attachment) const;
    bool isMultiview() const;
    bool readDisallowedByMultiview() const;
    GLsizei getNumViews() const;
    GLint getBaseViewIndex() const;
    Extents getExtents() const;

    size_t getDrawbufferStateCount() const;
    GLenum getDrawBufferState(size_t drawBuffer) const;
    const std::vector<GLenum> &getDrawBufferStates() const;
    void setDrawBuffers(size_t count, const GLenum *buffers);
    const FramebufferAttachment *getDrawBuffer(size_t drawBuffer) const;
    ComponentType getDrawbufferWriteType(size_t drawBuffer) const;
    ComponentTypeMask getDrawBufferTypeMask() const;
    DrawBufferMask getDrawBufferMask() const;
    bool hasEnabledDrawBuffer() const;

    GLenum getReadBufferState() const;
    void setReadBuffer(GLenum buffer);

    size_t getNumColorAttachments() const;
    bool hasDepth() const;
    bool hasStencil() const;

    bool usingExtendedDrawBuffers() const;

    // This method calls checkStatus.
    int getSamples(const Context *context) const;
    int getResourceSamples(const Context *context) const;

    angle::Result getSamplePosition(const Context *context, size_t index, GLfloat *xy) const;

    GLint getDefaultWidth() const;
    GLint getDefaultHeight() const;
    GLint getDefaultSamples() const;
    bool getDefaultFixedSampleLocations() const;
    GLint getDefaultLayers() const;
    void setDefaultWidth(const Context *context, GLint defaultWidth);
    void setDefaultHeight(const Context *context, GLint defaultHeight);
    void setDefaultSamples(const Context *context, GLint defaultSamples);
    void setDefaultFixedSampleLocations(const Context *context, bool defaultFixedSampleLocations);
    void setDefaultLayers(GLint defaultLayers);

    void invalidateCompletenessCache();
    ANGLE_INLINE bool cachedStatusValid() { return mCachedStatus.valid(); }

    ANGLE_INLINE GLenum checkStatus(const Context *context) const
    {
        // The default framebuffer is always complete except when it is surfaceless in which
        // case it is always unsupported.
        ASSERT(!isDefault() || mCachedStatus.valid());
        if (isDefault() || (!hasAnyDirtyBit() && mCachedStatus.valid()))
        {
            return mCachedStatus.value();
        }

        return checkStatusImpl(context);
    }

    // For when we don't want to check completeness in getSamples().
    int getCachedSamples(const Context *context, AttachmentSampleType sampleType) const;

    // Helper for checkStatus == GL_FRAMEBUFFER_COMPLETE.
    ANGLE_INLINE bool isComplete(const Context *context) const
    {
        return (checkStatus(context) == GL_FRAMEBUFFER_COMPLETE);
    }

    bool hasValidDepthStencil() const;

    // Returns the offset into the texture backing the default framebuffer's surface if any. Returns
    // zero offset otherwise.  The renderer will apply the offset to scissor and viewport rects used
    // for draws, clears, and blits.
    const gl::Offset &getSurfaceTextureOffset() const;

    angle::Result discard(const Context *context, size_t count, const GLenum *attachments);
    angle::Result invalidate(const Context *context, size_t count, const GLenum *attachments);
    angle::Result invalidateSub(const Context *context,
                                size_t count,
                                const GLenum *attachments,
                                const Rectangle &area);

    angle::Result clear(const Context *context, GLbitfield mask);
    angle::Result clearBufferfv(const Context *context,
                                GLenum buffer,
                                GLint drawbuffer,
                                const GLfloat *values);
    angle::Result clearBufferuiv(const Context *context,
                                 GLenum buffer,
                                 GLint drawbuffer,
                                 const GLuint *values);
    angle::Result clearBufferiv(const Context *context,
                                GLenum buffer,
                                GLint drawbuffer,
                                const GLint *values);
    angle::Result clearBufferfi(const Context *context,
                                GLenum buffer,
                                GLint drawbuffer,
                                GLfloat depth,
                                GLint stencil);

    GLenum getImplementationColorReadFormat(const Context *context);
    GLenum getImplementationColorReadType(const Context *context);

    angle::Result readPixels(const Context *context,
                             const Rectangle &area,
                             GLenum format,
                             GLenum type,
                             const PixelPackState &pack,
                             Buffer *packBuffer,
                             void *pixels);

    angle::Result blit(const Context *context,
                       const Rectangle &sourceArea,
                       const Rectangle &destArea,
                       GLbitfield mask,
                       GLenum filter);
    bool isDefault() const { return mState.isDefault(); }

    enum DirtyBitType : size_t
    {
        DIRTY_BIT_COLOR_ATTACHMENT_0,
        DIRTY_BIT_COLOR_ATTACHMENT_MAX =
            DIRTY_BIT_COLOR_ATTACHMENT_0 + IMPLEMENTATION_MAX_FRAMEBUFFER_ATTACHMENTS,
        DIRTY_BIT_DEPTH_ATTACHMENT = DIRTY_BIT_COLOR_ATTACHMENT_MAX,
        DIRTY_BIT_STENCIL_ATTACHMENT,
        DIRTY_BIT_COLOR_BUFFER_CONTENTS_0,
        DIRTY_BIT_COLOR_BUFFER_CONTENTS_MAX =
            DIRTY_BIT_COLOR_BUFFER_CONTENTS_0 + IMPLEMENTATION_MAX_FRAMEBUFFER_ATTACHMENTS,
        DIRTY_BIT_DEPTH_BUFFER_CONTENTS = DIRTY_BIT_COLOR_BUFFER_CONTENTS_MAX,
        DIRTY_BIT_STENCIL_BUFFER_CONTENTS,
        DIRTY_BIT_DRAW_BUFFERS,
        DIRTY_BIT_READ_BUFFER,
        DIRTY_BIT_DEFAULT_WIDTH,
        DIRTY_BIT_DEFAULT_HEIGHT,
        DIRTY_BIT_DEFAULT_SAMPLES,
        DIRTY_BIT_DEFAULT_FIXED_SAMPLE_LOCATIONS,
        DIRTY_BIT_DEFAULT_LAYERS,
        DIRTY_BIT_UNKNOWN,
        DIRTY_BIT_MAX = DIRTY_BIT_UNKNOWN
    };

    using DirtyBits = angle::BitSet<DIRTY_BIT_MAX>;
    bool hasAnyDirtyBit() const { return mDirtyBits.any(); }

    DrawBufferMask getActiveFloat32ColorAttachmentDrawBufferMask() const
    {
        return mFloat32ColorAttachmentBits & getDrawBufferMask();
    }

    bool hasResourceThatNeedsInit() const { return mState.mResourceNeedsInit.any(); }

    angle::Result syncState(const Context *context, GLenum framebufferBinding) const;

    // Observer implementation
    void onSubjectStateChange(angle::SubjectIndex index, angle::SubjectMessage message) override;

    bool hasRenderingFeedbackLoop() const { return mState.mHasRenderingFeedbackLoop; }
    bool formsCopyingFeedbackLoopWith(TextureID copyTextureID,
                                      GLint copyTextureLevel,
                                      GLint copyTextureLayer) const;

    angle::Result ensureClearAttachmentsInitialized(const Context *context, GLbitfield mask);
    angle::Result ensureClearBufferAttachmentsInitialized(const Context *context,
                                                          GLenum buffer,
                                                          GLint drawbuffer);
    angle::Result ensureDrawAttachmentsInitialized(const Context *context);

    // Conservatively initializes both read color and depth. Blit can access the depth buffer.
    angle::Result ensureReadAttachmentsInitialized(const Context *context);
    Box getDimensions() const;

    static const FramebufferID kDefaultDrawFramebufferHandle;

    rx::Serial serial() const { return mSerial; }

  private:
    bool detachResourceById(const Context *context, GLenum resourceType, GLuint resourceId);
    bool detachMatchingAttachment(const Context *context,
                                  FramebufferAttachment *attachment,
                                  GLenum matchType,
                                  GLuint matchId);
    GLenum checkStatusWithGLFrontEnd(const Context *context) const;
    GLenum checkStatusImpl(const Context *context) const;
    void setAttachment(const Context *context,
                       GLenum type,
                       GLenum binding,
                       const ImageIndex &textureIndex,
                       FramebufferAttachmentObject *resource,
                       GLsizei numViews,
                       GLuint baseViewIndex,
                       bool isMultiview,
                       GLsizei samples);
    void commitWebGL1DepthStencilIfConsistent(const Context *context,
                                              GLsizei numViews,
                                              GLuint baseViewIndex,
                                              bool isMultiview,
                                              GLsizei samples);
    void setAttachmentImpl(const Context *context,
                           GLenum type,
                           GLenum binding,
                           const ImageIndex &textureIndex,
                           FramebufferAttachmentObject *resource,
                           GLsizei numViews,
                           GLuint baseViewIndex,
                           bool isMultiview,
                           GLsizei samples);
    void updateAttachment(const Context *context,
                          FramebufferAttachment *attachment,
                          size_t dirtyBit,
                          angle::ObserverBinding *onDirtyBinding,
                          GLenum type,
                          GLenum binding,
                          const ImageIndex &textureIndex,
                          FramebufferAttachmentObject *resource,
                          GLsizei numViews,
                          GLuint baseViewIndex,
                          bool isMultiview,
                          GLsizei samples);

    void markDrawAttachmentsInitialized(bool color, bool depth, bool stencil);
    void markBufferInitialized(GLenum bufferType, GLint bufferIndex);
    angle::Result ensureBufferInitialized(const Context *context,
                                          GLenum bufferType,
                                          GLint bufferIndex);

    // Checks that we have a partially masked clear:
    // * some color channels are masked out
    // * some stencil values are masked out
    // * scissor test partially overlaps the framebuffer
    bool partialClearNeedsInit(const Context *context, bool color, bool depth, bool stencil);
    bool partialBufferClearNeedsInit(const Context *context, GLenum bufferType);

    FramebufferAttachment *getAttachmentFromSubjectIndex(angle::SubjectIndex index);

    ANGLE_INLINE void updateFloat32ColorAttachmentBits(size_t index,
                                                       const gl::InternalFormat *format)
    {
        mFloat32ColorAttachmentBits.set(index, format->type == GL_FLOAT);
    }

    rx::Serial mSerial;
    FramebufferState mState;
    rx::FramebufferImpl *mImpl;

    mutable Optional<GLenum> mCachedStatus;
    std::vector<angle::ObserverBinding> mDirtyColorAttachmentBindings;
    angle::ObserverBinding mDirtyDepthAttachmentBinding;
    angle::ObserverBinding mDirtyStencilAttachmentBinding;

    mutable DirtyBits mDirtyBits;
    DrawBufferMask mFloat32ColorAttachmentBits;

    // The dirty bits guard is checked when we get a dependent state change message. We verify that
    // we don't set a dirty bit that isn't already set, when inside the dirty bits syncState.
    mutable Optional<DirtyBits> mDirtyBitsGuard;
};

}  // namespace gl

#endif  // LIBANGLE_FRAMEBUFFER_H_
