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

#include "src/gpu/ganesh/StencilMaskHelper.h"

#include "include/core/SkMatrix.h"
#include "include/core/SkPath.h"
#include "include/core/SkRegion.h"
#include "src/gpu/ganesh/GrRecordingContextPriv.h"
#include "src/gpu/ganesh/GrStencilSettings.h"
#include "src/gpu/ganesh/SurfaceDrawContext.h"
#include "src/gpu/ganesh/effects/GrDisableColorXP.h"
#include "src/gpu/ganesh/geometry/GrShape.h"
#include "src/gpu/ganesh/geometry/GrStyledShape.h"

namespace {

////////////////////////////////////////////////////////////////////////////////
// Stencil Rules for Merging user stencil space into clip
//

///////
// Replace
static constexpr GrUserStencilSettings gUserToClipReplace(
    GrUserStencilSettings::StaticInit<
        0x0000,
        GrUserStencilTest::kNotEqual,
        0xffff,
        GrUserStencilOp::kSetClipAndReplaceUserBits,
        GrUserStencilOp::kZeroClipAndUserBits,
        0xffff>()
);

static constexpr GrUserStencilSettings gInvUserToClipReplace(
    GrUserStencilSettings::StaticInit<
        0x0000,
        GrUserStencilTest::kEqual,
        0xffff,
        GrUserStencilOp::kSetClipAndReplaceUserBits,
        GrUserStencilOp::kZeroClipAndUserBits,
        0xffff>()
);

///////
// Intersect
static constexpr GrUserStencilSettings gUserToClipIsect(
    GrUserStencilSettings::StaticInit<
        0x0000,
        GrUserStencilTest::kLessIfInClip, // "0 < userBits" is equivalent to "0 != userBits".
        0xffff,
        GrUserStencilOp::kSetClipAndReplaceUserBits,
        GrUserStencilOp::kZeroClipAndUserBits,
        0xffff>()
);

///////
// Difference
static constexpr GrUserStencilSettings gUserToClipDiff(
    GrUserStencilSettings::StaticInit<
        0x0000,
        GrUserStencilTest::kEqualIfInClip,
        0xffff,
        GrUserStencilOp::kSetClipAndReplaceUserBits,
        GrUserStencilOp::kZeroClipAndUserBits,
        0xffff>()
);

///////
// Union
static constexpr GrUserStencilSettings gUserToClipUnion(
    GrUserStencilSettings::StaticInit<
        0x0000,
        GrUserStencilTest::kNotEqual,
        0xffff,
        GrUserStencilOp::kSetClipAndReplaceUserBits,
        GrUserStencilOp::kKeep,
        0xffff>()
);

static constexpr GrUserStencilSettings gInvUserToClipUnionPass0( // Does not zero user bits.
    GrUserStencilSettings::StaticInit<
        0x0000,
        GrUserStencilTest::kEqual,
        0xffff,
        GrUserStencilOp::kSetClipBit,
        GrUserStencilOp::kKeep,
        0x0000>()
);

///////
// Xor
static constexpr GrUserStencilSettings gUserToClipXorPass0( // Does not zero user bits.
    GrUserStencilSettings::StaticInit<
        0x0000,
        GrUserStencilTest::kNotEqual,
        0xffff,
        GrUserStencilOp::kInvertClipBit,
        GrUserStencilOp::kKeep,
        0x0000>()
);

static constexpr GrUserStencilSettings gInvUserToClipXorPass0( // Does not zero user bits.
    GrUserStencilSettings::StaticInit<
        0x0000,
        GrUserStencilTest::kEqual,
        0xffff,
        GrUserStencilOp::kInvertClipBit,
        GrUserStencilOp::kKeep,
        0x0000>()
);

///////
// Reverse Diff
static constexpr GrUserStencilSettings gUserToClipRDiffPass0( // Does not zero user bits.
    GrUserStencilSettings::StaticInit<
        0x0000,
        GrUserStencilTest::kNotEqual,
        0xffff,
        GrUserStencilOp::kInvertClipBit,
        GrUserStencilOp::kZeroClipBit,
        0x0000>()
);

static constexpr GrUserStencilSettings gInvUserToClipRDiffPass0( // Does not zero user bits.
    GrUserStencilSettings::StaticInit<
        0x0000,
        GrUserStencilTest::kEqual,
        0xffff,
        GrUserStencilOp::kInvertClipBit,
        GrUserStencilOp::kZeroClipBit,
        0x0000>()
);

///////
// Second pass to clear user bits (only needed sometimes)
static constexpr GrUserStencilSettings gZeroUserBits(
    GrUserStencilSettings::StaticInit<
        0x0000,
        GrUserStencilTest::kNotEqual,
        0xffff,
        GrUserStencilOp::kZero,
        GrUserStencilOp::kKeep,
        0xffff>()
);

static constexpr const GrUserStencilSettings* gUserToClipTable[2][1 + SkRegion::kLastOp][3] = {
    {  /* Normal fill. */
        {&gUserToClipDiff,           nullptr,         nullptr},  // kDifference_Op.
        {&gUserToClipIsect,          nullptr,         nullptr},  // kIntersect_Op.
        {&gUserToClipUnion,          nullptr,         nullptr},  // kUnion_Op.
        {&gUserToClipXorPass0,       &gZeroUserBits,  nullptr},  // kXOR_Op.
        {&gUserToClipRDiffPass0,     &gZeroUserBits,  nullptr},  // kReverseDifference_Op.
        {&gUserToClipReplace,        nullptr,         nullptr}   // kReplace_Op.

    }, /* Inverse fill. */ {
        {&gUserToClipIsect,          nullptr,         nullptr},  // ~diff (aka isect).
        {&gUserToClipDiff,           nullptr,         nullptr},  // ~isect (aka diff).
        {&gInvUserToClipUnionPass0,  &gZeroUserBits,  nullptr},  // ~union.
        {&gInvUserToClipXorPass0,    &gZeroUserBits,  nullptr},  // ~xor.
        {&gInvUserToClipRDiffPass0,  &gZeroUserBits,  nullptr},  // ~reverse diff.
        {&gInvUserToClipReplace,     nullptr,         nullptr}   // ~replace.
    }
};

///////
// Direct to Stencil

// We can render a clip element directly without first writing to the client
// portion of the clip when the fill is not inverse and the set operation will
// only modify the in/out status of samples covered by the clip element.

// this one only works if used right after stencil clip was cleared.
// Our clip mask creation code doesn't allow midstream replace ops.
static constexpr GrUserStencilSettings gReplaceClip(
    GrUserStencilSettings::StaticInit<
        0x0000,
        GrUserStencilTest::kAlways,
        0xffff,
        GrUserStencilOp::kSetClipBit,
        GrUserStencilOp::kSetClipBit,
        0x0000>()
);

static constexpr GrUserStencilSettings gUnionClip(
    GrUserStencilSettings::StaticInit<
        0x0000,
        GrUserStencilTest::kAlwaysIfInClip,
        0xffff,
        GrUserStencilOp::kKeep,
        GrUserStencilOp::kSetClipBit,
        0x0000>()
);

static constexpr GrUserStencilSettings gXorClip(
    GrUserStencilSettings::StaticInit<
        0x0000,
        GrUserStencilTest::kAlways,
        0xffff,
        GrUserStencilOp::kInvertClipBit,
        GrUserStencilOp::kInvertClipBit,
        0x0000>()
);

static constexpr GrUserStencilSettings gDiffClip(
    GrUserStencilSettings::StaticInit<
        0x0000,
        GrUserStencilTest::kAlwaysIfInClip,
        0xffff,
        GrUserStencilOp::kZeroClipBit,
        GrUserStencilOp::kKeep,
        0x0000>()
);

static constexpr const GrUserStencilSettings* gDirectDrawTable[1 + SkRegion::kLastOp][2] = {
    {&gDiffClip,     nullptr},  // kDifference_Op.
    {nullptr,        nullptr},  // kIntersect_Op.
    {&gUnionClip,    nullptr},  // kUnion_Op.
    {&gXorClip,      nullptr},  // kXOR_Op.
    {nullptr,        nullptr},  // kReverseDifference_Op.
    {&gReplaceClip,  nullptr}   // kReplace_Op.
};

static_assert(0 == SkRegion::kDifference_Op);
static_assert(1 == SkRegion::kIntersect_Op);
static_assert(2 == SkRegion::kUnion_Op);
static_assert(3 == SkRegion::kXOR_Op);
static_assert(4 == SkRegion::kReverseDifference_Op);
static_assert(5 == SkRegion::kReplace_Op);

// Settings used to when not allowed to draw directly to the clip to fill the user stencil bits
// before applying the covering clip stencil passes.
static constexpr GrUserStencilSettings gDrawToStencil(
    GrUserStencilSettings::StaticInit<
        0x0000,
        GrUserStencilTest::kAlways,
        0xffff,
        GrUserStencilOp::kIncMaybeClamp,
        GrUserStencilOp::kIncMaybeClamp,
        0xffff>()
);

// Get the stencil settings per-pass to achieve the given fill+region op effect on the
// stencil buffer.
//
// If drawDirectToClip comes back false, the caller must first draw the element into the user
// stencil bits, and then cover the clip area with multiple passes using the returned
// stencil settings.

// If drawDirectToClip is true, the returned array will only have one pass and the
// caller should use those stencil settings while drawing the element directly.
//
// This returns a null-terminated list of const GrUserStencilSettings*
GrUserStencilSettings const* const* get_stencil_passes(
        SkRegion::Op op,
        skgpu::ganesh::PathRenderer::StencilSupport stencilSupport,
        bool fillInverted,
        bool* drawDirectToClip) {
    bool canRenderDirectToStencil =
            skgpu::ganesh::PathRenderer::kNoRestriction_StencilSupport == stencilSupport;

    // TODO: inverse fill + intersect op can be direct.
    // TODO: this can be greatly simplified when we only need intersect and difference ops and
    //       none of the paths will be inverse-filled (just toggle the op instead).
    SkASSERT((unsigned)op <= SkRegion::kLastOp);
    if (canRenderDirectToStencil && !fillInverted) {
        GrUserStencilSettings const* const* directPass = gDirectDrawTable[op];
        if (directPass[0]) {
            *drawDirectToClip = true;
            return directPass;
        }
    }
    *drawDirectToClip = false;
    return gUserToClipTable[fillInverted][op];
}

void draw_stencil_rect(skgpu::ganesh::SurfaceDrawContext* sdc,
                       const GrHardClip& clip,
                       const GrUserStencilSettings* ss,
                       const SkMatrix& matrix,
                       const SkRect& rect,
                       GrAA aa) {
    GrPaint paint;
    paint.setXPFactory(GrDisableColorXPFactory::Get());
    sdc->stencilRect(&clip, ss, std::move(paint), aa, matrix, rect);
}

void draw_path(GrRecordingContext* rContext,
               skgpu::ganesh::SurfaceDrawContext* sdc,
               skgpu::ganesh::PathRenderer* pr,
               const GrHardClip& clip,
               const SkIRect& bounds,
               const GrUserStencilSettings* ss,
               const SkMatrix& matrix,
               const GrStyledShape& shape,
               GrAA aa) {
    GrPaint paint;
    paint.setXPFactory(GrDisableColorXPFactory::Get());

    // kMSAA is the only type of AA that's possible on a stencil buffer.
    GrAAType pathAAType = aa == GrAA::kYes ? GrAAType::kMSAA : GrAAType::kNone;

    skgpu::ganesh::PathRenderer::DrawPathArgs args{rContext,
                                                   std::move(paint),
                                                   ss,
                                                   sdc,
                                                   &clip,
                                                   &bounds,
                                                   &matrix,
                                                   &shape,
                                                   pathAAType,
                                                   false};
    pr->drawPath(args);
}

void stencil_path(GrRecordingContext* rContext,
                  skgpu::ganesh::SurfaceDrawContext* sdc,
                  skgpu::ganesh::PathRenderer* pr,
                  const GrFixedClip& clip,
                  const SkMatrix& matrix,
                  const GrStyledShape& shape,
                  GrAA aa) {
    skgpu::ganesh::PathRenderer::StencilPathArgs args;
    args.fContext = rContext;
    args.fSurfaceDrawContext = sdc;
    args.fClip = &clip;
    args.fClipConservativeBounds = &clip.scissorRect();
    args.fViewMatrix = &matrix;
    args.fShape = &shape;
    args.fDoStencilMSAA = aa;

    pr->stencilPath(args);
}

GrAA supported_aa(skgpu::ganesh::SurfaceDrawContext* sdc, GrAA aa) {
    return GrAA(sdc->numSamples() > 1 || sdc->canUseDynamicMSAA());
}

}  // namespace

namespace skgpu::ganesh {

StencilMaskHelper::StencilMaskHelper(GrRecordingContext* rContext,
                                     SurfaceDrawContext* sdc)
        : fContext(rContext)
        , fSDC(sdc)
        , fClip(sdc->dimensions()) {
}

bool StencilMaskHelper::init(const SkIRect& bounds, uint32_t genID,
                             const GrWindowRectangles& windowRects, int numFPs) {
    if (!fSDC->mustRenderClip(genID, bounds, numFPs)) {
        return false;
    }

    fClip.setStencilClip(genID);
    // Should have caught bounds not intersecting the render target much earlier in clip application
    SkAssertResult(fClip.fixedClip().setScissor(bounds));
    if (!windowRects.empty()) {
        fClip.fixedClip().setWindowRectangles(
                windowRects, GrWindowRectsState::Mode::kExclusive);
    }
    fNumFPs = numFPs;
    return true;
}

void StencilMaskHelper::drawRect(const SkRect& rect,
                                 const SkMatrix& matrix,
                                 SkRegion::Op op,
                                 GrAA aa) {
    if (rect.isEmpty()) {
        return;
    }

    bool drawDirectToClip;
    auto passes = get_stencil_passes(op, PathRenderer::kNoRestriction_StencilSupport,
                                     false, &drawDirectToClip);
    aa = supported_aa(fSDC, aa);

    if (!drawDirectToClip) {
        // Draw to client stencil bits first
        draw_stencil_rect(fSDC, fClip.fixedClip(), &gDrawToStencil, matrix, rect, aa);
    }

    // Now modify the clip bit (either by rendering directly), or by covering the bounding box
    // of the clip
    for (GrUserStencilSettings const* const* pass = passes; *pass; ++pass) {
        if (drawDirectToClip) {
            draw_stencil_rect(fSDC, fClip, *pass, matrix, rect, aa);
        } else {
            draw_stencil_rect(fSDC, fClip, *pass, SkMatrix::I(),
                              SkRect::Make(fClip.fixedClip().scissorRect()), aa);
        }
    }
}

bool StencilMaskHelper::drawPath(const SkPath& path,
                                 const SkMatrix& matrix,
                                 SkRegion::Op op,
                                 GrAA aa) {
    if (path.isEmpty()) {
        return true;
    }

    // drawPath follows a similar approach to drawRect(), where we either draw directly to the clip
    // bit or first draw to client bits and then apply a cover pass. The complicating factor is that
    // we rely on path rendering and how the chosen path renderer uses the stencil buffer.
    aa = supported_aa(fSDC, aa);

    GrAAType pathAAType = aa == GrAA::kYes ? GrAAType::kMSAA : GrAAType::kNone;

    // This will be used to determine whether the clip shape can be rendered into the
    // stencil with arbitrary stencil settings.
    PathRenderer::StencilSupport stencilSupport;

    // Make path canonical with regards to fill type (inverse handled by stencil settings).
    bool fillInverted = path.isInverseFillType();
    SkTCopyOnFirstWrite<SkPath> clipPath(path);
    if (fillInverted) {
        clipPath.writable()->toggleInverseFillType();
    }

    GrStyledShape shape(*clipPath, GrStyle::SimpleFill());
    SkASSERT(!shape.inverseFilled());

    PathRenderer::CanDrawPathArgs canDrawArgs;
    canDrawArgs.fCaps = fContext->priv().caps();
    canDrawArgs.fProxy = fSDC->asRenderTargetProxy();
    canDrawArgs.fClipConservativeBounds = &fClip.fixedClip().scissorRect();
    canDrawArgs.fViewMatrix = &matrix;
    canDrawArgs.fShape = &shape;
    canDrawArgs.fPaint = nullptr;
    canDrawArgs.fSurfaceProps = &fSDC->surfaceProps();
    canDrawArgs.fAAType = pathAAType;
    canDrawArgs.fHasUserStencilSettings = false;

    auto pr =  fContext->priv().drawingManager()->getPathRenderer(
            canDrawArgs, false, PathRendererChain::DrawType::kStencil, &stencilSupport);
    if (!pr) {
        return false;
    }

    bool drawDirectToClip;
    auto passes = get_stencil_passes(op, stencilSupport, fillInverted, &drawDirectToClip);

    // Write to client bits if necessary
    if (!drawDirectToClip) {
        if (stencilSupport == PathRenderer::kNoRestriction_StencilSupport) {
            draw_path(fContext, fSDC, pr, fClip.fixedClip(), fClip.fixedClip().scissorRect(),
                      &gDrawToStencil, matrix, shape, aa);
        } else {
            stencil_path(fContext, fSDC, pr, fClip.fixedClip(), matrix, shape, aa);
        }
    }

    // Now modify the clip bit (either by rendering directly), or by covering the bounding box
    // of the clip
    for (GrUserStencilSettings const* const* pass = passes; *pass; ++pass) {
        if (drawDirectToClip) {
            draw_path(fContext, fSDC, pr, fClip, fClip.fixedClip().scissorRect(),
                      *pass, matrix, shape, aa);
        } else {
            draw_stencil_rect(fSDC, fClip, *pass, SkMatrix::I(),
                              SkRect::Make(fClip.fixedClip().scissorRect()), aa);
        }
    }

    return true;
}

bool StencilMaskHelper::drawShape(const GrShape& shape,
                                  const SkMatrix& matrix,
                                  SkRegion::Op op,
                                  GrAA aa) {
    if (shape.isRect() && !shape.inverted()) {
        this->drawRect(shape.rect(), matrix, op, aa);
        return true;
    } else {
        SkPath p;
        shape.asPath(&p);
        return this->drawPath(p, matrix, op, aa);
    }
}

void StencilMaskHelper::clear(bool insideStencil) {
    if (fClip.fixedClip().hasWindowRectangles()) {
        // Use a draw to benefit from window rectangles when resetting the stencil buffer; for
        // large buffers with MSAA this can be significant.
        draw_stencil_rect(fSDC, fClip.fixedClip(),
                          GrStencilSettings::SetClipBitSettings(insideStencil), SkMatrix::I(),
                          SkRect::Make(fClip.fixedClip().scissorRect()), GrAA::kNo);
    } else {
        fSDC->clearStencilClip(fClip.fixedClip().scissorRect(), insideStencil);
    }
}

void StencilMaskHelper::finish() {
    fSDC->setLastClip(fClip.stencilStackID(), fClip.fixedClip().scissorRect(), fNumFPs);
}

}  // namespace skgpu::ganesh
