/*
 * Copyright 2011 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef GrGLGpu_DEFINED
#define GrGLGpu_DEFINED

#include "include/core/SkTypes.h"
#include "include/gpu/ganesh/gl/GrGLBackendSurface.h"
#include "include/private/base/SkTArray.h"
#include "src/core/SkChecksum.h"
#include "src/core/SkLRUCache.h"
#include "src/gpu/ganesh/GrFinishCallbacks.h"
#include "src/gpu/ganesh/GrGpu.h"
#include "src/gpu/ganesh/GrNativeRect.h"
#include "src/gpu/ganesh/GrProgramDesc.h"
#include "src/gpu/ganesh/GrThreadSafePipelineBuilder.h"
#include "src/gpu/ganesh/GrWindowRectsState.h"
#include "src/gpu/ganesh/GrXferProcessor.h"
#include "src/gpu/ganesh/gl/GrGLAttachment.h"
#include "src/gpu/ganesh/gl/GrGLContext.h"
#include "src/gpu/ganesh/gl/GrGLProgram.h"
#include "src/gpu/ganesh/gl/GrGLRenderTarget.h"
#include "src/gpu/ganesh/gl/GrGLTexture.h"
#include "src/gpu/ganesh/gl/GrGLVertexArray.h"

class GrGLBuffer;
class GrGLOpsRenderPass;
class GrPipeline;
enum class SkTextureCompressionType;

namespace skgpu {
class Swizzle;
}

class GrGLGpu final : public GrGpu {
public:
    static sk_sp<GrGpu> Make(sk_sp<const GrGLInterface>, const GrContextOptions&, GrDirectContext*);
    ~GrGLGpu() override;

    void disconnect(DisconnectType) override;

    GrThreadSafePipelineBuilder* pipelineBuilder() override;
    sk_sp<GrThreadSafePipelineBuilder> refPipelineBuilder() override;

    const GrGLContext& glContext() const { return *fGLContext; }

    const GrGLInterface* glInterface() const { return fGLContext->glInterface(); }
    const GrGLContextInfo& ctxInfo() const { return *fGLContext; }
    GrGLStandard glStandard() const { return fGLContext->standard(); }
    GrGLVersion glVersion() const { return fGLContext->version(); }
    SkSL::GLSLGeneration glslGeneration() const { return fGLContext->glslGeneration(); }
    const GrGLCaps& glCaps() const { return *fGLContext->caps(); }

    GrStagingBufferManager* stagingBufferManager() override { return fStagingBufferManager.get(); }

    // Used by GrGLProgram to configure OpenGL state.
    void bindTexture(int unitIdx, GrSamplerState samplerState, const skgpu::Swizzle&, GrGLTexture*);

    // These functions should be used to bind GL objects. They track the GL state and skip redundant
    // bindings. Making the equivalent glBind calls directly will confuse the state tracking.
    void bindVertexArray(GrGLuint id) {
        fHWVertexArrayState.setVertexArrayID(this, id);
    }

    // These callbacks update state tracking when GL objects are deleted. They are called from
    // GrGLResource onRelease functions.
    void notifyVertexArrayDelete(GrGLuint id) {
        fHWVertexArrayState.notifyVertexArrayDelete(id);
    }

    // Binds a buffer to the GL target corresponding to 'type', updates internal state tracking, and
    // returns the GL target the buffer was bound to.
    // When 'type' is kIndex_GrBufferType, this function will also implicitly bind the default VAO.
    // If the caller wishes to bind an index buffer to a specific VAO, it can call glBind directly.
    GrGLenum bindBuffer(GrGpuBufferType type, const GrBuffer*);

    // Flushes state from GrProgramInfo to GL. Returns false if the state couldn't be set.
    bool flushGLState(GrRenderTarget*, bool useMultisampleFBO, const GrProgramInfo&);
    void flushScissorRect(const SkIRect& scissor, int rtHeight, GrSurfaceOrigin);

    // The flushRenderTarget methods will all set the initial viewport to the full extent of the
    // backing render target.
    void flushViewport(const SkIRect& viewport, int rtHeight, GrSurfaceOrigin);

    // Returns the last program bound by flushGLState(), or nullptr if a different program has since
    // been put into use via some other method (e.g., resetContext, copySurfaceAsDraw).
    // The returned GrGLProgram can be used for binding textures and vertex attributes.
    GrGLProgram* currentProgram() {
        this->handleDirtyContext();
        return fHWProgram.get();
    }

    // Binds the vertex array that should be used for internal draws, enables 'numAttribs' vertex
    // arrays, and flushes the desired primitive restart settings. If an index buffer is provided,
    // it will be bound to the vertex array. Otherwise the index buffer binding will be left
    // unchanged.
    //
    // NOTE: This binds the default VAO (ID=zero) unless we are on a core profile, in which case we
    // use a placeholder array instead.
    GrGLAttribArrayState* bindInternalVertexArray(const GrBuffer* indexBuffer, int numAttribs,
                                                  GrPrimitiveRestart primitiveRestart) {
        auto* attribState = fHWVertexArrayState.bindInternalVertexArray(this, indexBuffer);
        attribState->enableVertexArrays(this, numAttribs, primitiveRestart);
        return attribState;
    }

    // Applies any necessary workarounds and returns the GL primitive type to use in draw calls.
    GrGLenum prepareToDraw(GrPrimitiveType primitiveType);

    using ResolveDirection = GrGLRenderTarget::ResolveDirection;

    // Resolves the render target's single sample FBO into the MSAA, or vice versa.
    // If glCaps.framebufferResolvesMustBeFullSize() is true, resolveRect must be equal the render
    // target's bounds rect.
    // If blitting single to MSAA, glCaps.canResolveSingleToMSAA() must be true.
    void resolveRenderFBOs(GrGLRenderTarget*, const SkIRect& resolveRect, ResolveDirection,
                           bool invalidateReadBufferAfterBlit = false);

    // For loading a dynamic MSAA framebuffer when glCaps.canResolveSingleToMSAA() is false.
    // NOTE: If glCaps.framebufferResolvesMustBeFullSize() is also true, the drawBounds should be
    // equal to the proxy bounds. This is because the render pass will have to do a full size
    // resolve back into the single sample FBO when rendering is complete.
    void drawSingleIntoMSAAFBO(GrGLRenderTarget* rt, const SkIRect& drawBounds) {
        this->copySurfaceAsDraw(rt, true/*drawToMultisampleFBO*/, rt, drawBounds, drawBounds,
                                GrSamplerState::Filter::kNearest);
    }

    // The GrGLOpsRenderPass does not buffer up draws before submitting them to the gpu.
    // Thus this is the implementation of the clear call for the corresponding passthrough function
    // on GrGLOpsRenderPass.
    void clear(const GrScissorState&, std::array<float, 4> color, GrRenderTarget*,
               bool useMultisampleFBO, GrSurfaceOrigin);

    // The GrGLOpsRenderPass does not buffer up draws before submitting them to the gpu.
    // Thus this is the implementation of the clearStencil call for the corresponding passthrough
    // function on GrGLOpsrenderPass.
    void clearStencilClip(const GrScissorState&, bool insideStencilMask,
                          GrRenderTarget*, bool useMultisampleFBO, GrSurfaceOrigin);

    void beginCommandBuffer(GrGLRenderTarget*, bool useMultisampleFBO,
                            const SkIRect& bounds, GrSurfaceOrigin,
                            const GrOpsRenderPass::LoadAndStoreInfo& colorLoadStore,
                            const GrOpsRenderPass::StencilLoadAndStoreInfo& stencilLoadStore);

    void endCommandBuffer(GrGLRenderTarget*, bool useMultisampleFBO,
                          const GrOpsRenderPass::LoadAndStoreInfo& colorLoadStore,
                          const GrOpsRenderPass::StencilLoadAndStoreInfo& stencilLoadStore);

    void invalidateBoundRenderTarget() {
        fHWBoundRenderTargetUniqueID.makeInvalid();
    }

    sk_sp<GrAttachment> makeStencilAttachment(const GrBackendFormat& colorFormat,
                                              SkISize dimensions, int numStencilSamples) override;

    sk_sp<GrAttachment> makeMSAAAttachment(SkISize dimensions,
                                           const GrBackendFormat& format,
                                           int numSamples,
                                           GrProtected isProtected,
                                           GrMemoryless) override;

    void deleteBackendTexture(const GrBackendTexture&) override;

    bool compile(const GrProgramDesc&, const GrProgramInfo&) override;

    bool precompileShader(const SkData& key, const SkData& data) override {
        return fProgramCache->precompileShader(this->getContext(), key, data);
    }

#if GR_TEST_UTILS
    bool isTestingOnlyBackendTexture(const GrBackendTexture&) const override;

    GrBackendRenderTarget createTestingOnlyBackendRenderTarget(SkISize dimensions,
                                                               GrColorType,
                                                               int sampleCnt,
                                                               GrProtected) override;
    void deleteTestingOnlyBackendRenderTarget(const GrBackendRenderTarget&) override;

    const GrGLContext* glContextForTesting() const override { return &this->glContext(); }

    void resetShaderCacheForTesting() const override { fProgramCache->reset(); }
#endif

    void willExecute() override;

    void submit(GrOpsRenderPass* renderPass) override;

    [[nodiscard]] GrFence insertFence() override;
    bool waitFence(GrFence) override;
    void deleteFence(GrFence) override;

    [[nodiscard]] std::unique_ptr<GrSemaphore> makeSemaphore(bool isOwned) override;
    std::unique_ptr<GrSemaphore> wrapBackendSemaphore(const GrBackendSemaphore&,
                                                      GrSemaphoreWrapType,
                                                      GrWrapOwnership) override;
    void insertSemaphore(GrSemaphore* semaphore) override;
    void waitSemaphore(GrSemaphore* semaphore) override;

    void checkFinishProcs() override;
    void finishOutstandingGpuWork() override;

    // Calls glGetError() until no errors are reported. Also looks for OOMs.
    void clearErrorsAndCheckForOOM();
    // Calls glGetError() once and returns the result. Also looks for an OOM.
    GrGLenum getErrorAndCheckForOOM();

    std::unique_ptr<GrSemaphore> prepareTextureForCrossContextUsage(GrTexture*) override;

    void deleteSync(GrGLsync);

    void bindFramebuffer(GrGLenum fboTarget, GrGLuint fboid);
    void deleteFramebuffer(GrGLuint fboid);

    void flushProgram(sk_sp<GrGLProgram>);

    // Version for programs that aren't GrGLProgram.
    void flushProgram(GrGLuint);

    // GrGLOpsRenderPass directly makes GL draws. GrGLGpu uses this notification to mark the
    // destination surface dirty if color writes are enabled.
    void didDrawTo(GrRenderTarget*);

private:
    GrGLGpu(std::unique_ptr<GrGLContext>, GrDirectContext*);

    // GrGpu overrides
    GrBackendTexture onCreateBackendTexture(SkISize dimensions,
                                            const GrBackendFormat&,
                                            GrRenderable,
                                            GrMipmapped,
                                            GrProtected,
                                            std::string_view label) override;

    GrBackendTexture onCreateCompressedBackendTexture(SkISize dimensions,
                                                      const GrBackendFormat&,
                                                      GrMipmapped,
                                                      GrProtected) override;

    bool onClearBackendTexture(const GrBackendTexture&,
                               sk_sp<skgpu::RefCntedCallback> finishedCallback,
                               std::array<float, 4> color) override;

    bool onUpdateCompressedBackendTexture(const GrBackendTexture&,
                                          sk_sp<skgpu::RefCntedCallback> finishedCallback,
                                          const void* data,
                                          size_t length) override;

    void onResetContext(uint32_t resetBits) override;

    void onResetTextureBindings() override;

    void xferBarrier(GrRenderTarget*, GrXferBarrierType) override;

    sk_sp<GrTexture> onCreateTexture(SkISize dimensions,
                                     const GrBackendFormat&,
                                     GrRenderable,
                                     int renderTargetSampleCnt,
                                     skgpu::Budgeted,
                                     GrProtected,
                                     int mipLevelCount,
                                     uint32_t levelClearMask,
                                     std::string_view label) override;
    sk_sp<GrTexture> onCreateCompressedTexture(SkISize dimensions,
                                               const GrBackendFormat&,
                                               skgpu::Budgeted,
                                               GrMipmapped,
                                               GrProtected,
                                               const void* data,
                                               size_t dataSize) override;

    sk_sp<GrGpuBuffer> onCreateBuffer(size_t size, GrGpuBufferType, GrAccessPattern) override;

    sk_sp<GrTexture> onWrapBackendTexture(const GrBackendTexture&,
                                          GrWrapOwnership,
                                          GrWrapCacheable,
                                          GrIOType) override;
    sk_sp<GrTexture> onWrapCompressedBackendTexture(const GrBackendTexture&,
                                                    GrWrapOwnership,
                                                    GrWrapCacheable) override;
    sk_sp<GrTexture> onWrapRenderableBackendTexture(const GrBackendTexture&,
                                                    int sampleCnt,
                                                    GrWrapOwnership,
                                                    GrWrapCacheable) override;
    sk_sp<GrRenderTarget> onWrapBackendRenderTarget(const GrBackendRenderTarget&) override;

    // Given a GL format return the index into the stencil format array on GrGLCaps to a
    // compatible stencil format, or negative if there is no compatible stencil format.
    int getCompatibleStencilIndex(GrGLFormat format);

    GrBackendFormat getPreferredStencilFormat(const GrBackendFormat& format) override {
        int idx = this->getCompatibleStencilIndex(GrBackendFormats::AsGLFormat(format));
        if (idx < 0) {
            return {};
        }
        return GrBackendFormats::MakeGL(GrGLFormatToEnum(this->glCaps().stencilFormats()[idx]),
                                        GR_GL_TEXTURE_NONE);
    }

    void onFBOChanged();

    // Returns whether the texture is successfully created. On success, a non-zero texture ID is
    // returned. On failure, zero is returned.
    // The texture is populated with |texels|, if it is non-null.
    // The texture parameters are cached in |initialTexParams|.
    GrGLuint createTexture(SkISize dimensions,
                           GrGLFormat,
                           GrGLenum target,
                           GrRenderable,
                           GrGLTextureParameters::SamplerOverriddenState*,
                           int mipLevelCount,
                           GrProtected isProtected,
                           std::string_view label);

    GrGLuint createCompressedTexture2D(SkISize dimensions,
                                       SkTextureCompressionType compression,
                                       GrGLFormat,
                                       GrMipmapped,
                                       GrProtected,
                                       GrGLTextureParameters::SamplerOverriddenState*);

    bool onReadPixels(GrSurface*,
                      SkIRect,
                      GrColorType surfaceColorType,
                      GrColorType dstColorType,
                      void*,
                      size_t rowBytes) override;

    bool onWritePixels(GrSurface*,
                       SkIRect,
                       GrColorType surfaceColorType,
                       GrColorType srcColorType,
                       const GrMipLevel[],
                       int mipLevelCount,
                       bool prepForTexSampling) override;

    bool onTransferFromBufferToBuffer(sk_sp<GrGpuBuffer> src,
                                      size_t srcOffset,
                                      sk_sp<GrGpuBuffer> dst,
                                      size_t dstOffset,
                                      size_t size) override;

    bool onTransferPixelsTo(GrTexture*,
                            SkIRect,
                            GrColorType textureColorType,
                            GrColorType bufferColorType,
                            sk_sp<GrGpuBuffer>,
                            size_t offset,
                            size_t rowBytes) override;

    bool onTransferPixelsFrom(GrSurface*,
                              SkIRect,
                              GrColorType surfaceColorType,
                              GrColorType bufferColorType,
                              sk_sp<GrGpuBuffer>,
                              size_t offset) override;

    bool readOrTransferPixelsFrom(GrSurface*,
                                  SkIRect rect,
                                  GrColorType surfaceColorType,
                                  GrColorType dstColorType,
                                  void* offsetOrPtr,
                                  int rowWidthInPixels);

    // Unbinds xfer buffers from GL for operations that don't need them.
    // Before calling any variation of TexImage, TexSubImage, etc..., call this with
    // GrGpuBufferType::kXferCpuToGpu to ensure that the PIXEL_UNPACK_BUFFER is unbound.
    // Before calling ReadPixels and reading back into cpu memory call this with
    // GrGpuBufferType::kXferGpuToCpu to ensure that the PIXEL_PACK_BUFFER is unbound.
    void unbindXferBuffer(GrGpuBufferType type);

    void onResolveRenderTarget(GrRenderTarget* target, const SkIRect& resolveRect) override;

    bool onRegenerateMipMapLevels(GrTexture*) override;

    bool onCopySurface(GrSurface* dst, const SkIRect& dstRect,
                       GrSurface* src, const SkIRect& srcRect,
                       GrSamplerState::Filter) override;

    // binds texture unit in GL
    void setTextureUnit(int unitIdx);

    void flushBlendAndColorWrite(const skgpu::BlendInfo&, const skgpu::Swizzle&);

    void addFinishedProc(GrGpuFinishedProc finishedProc,
                         GrGpuFinishedContext finishedContext) override;

    GrOpsRenderPass* onGetOpsRenderPass(
            GrRenderTarget*,
            bool useMultisampleFBO,
            GrAttachment*,
            GrSurfaceOrigin,
            const SkIRect&,
            const GrOpsRenderPass::LoadAndStoreInfo&,
            const GrOpsRenderPass::StencilLoadAndStoreInfo&,
            const skia_private::TArray<GrSurfaceProxy*, true>& sampledProxies,
            GrXferBarrierFlags renderPassXferBarriers) override;

    bool onSubmitToGpu(bool syncCpu) override;

    bool waitSync(GrGLsync, uint64_t timeout, bool flush);

    bool copySurfaceAsDraw(GrSurface* dst, bool drawToMultisampleFBO, GrSurface* src,
                           const SkIRect& srcRect, const SkIRect& dstRect, GrSamplerState::Filter);
    void copySurfaceAsCopyTexSubImage(GrSurface* dst, GrSurface* src, const SkIRect& srcRect,
                                      const SkIPoint& dstPoint);
    bool copySurfaceAsBlitFramebuffer(GrSurface* dst, GrSurface* src, const SkIRect& srcRect,
                                      const SkIRect& dstRect, GrSamplerState::Filter);

    class ProgramCache : public GrThreadSafePipelineBuilder {
    public:
        ProgramCache(int runtimeProgramCacheSize);
        ~ProgramCache() override;

        void abandon();
        void reset();
        sk_sp<GrGLProgram> findOrCreateProgram(GrDirectContext*,
                                               const GrProgramInfo&);
        sk_sp<GrGLProgram> findOrCreateProgram(GrDirectContext*,
                                               const GrProgramDesc&,
                                               const GrProgramInfo&,
                                               Stats::ProgramCacheResult*);
        bool precompileShader(GrDirectContext*, const SkData& key, const SkData& data);

    private:
        struct Entry;

        sk_sp<GrGLProgram> findOrCreateProgramImpl(GrDirectContext*,
                                                   const GrProgramDesc&,
                                                   const GrProgramInfo&,
                                                   Stats::ProgramCacheResult*);

        struct DescHash {
            uint32_t operator()(const GrProgramDesc& desc) const {
                return SkChecksum::Hash32(desc.asKey(), desc.keyLength());
            }
        };

        SkLRUCache<GrProgramDesc, std::unique_ptr<Entry>, DescHash> fMap;
    };

    void flushColorWrite(bool writeColor);
    void flushClearColor(std::array<float, 4>);

    // flushes the scissor. see the note on flushBoundTextureAndParams about
    // flushing the scissor after that function is called.
    void flushScissor(const GrScissorState& scissorState, int rtHeight, GrSurfaceOrigin rtOrigin) {
        this->flushScissorTest(GrScissorTest(scissorState.enabled()));
        if (scissorState.enabled()) {
            this->flushScissorRect(scissorState.rect(), rtHeight, rtOrigin);
        }
    }
    void flushScissorTest(GrScissorTest);

    void flushWindowRectangles(const GrWindowRectsState&, const GrGLRenderTarget*, GrSurfaceOrigin);
    void disableWindowRectangles();

    int numTextureUnits() const { return this->caps()->shaderCaps()->fMaxFragmentSamplers; }

    // Binds a texture to a target on the "scratch" texture unit to use for texture operations
    // other than usual draw flow (i.e. a GrGLProgram derived from a GrPipeline used to draw). It
    // ensures that such operations don't negatively interact with draws. The active texture unit
    // and the binding for 'target' will change.
    void bindTextureToScratchUnit(GrGLenum target, GrGLint textureID);

    void flushRenderTarget(GrGLRenderTarget*, bool useMultisampleFBO);

    void flushStencil(const GrStencilSettings&, GrSurfaceOrigin);
    void disableStencil();

    void flushConservativeRasterState(bool enable);

    void flushWireframeState(bool enable);

    void flushFramebufferSRGB(bool enable);

    // Uploads src data of a color type to the currently bound texture on the active texture unit.
    // The caller specifies color type that the texture is being used with, which may be different
    // than the src color type. This fails if the combination of texture format, texture color type,
    // and src data color type are not valid. No conversion is performed on the data before passing
    // it to GL. 'dstRect' must be the texture bounds if mipLevelCount is greater than 1.
    bool uploadColorTypeTexData(GrGLFormat textureFormat,
                                GrColorType textureColorType,
                                SkISize texDims,
                                GrGLenum target,
                                SkIRect dstRect,
                                GrColorType srcColorType,
                                const GrMipLevel texels[],
                                int mipLevelCount);

    // Uploads a constant color to a texture using the "default" format and color type. Overwrites
    // entire levels. Bit n in 'levelMask' indicates whether level n should be written. This
    // function doesn't know if MIP levels have been allocated, thus levelMask should not have bits
    // beyond the low bit set if the texture is not MIP mapped.
    bool uploadColorToTex(GrGLFormat textureFormat,
                          SkISize texDims,
                          GrGLenum target,
                          std::array<float, 4> color,
                          uint32_t levelMask);

    // Pushes data to the currently bound texture to the currently active unit. 'dstRect' must be
    // the texture bounds if mipLevelCount is greater than 1.
    void uploadTexData(SkISize dimensions,
                       GrGLenum target,
                       SkIRect dstRect,
                       GrGLenum externalFormat,
                       GrGLenum externalType,
                       size_t bpp,
                       const GrMipLevel texels[],
                       int mipLevelCount);

    // Helper for onCreateCompressedTexture. Compressed textures are read-only so we only use this
    // to populate a new texture. Returns false if we failed to create and upload the texture.
    bool uploadCompressedTexData(SkTextureCompressionType compressionType,
                                 GrGLFormat,
                                 SkISize dimensions,
                                 GrMipmapped,
                                 GrGLenum target,
                                 const void* data, size_t dataSize);

    // Calls one of various versions of renderBufferStorageMultisample.
    bool renderbufferStorageMSAA(const GrGLContext& ctx, int sampleCount, GrGLenum format,
                                 int width, int height);

    bool createRenderTargetObjects(const GrGLTexture::Desc&,
                                   int sampleCount,
                                   GrGLRenderTarget::IDs*);
    enum TempFBOTarget {
        kSrc_TempFBOTarget,
        kDst_TempFBOTarget
    };

    // Binds a surface as a FBO for copying, reading, or clearing. If the surface already owns an
    // FBO ID then that ID is bound. If not the surface is temporarily bound to a FBO and that FBO
    // is bound. This must be paired with a call to unbindSurfaceFBOForPixelOps().
    void bindSurfaceFBOForPixelOps(GrSurface* surface, int mipLevel, GrGLenum fboTarget,
                                   TempFBOTarget tempFBOTarget);

    // Must be called if bindSurfaceFBOForPixelOps was used to bind a surface for copying.
    void unbindSurfaceFBOForPixelOps(GrSurface* surface, int mipLevel, GrGLenum fboTarget);

#ifdef SK_ENABLE_DUMP_GPU
    void onDumpJSON(SkJSONWriter*) const override;
#endif

    bool createCopyProgram(GrTexture* srcTexture);
    bool createMipmapProgram(int progIdx);

    std::unique_ptr<GrGLContext> fGLContext;

    // GL program-related state
    sk_sp<ProgramCache>         fProgramCache;

    ///////////////////////////////////////////////////////////////////////////
    ///@name Caching of GL State
    ///@{
    int                         fHWActiveTextureUnitIdx;

    GrGLuint                    fHWProgramID;
    sk_sp<GrGLProgram>          fHWProgram;

    enum TriState {
        kNo_TriState,
        kYes_TriState,
        kUnknown_TriState
    };

    GrGLuint                    fTempSrcFBOID;
    GrGLuint                    fTempDstFBOID;

    GrGLuint                    fStencilClearFBOID;

    // last scissor / viewport scissor state seen by the GL.
    struct {
        TriState fEnabled;
        GrNativeRect fRect;
        void invalidate() {
            fEnabled = kUnknown_TriState;
            fRect.invalidate();
        }
    } fHWScissorSettings;

    class {
    public:
        bool valid() const { return kInvalidSurfaceOrigin != fRTOrigin; }
        void invalidate() { fRTOrigin = kInvalidSurfaceOrigin; }
        bool knownDisabled() const { return this->valid() && !fWindowState.enabled(); }
        void setDisabled() {
            fRTOrigin = kTopLeft_GrSurfaceOrigin;
            fWindowState.setDisabled();
        }

        void set(GrSurfaceOrigin rtOrigin, int width, int height,
                 const GrWindowRectsState& windowState) {
            fRTOrigin = rtOrigin;
            fWidth = width;
            fHeight = height;
            fWindowState = windowState;
        }

        bool knownEqualTo(GrSurfaceOrigin rtOrigin, int width, int height,
                          const GrWindowRectsState& windowState) const {
            if (!this->valid()) {
                return false;
            }
            if (fWindowState.numWindows() &&
                (fRTOrigin != rtOrigin || fWidth != width || fHeight != height)) {
                return false;
            }
            return fWindowState == windowState;
        }

    private:
        enum { kInvalidSurfaceOrigin = -1 };

        int                  fRTOrigin;
        int                  fWidth;
        int                  fHeight;
        GrWindowRectsState   fWindowState;
    } fHWWindowRectsState;

    GrNativeRect fHWViewport;

    /**
     * Tracks vertex attrib array state.
     */
    class HWVertexArrayState {
    public:
        HWVertexArrayState() : fCoreProfileVertexArray(nullptr) { this->invalidate(); }

        ~HWVertexArrayState() { delete fCoreProfileVertexArray; }

        void invalidate() {
            fBoundVertexArrayIDIsValid = false;
            fDefaultVertexArrayAttribState.invalidate();
            if (fCoreProfileVertexArray) {
                fCoreProfileVertexArray->invalidateCachedState();
            }
        }

        void notifyVertexArrayDelete(GrGLuint id) {
            if (fBoundVertexArrayIDIsValid && fBoundVertexArrayID == id) {
                // Does implicit bind to 0
                fBoundVertexArrayID = 0;
            }
        }

        void setVertexArrayID(GrGLGpu* gpu, GrGLuint arrayID) {
            if (!gpu->glCaps().vertexArrayObjectSupport()) {
                SkASSERT(0 == arrayID);
                return;
            }
            if (!fBoundVertexArrayIDIsValid || arrayID != fBoundVertexArrayID) {
                GR_GL_CALL(gpu->glInterface(), BindVertexArray(arrayID));
                fBoundVertexArrayIDIsValid = true;
                fBoundVertexArrayID = arrayID;
            }
        }

        /**
         * Binds the vertex array that should be used for internal draws, and returns its attrib
         * state. This binds the default VAO (ID=zero) unless we are on a core profile, in which
         * case we use a placeholder array instead.
         *
         * If an index buffer is provided, it will be bound to the vertex array. Otherwise the
         * index buffer binding will be left unchanged.
         *
         * The returned GrGLAttribArrayState should be used to set vertex attribute arrays.
         */
        GrGLAttribArrayState* bindInternalVertexArray(GrGLGpu*, const GrBuffer* ibuff = nullptr);

    private:
        GrGLuint             fBoundVertexArrayID;
        bool                 fBoundVertexArrayIDIsValid;

        // We return a non-const pointer to this from bindArrayAndBuffersToDraw when vertex array 0
        // is bound. However, this class is internal to GrGLGpu and this object never leaks out of
        // GrGLGpu.
        GrGLAttribArrayState fDefaultVertexArrayAttribState;

        // This is used when we're using a core profile.
        GrGLVertexArray*     fCoreProfileVertexArray;
    } fHWVertexArrayState;

    struct {
        GrGLenum                fGLTarget;
        GrGpuResource::UniqueID fBoundBufferUniqueID;
        bool                    fBufferZeroKnownBound;

        void invalidate() {
            fBoundBufferUniqueID.makeInvalid();
            fBufferZeroKnownBound = false;
        }
    }                                       fHWBufferState[kGrGpuBufferTypeCount];

    auto* hwBufferState(GrGpuBufferType type) {
        unsigned typeAsUInt = static_cast<unsigned>(type);
        SkASSERT(typeAsUInt < std::size(fHWBufferState));
        SkASSERT(type != GrGpuBufferType::kUniform);
        return &fHWBufferState[typeAsUInt];
    }

    enum class FlushType {
        kIfRequired,
        kForce,
    };

    // This calls glFlush if it is required for previous operations or kForce is passed.
    void flush(FlushType flushType = FlushType::kIfRequired);

    void setNeedsFlush() { fNeedsGLFlush = true; }

    struct {
        skgpu::BlendEquation fEquation;
        skgpu::BlendCoeff    fSrcCoeff;
        skgpu::BlendCoeff    fDstCoeff;
        SkPMColor4f          fConstColor;
        bool                 fConstColorValid;
        TriState             fEnabled;

        void invalidate() {
            fEquation = skgpu::BlendEquation::kIllegal;
            fSrcCoeff = skgpu::BlendCoeff::kIllegal;
            fDstCoeff = skgpu::BlendCoeff::kIllegal;
            fConstColorValid = false;
            fEnabled = kUnknown_TriState;
        }
    }                                       fHWBlendState;

    TriState                                fHWConservativeRasterEnabled;

    TriState                                fHWWireframeEnabled;

    GrStencilSettings                       fHWStencilSettings;
    GrSurfaceOrigin                         fHWStencilOrigin;
    TriState                                fHWStencilTestEnabled;

    TriState                                fHWWriteToColor;
    GrGpuResource::UniqueID                 fHWBoundRenderTargetUniqueID;
    bool                                    fHWBoundFramebufferIsMSAA;
    TriState                                fHWSRGBFramebuffer;

    class TextureUnitBindings {
    public:
        TextureUnitBindings() = default;
        TextureUnitBindings(const TextureUnitBindings&) = delete;
        TextureUnitBindings& operator=(const TextureUnitBindings&) = delete;

        GrGpuResource::UniqueID boundID(GrGLenum target) const;
        bool hasBeenModified(GrGLenum target) const;
        void setBoundID(GrGLenum target, GrGpuResource::UniqueID);
        void invalidateForScratchUse(GrGLenum target);
        void invalidateAllTargets(bool markUnmodified);

    private:
        struct TargetBinding {
            GrGpuResource::UniqueID fBoundResourceID;
            bool fHasBeenModified = false;
        };
        TargetBinding fTargetBindings[3];
    };
    skia_private::AutoTArray<TextureUnitBindings> fHWTextureUnitBindings;

    GrGLfloat fHWClearColor[4];

    GrGLuint fBoundDrawFramebuffer = 0;

    /** IDs for copy surface program. (3 sampler types) */
    struct {
        GrGLuint    fProgram = 0;
        GrGLint     fTextureUniform = 0;
        GrGLint     fTexCoordXformUniform = 0;
        GrGLint     fPosXformUniform = 0;
    }                                       fCopyPrograms[3];
    sk_sp<GrGLBuffer>                       fCopyProgramArrayBuffer;

    /** IDs for texture mipmap program. (4 filter configurations) */
    struct {
        GrGLuint    fProgram = 0;
        GrGLint     fTextureUniform = 0;
        GrGLint     fTexCoordXformUniform = 0;
    }                                       fMipmapPrograms[4];
    sk_sp<GrGLBuffer>                       fMipmapProgramArrayBuffer;

    static int TextureToCopyProgramIdx(GrTexture* texture);

    static int TextureSizeToMipmapProgramIdx(int width, int height) {
        const bool wide = (width > 1) && SkToBool(width & 0x1);
        const bool tall = (height > 1) && SkToBool(height & 0x1);
        return (wide ? 0x2 : 0x0) | (tall ? 0x1 : 0x0);
    }

    GrPrimitiveType fLastPrimitiveType;

    GrGLTextureParameters::ResetTimestamp fResetTimestampForTextureParameters = 0;

    class SamplerObjectCache;
    std::unique_ptr<SamplerObjectCache> fSamplerObjectCache;

    std::unique_ptr<GrGLOpsRenderPass> fCachedOpsRenderPass;

    std::unique_ptr<GrStagingBufferManager> fStagingBufferManager;

    GrFinishCallbacks fFinishCallbacks;

    // If we've called a command that requires us to call glFlush than this will be set to true
    // since we defer calling flush until submit time. When we call submitToGpu if this is true then
    // we call glFlush and reset this to false.
    bool fNeedsGLFlush = false;

    SkDEBUGCODE(bool fIsExecutingCommandBuffer_DebugOnly = false);

    friend class GrGLPathRendering; // For accessing setTextureUnit.

    using INHERITED = GrGpu;
};

#endif
