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

// PixelLocalStorage.h: Defines the renderer-agnostic container classes
// gl::PixelLocalStorage and gl::PixelLocalStoragePlane for
// ANGLE_shader_pixel_local_storage.

#ifndef LIBANGLE_PIXEL_LOCAL_STORAGE_H_
#define LIBANGLE_PIXEL_LOCAL_STORAGE_H_

#include "GLSLANG/ShaderLang.h"
#include "angle_gl.h"
#include "libANGLE/ImageIndex.h"
#include "libANGLE/angletypes.h"

namespace gl
{

struct Caps;
class Context;
class Texture;

// Holds the configuration of an ANGLE_shader_pixel_local_storage plane.
//
// Unlike normal framebuffer attachments, pixel local storage planes don't take effect until the
// application calls glBeginPixelLocalStorageANGLE, and the manner in which they take effect is
// highly dependent on the backend implementation. A PixelLocalStoragePlane is just a plain data
// description what to set up later once PLS is enabled.
class PixelLocalStoragePlane : angle::NonCopyable, public angle::ObserverInterface
{
  public:
    PixelLocalStoragePlane();
    ~PixelLocalStoragePlane() override;

    // Called when the context is lost or destroyed. Causes this class to clear its GL object
    // handles.
    void onContextObjectsLost();

    void deinitialize(Context *);
    void setMemoryless(Context *, GLenum internalformat);
    void setTextureBacked(Context *, Texture *, int level, int layer);
    void onSubjectStateChange(angle::SubjectIndex, angle::SubjectMessage) override;

    // Returns true if the plane is deinitialized, either explicitly or implicitly via deleting the
    // texture that was attached to it.
    bool isDeinitialized() const;

    GLenum getInternalformat() const { return mInternalformat; }
    bool isMemoryless() const { return mMemoryless; }
    TextureID getTextureID() const { return mTextureID; }

    // Implements glGetIntegeri_v() for GL_PIXEL_LOCAL_FORMAT_ANGLE,
    // GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, and
    // GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE
    GLint getIntegeri(GLenum target) const;

    // If this plane is texture backed, stores the bound texture image's {width, height, 0} to
    // Extents and returns true. Otherwise returns false, meaning the plane is either deinitialized
    // or memoryless.
    bool getTextureImageExtents(const Context *, Extents *extents) const;

    // Ensures we have an internal backing texture for memoryless planes. In some implementations we
    // need a backing texture even if the plane is memoryless.
    void ensureBackingTextureIfMemoryless(Context *, Extents plsSize);

    // Attaches this plane to the specified color attachment point on the current draw framebuffer.
    void attachToDrawFramebuffer(Context *, GLenum colorAttachment) const;

    // Interface for clearing typed pixel local storage planes.
    class ClearCommands
    {
      public:
        virtual ~ClearCommands() {}
        virtual void clearfv(int target, const GLfloat[]) const = 0;
        virtual void cleariv(int target, const GLint[]) const   = 0;
        virtual void clearuiv(int target, const GLuint[]) const = 0;
    };

    // Issues the approprite command from ClearCommands for this plane's internalformat. Uses the
    // clear state value that corresponds to mInternalFormat, and potentially clamps it to ensure it
    // is representable.
    void issueClearCommand(ClearCommands *, int target, GLenum loadop) const;

    // Binds this PLS plane to a texture image unit for image load/store shader operations.
    void bindToImage(Context *, GLuint unit, bool needsR32Packing) const;

    // Low-level access to the backing texture. The plane must not be memoryless or deinitialized.
    const ImageIndex &getTextureImageIndex() const { return mTextureImageIndex; }
    const Texture *getBackingTexture(const Context *context) const;

    void setClearValuef(const GLfloat value[4]) { memcpy(mClearValuef.data(), value, 4 * 4); }
    void setClearValuei(const GLint value[4]) { memcpy(mClearValuei.data(), value, 4 * 4); }
    void setClearValueui(const GLuint value[4]) { memcpy(mClearValueui.data(), value, 4 * 4); }

    void getClearValuef(GLfloat value[4]) const { memcpy(value, mClearValuef.data(), 4 * 4); }
    void getClearValuei(GLint value[4]) const { memcpy(value, mClearValuei.data(), 4 * 4); }
    void getClearValueui(GLuint value[4]) const { memcpy(value, mClearValueui.data(), 4 * 4); }

    // True if PLS is currently active and this plane is enabled.
    bool isActive() const { return mActive; }
    void markActive(bool active) { mActive = active; }

  private:
    GLenum mInternalformat = GL_NONE;  // GL_NONE if this plane is in a deinitialized state.
    bool mMemoryless       = false;
    TextureID mTextureID   = TextureID();
    ImageIndex mTextureImageIndex;

    // Clear value state.
    std::array<GLfloat, 4> mClearValuef{};
    std::array<GLint, 4> mClearValuei{};
    std::array<GLuint, 4> mClearValueui{};

    // True if PLS is currently active and this plane is enabled.
    bool mActive = false;

    angle::ObserverBinding mTextureObserver;
};

// Manages a collection of PixelLocalStoragePlanes and applies them to ANGLE's GL state.
//
// The main magic of ANGLE_shader_pixel_local_storage happens inside shaders, so we just emulate the
// client API on top of ANGLE's OpenGL ES API for simplicity.
class PixelLocalStorage
{
  public:
    static std::unique_ptr<PixelLocalStorage> Make(const Context *);

    virtual ~PixelLocalStorage();

    // Called when the owning framebuffer is being destroyed.
    void onFramebufferDestroyed(const Context *);

    // Deletes any GL objects that have been allocated for pixel local storage. These can't be
    // cleaned up in the destructor because they require a non-const Context object.
    void deleteContextObjects(Context *);

    const PixelLocalStoragePlane &getPlane(GLint plane) const
    {
        ASSERT(0 <= plane && plane < IMPLEMENTATION_MAX_PIXEL_LOCAL_STORAGE_PLANES);
        return mPlanes[plane];
    }

    const PixelLocalStoragePlane *getPlanes() { return mPlanes.data(); }

    size_t interruptCount() const { return mInterruptCount; }

    // ANGLE_shader_pixel_local_storage API.
    void deinitialize(Context *context, GLint plane) { mPlanes[plane].deinitialize(context); }
    void setMemoryless(Context *context, GLint plane, GLenum internalformat)
    {
        mPlanes[plane].setMemoryless(context, internalformat);
    }
    void setTextureBacked(Context *context, GLint plane, Texture *tex, int level, int layer)
    {
        mPlanes[plane].setTextureBacked(context, tex, level, layer);
    }
    void setClearValuef(GLint plane, const GLfloat val[4]) { mPlanes[plane].setClearValuef(val); }
    void setClearValuei(GLint plane, const GLint val[4]) { mPlanes[plane].setClearValuei(val); }
    void setClearValueui(GLint plane, const GLuint val[4]) { mPlanes[plane].setClearValueui(val); }
    void begin(Context *, GLsizei n, const GLenum loadops[]);
    void end(Context *, const GLenum storeops[]);
    void barrier(Context *);
    void interrupt(Context *);
    void restore(Context *);

  protected:
    PixelLocalStorage(const ShPixelLocalStorageOptions &, const Caps &);

    // Called when the context is lost or destroyed. Causes the subclass to clear its GL object
    // handles.
    virtual void onContextObjectsLost() = 0;

    // Called when the framebuffer is being destroyed. Causes the subclass to delete its frontend GL
    // object handles.
    virtual void onDeleteContextObjects(Context *) = 0;

    // ANGLE_shader_pixel_local_storage API.
    virtual void onBegin(Context *, GLsizei n, const GLenum loadops[], Extents plsSize) = 0;
    virtual void onEnd(Context *, const GLenum storeops[])                              = 0;
    virtual void onBarrier(Context *)                                                   = 0;

    const ShPixelLocalStorageOptions mPLSOptions;

  private:
    angle::FixedVector<PixelLocalStoragePlane, IMPLEMENTATION_MAX_PIXEL_LOCAL_STORAGE_PLANES>
        mPlanes;
    size_t mInterruptCount           = 0;
    GLsizei mActivePlanesAtInterrupt = 0;
};

}  // namespace gl

#endif  // LIBANGLE_PIXEL_LOCAL_STORAGE_H_
