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

#ifndef skgpu_graphite_GraphiteTypes_DEFINED
#define skgpu_graphite_GraphiteTypes_DEFINED

#include "include/core/SkPoint.h"
#include "include/core/SkRect.h"
#include "include/core/SkTypes.h"
#include "include/gpu/GpuTypes.h"

#include <memory>

class SkSurface;

namespace skgpu {
class MutableTextureState;
}

namespace skgpu::graphite {

class BackendSemaphore;
class Recording;
class Task;

using GpuFinishedContext = void*;
using GpuFinishedProc = void (*)(GpuFinishedContext finishedContext, CallbackResult);

using GpuFinishedWithStatsProc = void (*)(GpuFinishedContext finishedContext,
                                          CallbackResult,
                                          const GpuStats&);

// NOTE: This can be converted to just an `enum class InsertStatus {}` once clients are migrated
// off of assuming `Context::insertRecording()` returns a boolean.
class InsertStatus {
public:
    // Do not refer to V directly; use these constants as if InsertStatus were a class enum, e.g.
    // InsertStatus::kSuccess.
    enum V {
        // Everything successfully added to underlying CommandBuffer
        kSuccess,
        // Recording or InsertRecordingInfo invalid, no CB changes
        kInvalidRecording,
        // Promise image instantiation failed, no CB changes
        kPromiseImageInstantiationFailed,
        // Internal failure, CB partially modified, state unrecoverable or unknown (e.g. dependent
        // texture uploads for future Recordings may or may not get executed)
        kAddCommandsFailed,
        // Internal failure, shader pipeline compilation failed (driver issue, or disk corruption),
        // state unrecoverable.
        kAsyncShaderCompilesFailed
    };

    constexpr InsertStatus() : fValue(kSuccess) {}
    /*implicit*/ constexpr InsertStatus(V v) : fValue(v) {}

    operator InsertStatus::V() const {
        return fValue;
    }

    // Assist migration from old bool return value of insertRecording; kSuccess is true,
    // all other error statuses are false.
    // NOTE: This is intentionally not explicit so that InsertStatus can be assigned correctly to
    // a bool or returned as a bool, since these are not boolean contexts that automatically apply
    // explicit bool operators (e.g. inside an if condition).
    operator bool() const {
        return fValue == kSuccess;
    }

private:
    V fValue;
};

/**
 * The fFinishedProc is called when the Recording has been submitted and finished on the GPU, or
 * when there is a failure that caused it not to be submitted. The callback will always be called
 * and the caller can use the callback to know it is safe to free any resources associated with
 * the Recording that they may be holding onto. If the Recording is successfully submitted to the
 * GPU the callback will be called with CallbackResult::kSuccess once the GPU has finished. All
 * other cases where some failure occurred it will be called with CallbackResult::kFailed.
 *
 * Alternatively, the client can provide fFinishedProcWithStats. This provides additional
 * information about execution of the recording on the GPU. Only the stats requested using
 * fStatsFlags will be valid and only if CallbackResult is kSuccess. If both fFinishedProc
 * and fFinishedProcWithStats are provided the latter is preferred and the former won't be
 * called.
 *
 * The fTargetSurface, if provided, is used as a target for any draws recorded onto a deferred
 * canvas returned from Recorder::makeDeferredCanvas. This target surface must be provided iff
 * the Recording contains any such draws. It must be Graphite-backed and its backing texture's
 * TextureInfo must match the info provided to the Recorder when making the deferred canvas.
 *
 * fTargetTranslation is an additional translation applied to draws targeting fTargetSurface.
 *
 * fTargetClip is an additional clip applied to draws targeting fTargetSurface. It is defined in the
 * local replay space, that is, with fTargetTranslation applied. An empty clip will not be applied.
 *
 * The client may pass in two arrays of initialized BackendSemaphores to be included in the
 * command stream. At some time before issuing commands in the Recording, the fWaitSemaphores will
 * be waited on by the gpu. We only guarantee these wait semaphores block transfer and fragment
 * shader work. Similarly, at some time after issuing the Recording's commands, the
 * fSignalSemaphores will be signaled by the gpu. Depending on the platform, the timing of the wait
 * and signal operations will either be immediately before or after the given Recording's command
 * stream, respectively, or before and after the entire CommandBuffer's command stream. The
 * semaphores are not sent to the GPU until the next Context::submit call is made.
 *
 * The client will own and be responsible for deleting the underlying semaphore objects after the
 * submission completes, however the BackendSemaphore objects themselves can be deleted as soon
 * as this function returns.
 */
struct InsertRecordingInfo {
    Recording* fRecording = nullptr;

    SkSurface* fTargetSurface = nullptr;
    SkIVector fTargetTranslation = {0, 0};
    SkIRect fTargetClip = {0, 0, 0, 0};
    MutableTextureState* fTargetTextureState = nullptr;

    size_t fNumWaitSemaphores = 0;
    BackendSemaphore* fWaitSemaphores = nullptr;
    size_t fNumSignalSemaphores = 0;
    BackendSemaphore* fSignalSemaphores = nullptr;

    GpuStatsFlags fGpuStatsFlags = GpuStatsFlags::kNone;
    GpuFinishedContext fFinishedContext = nullptr;
    GpuFinishedProc fFinishedProc = nullptr;
    GpuFinishedWithStatsProc fFinishedWithStatsProc = nullptr;

    // For unit testing purposes, this can be used to induce a known failure status from
    // Context::insertRecording(). When this set to anything other than kSuccess, insertRecording()
    // will operate as normal until the first condition that would normally return the simulated
    // status is encountered. At that point, operations are treated as if that condition had failed.
    // This leaves the Context in a state consistent with encountering the InsertStatus in a normal
    // application.
    //
    // NOTE: If the simulated failure status is one of the later error codes but the inserted
    // Recording would fail with an earlier error code normally, that error is still returned.
    InsertStatus fSimulatedStatus = InsertStatus::kSuccess;
};

/**
 * The fFinishedProc is called when the Recording has been submitted and finished on the GPU, or
 * when there is a failure that caused it not to be submitted. The callback will always be called
 * and the caller can use the callback to know it is safe to free any resources associated with
 * the Recording that they may be holding onto. If the Recording is successfully submitted to the
 * GPU the callback will be called with CallbackResult::kSuccess once the GPU has finished. All
 * other cases where some failure occured it will be called with CallbackResult::kFailed.
 */
struct InsertFinishInfo {
    InsertFinishInfo() = default;
    InsertFinishInfo(GpuFinishedContext context, GpuFinishedProc proc)
            : fFinishedContext{context}, fFinishedProc{proc} {}
    InsertFinishInfo(GpuFinishedContext context, GpuFinishedWithStatsProc proc)
            : fFinishedContext{context}, fFinishedWithStatsProc{proc} {}
    GpuFinishedContext fFinishedContext = nullptr;
    GpuFinishedProc fFinishedProc = nullptr;
    GpuFinishedWithStatsProc fFinishedWithStatsProc = nullptr;
    GpuStatsFlags fGpuStatsFlags = GpuStatsFlags::kNone;
};

/**
 * Actually submit work to the GPU and track its completion
 */
enum class SyncToCpu : bool {
    kYes = true,
    kNo = false
};

enum class MarkFrameBoundary : bool {
    kYes = true,
    kNo = false
};

struct SubmitInfo {
    SyncToCpu fSync = SyncToCpu::kNo;
    MarkFrameBoundary fMarkBoundary = MarkFrameBoundary::kNo;
    uint64_t fFrameID = 0;

    constexpr SubmitInfo() = default;

    constexpr SubmitInfo(SyncToCpu sync)
        : fSync(sync)
        , fMarkBoundary(MarkFrameBoundary::kNo)
        , fFrameID(0) {}

    constexpr SubmitInfo(SyncToCpu sync, uint64_t frameID)
        : fSync(sync)
        , fMarkBoundary(MarkFrameBoundary::kYes)
        , fFrameID(frameID) {}
};

/*
 * For Promise Images - should the Promise Image be fulfilled every time a Recording that references
 * it is inserted into the Context.
 */
enum class Volatile : bool {
    kNo = false,              // only fulfilled once
    kYes = true               // fulfilled on every insertion call
};

enum class DepthStencilFlags : int {
    kNone         = 0b000,
    kDepth        = 0b001,
    kStencil      = 0b010,
    kDepthStencil = kDepth | kStencil,
};

// NOTE: This can be converted to just an `enum class SampleCount {}` once clients are migrated
// off of writing integer values into backend TextureInfo fields or ContextOptions.
class SampleCount {
public:
    // Do not refer to V directly; use these constants as if SampleCount were a class enum, e.g.
    // SampleCount::k4.
    enum V : uint8_t {
        k1  = 1,
        k2  = 2,
        k4  = 4,
        k8  = 8,
        k16 = 16
    };

    constexpr SampleCount() : fValue(k1) {}
    /*implicit*/ constexpr SampleCount(V v) : fValue(v) {}

    // Behave like an enum
    constexpr bool operator ==(const SampleCount& o) const { return fValue == o.fValue; }
    constexpr bool operator  <(const SampleCount& o) const { return fValue  < o.fValue; }
    constexpr bool operator <=(const SampleCount& o) const { return fValue <= o.fValue; }
    constexpr bool operator  >(const SampleCount& o) const { return fValue  > o.fValue; }
    constexpr bool operator >=(const SampleCount& o) const { return fValue >= o.fValue; }

    // This needs to be explicit so that ternaries that return constants mixed with variables aren't
    // ambiguous; internal code can cast for switch statements.
    explicit constexpr operator SampleCount::V() const { return fValue; }
    explicit constexpr operator uint8_t()        const { return (uint8_t) fValue; }
    explicit constexpr operator unsigned int()   const { return (unsigned int) fValue; }

    // Assist migration from old code that would assign integers to sample count fields that used
    // to be uint8_t and are now more strictly typed to SampleCount. Asserts if the value doesn't
    // match a SampleCount value.
    /*implicit*/ constexpr SampleCount(uint8_t v) : fValue((V) v) {
        SkASSERT(v == 1 || v == 2 || v == 4 || v == 8 || v == 16);
    }
    constexpr SampleCount& operator=(uint8_t sampleCount) {
        return (*this = SampleCount(sampleCount));
    }

private:
    V fValue;
};

/**
 * Convert an integer value to a strictly typed SampleCount value, rounding down to the lowest
 * valid sample count if needed if `sampleCount` is not already equivalent.
 */
constexpr SampleCount ToSampleCount(uint32_t sampleCount) {
    return sampleCount >= 16 ? SampleCount::k16 :
           sampleCount >= 8  ? SampleCount::k8  :
           sampleCount >= 4  ? SampleCount::k4  :
           sampleCount >= 2  ? SampleCount::k2  :
                               SampleCount::k1;
}

/*
 * This enum allows mapping from a set of observed RenderSteps (e.g., from a GraphicsPipeline
 * printout) to the correct 'drawTypes' parameter needed by the Precompilation API.
 */
enum DrawTypeFlags : uint16_t {

    kNone             = 0,

    // kBitmapText_Mask should be used for the BitmapTextRenderStep[mask] RenderStep
    kBitmapText_Mask  = 1 << 0,
    // kBitmapText_LCD should be used for the BitmapTextRenderStep[LCD] RenderStep
    kBitmapText_LCD   = 1 << 1,
    // kBitmapText_Color should be used for the BitmapTextRenderStep[color] RenderStep
    kBitmapText_Color = 1 << 2,
    // kSDFText should be used for the SDFTextRenderStep RenderStep
    kSDFText          = 1 << 3,
    // kSDFText_LCD should be used for the SDFTextLCDRenderStep RenderStep
    kSDFText_LCD      = 1 << 4,

    // kDrawVertices should be used to generate Pipelines that use the following RenderSteps:
    //    VerticesRenderStep[*] for:
    //        [Tris], [TrisTexCoords], [TrisColor], [TrisColorTexCoords],
    //        [Tristrips], [TristripsTexCoords], [TristripsColor], [TristripsColorTexCoords]
    kDrawVertices     = 1 << 5,

    // kCircularArc renders filled circular arcs, with or without the center included, and
    // stroked circular arcs with butt or round caps that don't include the center point.
    // It corresponds to the CircularArcRenderStep.
    kCircularArc      = 1 << 6,

    // kSimpleShape should be used to generate Pipelines that use the following RenderSteps:
    //    AnalyticRRectRenderStep
    //    PerEdgeAAQuadRenderStep
    //    CoverBoundsRenderStep[NonAAFill]
    kAnalyticRRect    = 1 << 7,
    kPerEdgeAAQuad    = 1 << 8,
    kNonAAFillRect    = 1 << 9,

    kSimpleShape      = kAnalyticRRect | kPerEdgeAAQuad | kNonAAFillRect,

    // kNonSimpleShape should be used to generate Pipelines that use the following RenderSteps:
    //    CoverageMaskRenderStep
    //    CoverBoundsRenderStep[*] for [InverseCover], [RegularCover]
    //    TessellateStrokeRenderStep
    //    TessellateWedgesRenderStep[*] for [Convex], [EvenOdd], [Winding]
    //    TessellateCurvesRenderStep[*] for [EvenOdd], [Winding]
    //    MiddleOutFanRenderStep[*] for [EvenOdd], [Winding]
    kNonSimpleShape   = 1 << 10,

    // This draw type covers all the methods Skia uses to draw drop shadows. It can be used to
    // generate Pipelines which, as part of their labels, have:
    //     the AnalyticBlurRenderStep
    //     VerticesRenderStep[TrisColor] with a GaussianColorFilter
    // For this draw type the PaintOptions parameter to Precompile() will be ignored.
    kDropShadows      = 1 << 11,

    // kAnalyticClip should be combined with the primary drawType for Pipelines that contain
    // either of the following sub-strings:
    //    AnalyticClip
    //    AnalyticAndAtlasClip
    kAnalyticClip     = 1 << 12,

    kLast = kAnalyticClip,
};

} // namespace skgpu::graphite

#endif // skgpu_graphite_GraphiteTypes_DEFINED
