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

#ifndef skgpu_v1_Device_DEFINED
#define skgpu_v1_Device_DEFINED

#include "include/core/SkCanvas.h"
#include "include/core/SkClipOp.h"
#include "include/core/SkColor.h"
#include "include/core/SkImage.h"
#include "include/core/SkImageInfo.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkSamplingOptions.h"
#include "include/core/SkShader.h"
#include "include/core/SkSpan.h"
#include "include/core/SkSurface.h"
#include "include/gpu/ganesh/GrRecordingContext.h"
#include "include/gpu/ganesh/GrTypes.h"
#include "include/private/base/SkAssert.h"
#include "include/private/base/SkMacros.h"
#include "src/core/SkDevice.h"
#include "src/core/SkMatrixPriv.h"
#include "src/gpu/ganesh/ClipStack.h"
#include "src/gpu/ganesh/GrColorInfo.h"
#include "src/gpu/ganesh/GrSurfaceProxyView.h"
#include "src/text/gpu/SubRunControl.h"

#include <memory>
#include <utility>

class GrBackendSemaphore;
class GrClip;
class GrRenderTargetProxy;
class GrSurfaceProxy;
class SkBlender;
class SkColorSpace;
class SkDrawable;
class SkLatticeIter;
class SkMatrix;
class SkMesh;
class SkPaint;
class SkPath;
class SkPixmap;
class SkRRect;
class SkRecorder;
class SkRegion;
class SkSpecialImage;
class SkSurfaceProps;
class SkSurface_Ganesh;
class SkVertices;
enum SkAlphaType : int;
enum SkColorType : int;
enum class GrAA : bool;
enum class GrColorType;
enum class SkBackingFit;
enum class SkBlendMode;
enum class SkTileMode;
struct SkArc;
struct SkDrawShadowRec;
struct SkISize;
struct SkPoint;
struct SkRSXform;
namespace skgpu {
enum class Budgeted : bool;
enum class Mipmapped : bool;
class TiledTextureUtils;
}
namespace skif {
class Backend;
}
namespace sktext {
class GlyphRunList;
namespace gpu {
    class Slug;
}}


namespace skgpu::ganesh {

class SurfaceContext;
class SurfaceFillContext;
class SurfaceDrawContext;

/**
 *  Subclass of SkDevice, which directs all drawing to the GrGpu owned by the canvas.
 */
class Device final : public SkDevice {
public:
    enum class InitContents {
        kClear,
        kUninit
    };

    GrSurfaceProxyView readSurfaceView();
    GrRenderTargetProxy* targetProxy();

    GrRecordingContext* recordingContext() const override { return fContext.get(); }
    SkRecorder* baseRecorder() const override {
        SkASSERT(fContext);
        return fContext->asRecorder();
    }

    bool wait(int numSemaphores,
              const GrBackendSemaphore* waitSemaphores,
              bool deleteSemaphoresAfterWait);

    void discard();
    void resolveMSAA();

    bool replaceBackingProxy(SkSurface::ContentChangeMode,
                             sk_sp<GrRenderTargetProxy>,
                             GrColorType,
                             sk_sp<SkColorSpace>,
                             GrSurfaceOrigin,
                             const SkSurfaceProps&);
    bool replaceBackingProxy(SkSurface::ContentChangeMode);

    using RescaleGamma       = SkImage::RescaleGamma;
    using RescaleMode        = SkImage::RescaleMode;
    using ReadPixelsCallback = SkImage::ReadPixelsCallback;
    using ReadPixelsContext  = SkImage::ReadPixelsContext;

    void asyncRescaleAndReadPixels(const SkImageInfo& info,
                                   const SkIRect& srcRect,
                                   RescaleGamma rescaleGamma,
                                   RescaleMode rescaleMode,
                                   ReadPixelsCallback callback,
                                   ReadPixelsContext context);

    void asyncRescaleAndReadPixelsYUV420(SkYUVColorSpace yuvColorSpace,
                                         bool readAlpha,
                                         sk_sp<SkColorSpace> dstColorSpace,
                                         const SkIRect& srcRect,
                                         SkISize dstSize,
                                         RescaleGamma rescaleGamma,
                                         RescaleMode,
                                         ReadPixelsCallback callback,
                                         ReadPixelsContext context);

    /**
     * This factory uses the color space, origin, surface properties, and initialization
     * method along with the provided proxy to create the gpu device.
     */
    static sk_sp<Device> Make(GrRecordingContext*,
                              GrColorType,
                              sk_sp<GrSurfaceProxy>,
                              sk_sp<SkColorSpace>,
                              GrSurfaceOrigin,
                              const SkSurfaceProps&,
                              InitContents);

    /**
     * This factory uses the budgeted, imageInfo, fit, sampleCount, mipmapped, and isProtected
     * parameters to create a proxy to back the gpu device. The color space (from the image info),
     * origin, surface properties, and initialization method are then used (with the created proxy)
     * to create the device.
     */
    static sk_sp<Device> Make(GrRecordingContext*,
                              skgpu::Budgeted,
                              const SkImageInfo&,
                              SkBackingFit,
                              int sampleCount,
                              skgpu::Mipmapped,
                              GrProtected,
                              GrSurfaceOrigin,
                              const SkSurfaceProps&,
                              InitContents);

    ~Device() override;

    SurfaceDrawContext* surfaceDrawContext();
    const SurfaceDrawContext* surfaceDrawContext() const;
    SurfaceFillContext* surfaceFillContext();

    SkStrikeDeviceInfo strikeDeviceInfo() const override;

    // set all pixels to 0
    void clearAll();

    void drawPaint(const SkPaint& paint) override;
    void drawPoints(SkCanvas::PointMode, SkSpan<const SkPoint>, const SkPaint&) override;
    void drawRect(const SkRect& r, const SkPaint& paint) override;
    void drawRRect(const SkRRect& r, const SkPaint& paint) override;
    void drawDRRect(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint) override;
    void drawRegion(const SkRegion& r, const SkPaint& paint) override;
    void drawOval(const SkRect& oval, const SkPaint& paint) override;
    void drawArc(const SkArc& arc, const SkPaint& paint) override;
    void drawPath(const SkPath& path, const SkPaint& paint) override;

    void drawVertices(const SkVertices*, sk_sp<SkBlender>, const SkPaint&, bool) override;
    void drawMesh(const SkMesh&, sk_sp<SkBlender>, const SkPaint&) override;
#if !defined(SK_ENABLE_OPTIMIZE_SIZE)
    void drawShadow(SkCanvas*, const SkPath&, const SkDrawShadowRec&) override;
#endif
    void drawAtlas(SkSpan<const SkRSXform>, SkSpan<const SkRect>, SkSpan<const SkColor>,
                   sk_sp<SkBlender>, const SkPaint&) override;

    void drawImageRect(const SkImage*, const SkRect* src, const SkRect& dst,
                       const SkSamplingOptions&, const SkPaint&,
                       SkCanvas::SrcRectConstraint) override;
    bool shouldDrawAsTiledImageRect() const override { return true; }
    bool drawAsTiledImageRect(SkCanvas*,
                              const SkImage*,
                              const SkRect* src,
                              const SkRect& dst,
                              const SkSamplingOptions&,
                              const SkPaint&,
                              SkCanvas::SrcRectConstraint) override;
    void drawImageLattice(const SkImage*, const SkCanvas::Lattice&,
                          const SkRect& dst, SkFilterMode, const SkPaint&) override;

    void drawDrawable(SkCanvas*, SkDrawable*, const SkMatrix*) override;

    void drawDevice(SkDevice*, const SkSamplingOptions&, const SkPaint&) override;
    void drawSpecial(SkSpecialImage*, const SkMatrix& localToDevice, const SkSamplingOptions&,
                     const SkPaint&, SkCanvas::SrcRectConstraint) override;

    void drawCoverageMask(const SkSpecialImage* mask, const SkMatrix& maskToDevice,
                          const SkSamplingOptions& localToDevice, const SkPaint& paint) override;
    bool drawBlurredRRect(const SkRRect&, const SkPaint&, float deviceSigma) override;

    void drawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4], SkCanvas::QuadAAFlags aaFlags,
                        const SkColor4f& color, SkBlendMode mode) override;
    void drawEdgeAAImageSet(const SkCanvas::ImageSetEntry[], int count, const SkPoint dstClips[],
                            const SkMatrix preViewMatrices[], const SkSamplingOptions&,
                            const SkPaint&, SkCanvas::SrcRectConstraint) override;

    // Assumes the src and dst rects have already been optimized to fit the proxy.
    // Only implemented by the gpu devices.
    // This method is the lowest level draw used for tiled bitmap draws. It doesn't attempt to
    // modify its parameters (e.g., adjust src & dst) but just draws the image however it can. It
    // could, almost, be replaced with a drawEdgeAAImageSet call for the tiled bitmap draw use
    // case but the extra tilemode requirement and the intermediate parameter processing (e.g.,
    // trying to alter the SrcRectConstraint) currently block that.
    void drawEdgeAAImage(const SkImage*,
                         const SkRect& src,
                         const SkRect& dst,
                         const SkPoint dstClip[4],
                         SkCanvas::QuadAAFlags,
                         const SkMatrix& localToDevice,
                         const SkSamplingOptions&,
                         const SkPaint&,
                         SkCanvas::SrcRectConstraint,
                         const SkMatrix& srcToDst,
                         SkTileMode);

    sk_sp<sktext::gpu::Slug> convertGlyphRunListToSlug(const sktext::GlyphRunList& glyphRunList,
                                                       const SkPaint& paint) override;

    void drawSlug(SkCanvas*, const sktext::gpu::Slug* slug, const SkPaint& paint) override;

    sk_sp<SkSpecialImage> snapSpecial(const SkIRect& subset, bool forceCopy = false) override;
    sk_sp<SkSpecialImage> snapSpecialScaled(const SkIRect& subset, const SkISize& dstDims) override;

    sk_sp<SkDevice> createDevice(const CreateInfo&, const SkPaint*) override;

    sk_sp<SkSurface> makeSurface(const SkImageInfo&, const SkSurfaceProps&) override;

    Device* asGaneshDevice() override { return this; }

    SkIRect devClipBounds() const override { return fClip.getConservativeBounds(); }

    void pushClipStack() override { fClip.save(); }
    void popClipStack() override { fClip.restore(); }

    void clipRect(const SkRect& rect, SkClipOp op, bool aa) override {
        SkASSERT(op == SkClipOp::kIntersect || op == SkClipOp::kDifference);
        fClip.clipRect(this->localToDevice(), rect, GrAA(aa), op);
    }
    void clipRRect(const SkRRect& rrect, SkClipOp op, bool aa) override {
        SkASSERT(op == SkClipOp::kIntersect || op == SkClipOp::kDifference);
        fClip.clipRRect(this->localToDevice(), rrect, GrAA(aa), op);
    }
    void clipPath(const SkPath& path, SkClipOp op, bool aa) override;

    void replaceClip(const SkIRect& rect) override {
        // Transform from "global/canvas" coordinates to relative to this device
        SkRect deviceRect = SkMatrixPriv::MapRect(this->globalToDevice(), SkRect::Make(rect));
        fClip.replaceClip(deviceRect.round());
    }
    void clipRegion(const SkRegion& globalRgn, SkClipOp op) override;

    bool isClipAntiAliased() const override;

    bool isClipEmpty() const override {
        return fClip.clipState() == ClipStack::ClipState::kEmpty;
    }

    bool isClipRect() const override {
        return fClip.clipState() == ClipStack::ClipState::kDeviceRect ||
               fClip.clipState() == ClipStack::ClipState::kWideOpen;
    }

    bool isClipWideOpen() const override {
        return fClip.clipState() == ClipStack::ClipState::kWideOpen;
    }

    void android_utils_clipAsRgn(SkRegion*) const override;
    bool android_utils_clipWithStencil() override;

private:
    enum class DeviceFlags {
        kNone      = 0,
        kNeedClear = 1 << 0,  //!< Surface requires an initial clear
        kIsOpaque  = 1 << 1,  //!< Hint from client that rendering to this device will be
        //   opaque even if the config supports alpha.
    };
    SK_DECL_BITFIELD_CLASS_OPS_FRIENDS(DeviceFlags);

    static SkImageInfo MakeInfo(SurfaceContext*,  DeviceFlags);
    static bool CheckAlphaTypeAndGetFlags(SkAlphaType, InitContents, DeviceFlags*);

    sk_sp<GrRecordingContext> fContext;

    const sktext::gpu::SubRunControl fSubRunControl;

    std::unique_ptr<SurfaceDrawContext> fSurfaceDrawContext;

    ClipStack fClip;

    static sk_sp<Device> Make(std::unique_ptr<SurfaceDrawContext>,
                              SkAlphaType,
                              InitContents);

    Device(std::unique_ptr<SurfaceDrawContext>, DeviceFlags);

    void onDrawGlyphRunList(SkCanvas*, const sktext::GlyphRunList&, const SkPaint& paint) override;

    bool onReadPixels(const SkPixmap&, int, int) override;
    bool onWritePixels(const SkPixmap&, int, int) override;
    bool onAccessPixels(SkPixmap*) override;

    sk_sp<skif::Backend> createImageFilteringBackend(const SkSurfaceProps& surfaceProps,
                                                     SkColorType colorType) const override;

    void onClipShader(sk_sp<SkShader> shader) override {
        fClip.clipShader(std::move(shader));
    }

    const GrClip* clip() const { return &fClip; }

    // If not null, dstClip must be contained inside dst and will also respect the edge AA flags.
    // If 'preViewMatrix' is not null, final CTM will be this->ctm() * preViewMatrix.
    void drawImageQuadDirect(const SkImage*,
                             const SkRect& src,
                             const SkRect& dst,
                             const SkPoint dstClip[4],
                             SkCanvas::QuadAAFlags,
                             const SkMatrix* preViewMatrix,
                             const SkSamplingOptions&,
                             const SkPaint&,
                             SkCanvas::SrcRectConstraint);

    // FIXME(michaelludwig) - Should be removed in favor of using drawImageQuad with edge flags to
    // for every element in the SkLatticeIter.
    void drawViewLattice(GrSurfaceProxyView,
                         const GrColorInfo& colorInfo,
                         std::unique_ptr<SkLatticeIter>,
                         const SkRect& dst,
                         SkFilterMode,
                         const SkPaint&);

    friend class ::SkSurface_Ganesh;  // for access to surfaceProps
    friend class skgpu::TiledTextureUtils;   // for access to clip()
};

SK_MAKE_BITFIELD_CLASS_OPS(Device::DeviceFlags)

}  // namespace skgpu::ganesh

#endif // skgpu_v1_Device_DEFINED
