/*
 * Copyright 2022 Rive
 */

#pragma once

#include "rive/enum_bitset.hpp"
#include "rive/math/aabb.hpp"
#include "rive/math/mat2d.hpp"
#include "rive/math/vec2d.hpp"
#include "rive/math/simd.hpp"
#include "rive/shapes/paint/blend_mode.hpp"
#include "rive/shapes/paint/color.hpp"
#include "rive/renderer/trivial_block_allocator.hpp"
#include "rive/shapes/paint/image_sampler.hpp"

namespace rive
{
class GrInnerFanTriangulator;
class RenderBuffer;
} // namespace rive

// This header defines constants and data structures for Rive's pixel local
// storage path rendering algorithm.
//
// Main algorithm:
// https://docs.google.com/document/d/19Uk9eyFxav6dNSYsI2ZyiX9zHU1YOaJsMB2sdDFVz6s/edit
//
// Batching multiple unique paths:
// https://docs.google.com/document/d/1DLrQimS5pbNaJJ2sAW5oSOsH6_glwDPo73-mtG5_zns/edit
//
// Batching strokes as well:
// https://docs.google.com/document/d/1CRKihkFjbd1bwT08ErMCP4fwSR7D4gnHvgdw_esY9GM/edit
namespace rive::gpu
{
class Draw;
class Gradient;
class RenderContextImpl;
class RenderTarget;
class Texture;

// Global MipMap LOD Bias to apply to samplers. Going lower leads to sharper
// filtering at the expense of potential shimmering.
constexpr static float MIP_MAP_LOD_BIAS = -.5f;

// Tessellate in parametric space until each segment is within 1/4 pixel of the
// true curve.
constexpr static int kParametricPrecision = 4;

// Tessellate in polar space until the outset edge is within 1/8 pixel of the
// true stroke.
constexpr static int kPolarPrecision = 8;

// Maximum supported numbers of tessellated segments in a single curve.
constexpr static uint32_t kMaxParametricSegments = 1023;
constexpr static uint32_t kMaxPolarSegments = 1023;

// The Gaussian distribution is very blurry on the outer edges. Regardless of
// how wide a feather is, the polar segments never need to have a finer angle
// than this value.
constexpr static float FEATHER_POLAR_SEGMENT_MIN_ANGLE = math::PI / 16;

// cos(FEATHER_MIN_POLAR_SEGMENT_ANGLE / 2)
constexpr static float COS_FEATHER_POLAR_SEGMENT_MIN_ANGLE_OVER_2 =
    0.99518472667f;

// We allocate all our GPU buffers in rings. This ensures the CPU can prepare
// frames in parallel while the GPU renders them.
constexpr static int kBufferRingSize = 3;

// Every coverage value in pixel local storage has an associated 16-bit path ID.
// This ID enables us to batch multiple paths together without having to clear
// the coverage buffer in between. This ID is implemented as an fp16, so the
// maximum path ID therefore cannot be NaN (or conservatively, all 5 exponent
// bits cannot be 1's). We also skip denormalized values (exp == 0) because they
// have been empirically unreliable on Android as ID values.
constexpr static int kLargestFP16BeforeExponentAll1s = (0x1f << 10) - 1;
constexpr static int kLargestDenormalizedFP16 = 1023;
constexpr static int MaxPathID(int granularity)
{
    // Floating point equality gets funky when the exponent bits are all 1's, so
    // the largest pathID we can support is kLargestFP16BeforeExponentAll1s.
    //
    // The shader converts an integer path ID to fp16 as:
    //
    //     (id + kLargestDenormalizedFP16) * granularity
    //
    // So the largest path ID we can support is as follows.
    return kLargestFP16BeforeExponentAll1s / granularity -
           kLargestDenormalizedFP16;
}

// Each contour has its own unique ID, which it uses to index a data record
// containing per-contour information. This value is currently 16 bit.
constexpr static size_t kMaxContourID = 65535;
constexpr static uint32_t kContourIDMask = 0xffff;
static_assert((kMaxContourID & kContourIDMask) == kMaxContourID);

// Tessellation is performed by rendering vertices into a data texture. These
// values define the dimensions of the tessellation data texture.
constexpr static size_t kTessTextureWidth =
    2048; // GL_MAX_TEXTURE_SIZE spec minimum on ES3/WebGL2.
constexpr static size_t kTessTextureWidthLog2 = 11;
static_assert(1 << kTessTextureWidthLog2 == kTessTextureWidth);

// Gradients are implemented by sampling a horizontal ramp of pixels allocated
// in a global gradient texture.
constexpr static uint32_t kGradTextureWidth = 512;
constexpr static uint32_t kGradTextureWidthInSimpleRamps =
    kGradTextureWidth / 2;

// Depth/stencil parameters
constexpr static float DEPTH_MIN = 0.0f;
constexpr static float DEPTH_MAX = 1.0f;
constexpr static uint8_t STENCIL_CLEAR = 0u;

// Backend-specific capabilities/workarounds and fine tuning.
struct PlatformFeatures
{
    // Supported InterlockModes.
    // FIXME: MSAA is implicit even though it isn't implemented on all backends.
    bool supportsRasterOrderingMode = false;
    bool supportsAtomicMode = false;
    bool supportsClockwiseMode = false;
    // InterlockMode::Clockwise with fixedFunctionColorOutput and srcOver blend.
    // (Only viable for frames that don't use advanced blend.)
    bool supportsClockwiseFixedFunctionMode = false;
    bool supportsClockwiseAtomicMode = false;
    // Use KHR_blend_equation_advanced in msaa mode?
    bool supportsBlendAdvancedKHR = false;
    bool supportsBlendAdvancedCoherentKHR = false;
    // Required for @ENABLE_CLIP_RECT in msaa mode.
    bool supportsClipPlanes = false;
    bool avoidFlatVaryings = false;
    // Vivo Y21 (PowerVR Rogue GE8320; OpenGL ES 3.2 build 1.13@5776728a) seems
    // to hit some sort of reset condition that corrupts pixel local storage
    // when rendering a complex feather. Provide a workaround that allows the
    // implementation to opt in to always feathering to the atlas instead of
    // rendering directly to the screen.
    bool alwaysFeatherToAtlas = false;
    // clipSpaceBottomUp specifies whether the top of the viewport, in clip
    // coordinates, is at Y=+1 (OpenGL, Metal, D3D, WebGPU) or Y=-1 (Vulkan).
    //
    // framebufferBottomUp specifies whether "row 0" of the framebuffer is the
    // bottom of the image (OpenGL) or the top (Metal, D3D, WebGPU, Vulkan).
    //
    //
    //                                OpenGL
    //           (clipSpaceBottomUp=true, framebufferBottomUp=true)
    //
    //  Rive Pixel Space             Clip Space              Framebuffer
    //
    //  0 ----------->                   ^ +1                ^ height
    //  |          width                 |                   |
    //  |                         -1     |     +1            |
    //  |                 ===>    <------|------>    ===>    |
    //  |                                |                   |
    //  |                                |                   |          width
    //  v height                         v -1                0 ----------->
    //
    //
    //
    //                            Metal/D3D/WebGPU
    //           (clipSpaceBottomUp=true, framebufferBottomUp=false)
    //
    //  Rive Pixel Space             Clip Space              Framebuffer
    //
    //  0 ----------->                   ^ +1                0 ----------->
    //  |          width                 |                   |          width
    //  |                         -1     |     +1            |
    //  |                 ===>    <------|------>    ===>    |
    //  |                                |                   |
    //  |                                |                   |
    //  v height                         v -1                v height
    //
    //
    //
    //                                Vulkan
    //          (clipSpaceBottomUp=false, framebufferBottomUp=false)
    //
    //  Rive Pixel Space             Clip Space              Framebuffer
    //
    //  0 ----------->                   ^ -1                0 ----------->
    //  |          width                 |                   |          width
    //  |                         -1     |     +1            |
    //  |                 ===>    <------|------>    ===>    |
    //  |                                |                   |
    //  |                                |                   |
    //  v height                         v +1                v height
    //
    bool clipSpaceBottomUp = false;
    bool framebufferBottomUp = false;
    // Backend cannot initialize PLS with typical clear/load APIs in atomic
    // mode. Issue a "DrawType::renderPassInitialize" draw instead.
    bool atomicPLSInitNeedsDraw = false;
    // Backend API does not support initializing our (transient) MSAA color
    // buffer with the existing (non-MSAA) target texture at the beginning of a
    // render pass. Draw the previous renderTarget contents into it manually via
    // DrawType::renderPassInitialize when LoadAction::preserveRenderTarget is
    // specified.
    bool msaaColorPreserveNeedsDraw = false;
    // Workaround for precision issues. Determines how far apart we space unique
    // path IDs when they will be bit-casted to fp16.
    uint8_t pathIDGranularity = 1;
    // Maximum size (width or height) of a texture.
    uint32_t maxTextureSize = 2048;
    // Maximum length (in 32-bit uints) of the coverage buffer used for paths in
    // clockwiseFill/atomic mode. 2^27 bytes is the minimum storage buffer size
    // requirement in the Vulkan, GL, and D3D11 specs. Metal guarantees 256 MB.
    size_t maxCoverageBufferLength = (1 << 27) / sizeof(uint32_t);
};

// Gradient color stops are implemented as a horizontal span of pixels in a
// global gradient texture. They are rendered by "GradientSpan" instances.
struct GradientSpan
{
    // x0Fixed and x1Fixed are normalized texel x coordinates, in the
    // fixed-point range 0..65535.
    RIVE_ALWAYS_INLINE void set(uint32_t x0Fixed,
                                uint32_t x1Fixed,
                                uint32_t y,
                                uint32_t flags,
                                ColorInt color0_,
                                ColorInt color1_)
    {
        assert(x0Fixed < 65536);
        assert(x1Fixed < 65536);
        horizontalSpan = (x1Fixed << 16) | x0Fixed;
        yWithFlags = flags | y;
        color0 = color0_;
        color1 = color1_;
    }
    uint32_t horizontalSpan;
    uint32_t yWithFlags;
    uint32_t color0;
    uint32_t color1;
};
static_assert(sizeof(GradientSpan) == sizeof(uint32_t) * 4);
static_assert(256 % sizeof(GradientSpan) == 0);
// Metal requires vertex buffers to be 256-byte aligned.
constexpr static size_t kGradSpanBufferAlignmentInElements =
    256 / sizeof(GradientSpan);

// Gradient spans are drawn as 1px-tall triangle strips with 3 sub-rectangles.
constexpr uint32_t GRAD_SPAN_TRI_STRIP_VERTEX_COUNT = 8;

// Each curve gets tessellated into vertices. This is performed by rendering a
// horizontal span of positions and normals into the tessellation data texture,
// GP-GPU style. TessVertexSpan defines one instance of a horizontal
// tessellation span for rendering.
//
// Each span has an optional reflection, rendered right to left, with the same
// vertices in reverse order. These are used to draw mirrored patches with
// negative coverage when we have back-face culling enabled. This emits every
// triangle twice, once clockwise and once counterclockwise, and back-face
// culling naturally selects the triangle with the appropriately signed coverage
// (discarding the other).
struct TessVertexSpan
{
    RIVE_ALWAYS_INLINE void set(const Vec2D pts_[4],
                                Vec2D joinTangent_,
                                float y_,
                                int32_t x0,
                                int32_t x1,
                                uint32_t parametricSegmentCount,
                                uint32_t polarSegmentCount,
                                uint32_t joinSegmentCount,
                                uint32_t contourIDWithFlags_)
    {
        set(pts_,
            joinTangent_,
            y_,
            x0,
            x1,
            std::numeric_limits<float>::quiet_NaN(), // Discard the reflection.
            -1,
            -1,
            parametricSegmentCount,
            polarSegmentCount,
            joinSegmentCount,
            contourIDWithFlags_);
    }

    RIVE_ALWAYS_INLINE void set(const Vec2D pts_[4],
                                Vec2D joinTangent_,
                                float y_,
                                int32_t x0,
                                int32_t x1,
                                float reflectionY_,
                                int32_t reflectionX0,
                                int32_t reflectionX1,
                                uint32_t parametricSegmentCount,
                                uint32_t polarSegmentCount,
                                uint32_t joinSegmentCount,
                                uint32_t contourIDWithFlags_)
    {
#ifndef NDEBUG
        // Write to an intermediate local object in debug mode, so we can check
        // its values. (Otherwise we can't read it because mapped memory is
        // write-only.)
        TessVertexSpan localCopy;
#define LOCAL(VAR) localCopy.VAR
#else
#define LOCAL(VAR) VAR
#endif
        RIVE_INLINE_MEMCPY(LOCAL(pts), pts_, sizeof(LOCAL(pts)));
        LOCAL(joinTangent) = joinTangent_;
        LOCAL(y) = y_;
        LOCAL(reflectionY) = reflectionY_;
        LOCAL(x0x1) = (x1 << 16 | (x0 & 0xffff));
        LOCAL(reflectionX0X1) = (reflectionX1 << 16 | (reflectionX0 & 0xffff));
        LOCAL(segmentCounts) = (joinSegmentCount << 20) |
                               (polarSegmentCount << 10) |
                               parametricSegmentCount;
        LOCAL(contourIDWithFlags) = contourIDWithFlags_;
#undef LOCAL

        // Ensure we didn't lose any data from packing.
        assert(localCopy.x0x1 << 16 >> 16 == x0);
        assert(localCopy.x0x1 >> 16 == x1);
        assert(localCopy.reflectionX0X1 << 16 >> 16 == reflectionX0);
        assert(localCopy.reflectionX0X1 >> 16 == reflectionX1);
        assert((localCopy.segmentCounts & 0x3ff) == parametricSegmentCount);
        assert(((localCopy.segmentCounts >> 10) & 0x3ff) == polarSegmentCount);
        assert(localCopy.segmentCounts >> 20 == joinSegmentCount);

#ifndef NDEBUG
        memcpy(this, &localCopy, sizeof(*this));
#endif
    }

    Vec2D pts[4];      // Cubic bezier curve.
    Vec2D joinTangent; // Ending tangent of the join that follows the cubic.
    float y;
    float reflectionY;
    int32_t x0x1;
    int32_t reflectionX0X1;
    uint32_t segmentCounts;      // [joinSegmentCount, polarSegmentCount,
                                 // parametricSegmentCount]
    uint32_t contourIDWithFlags; // flags | contourID
};
static_assert(sizeof(TessVertexSpan) == sizeof(float) * 16);
static_assert(256 % sizeof(TessVertexSpan) == 0);
// Metal requires vertex buffers to be 256-byte aligned.
constexpr static size_t kTessVertexBufferAlignmentInElements =
    256 / sizeof(TessVertexSpan);

// Tessellation spans are drawn as two distinct, 1px-tall rectangles: the span
// and its reflection.
constexpr uint16_t kTessSpanIndices[4 * 3] =
    {0, 1, 2, 2, 1, 3, 4, 5, 6, 6, 5, 7};

// ImageRects are a special type of non-overlapping antialiased draw that we
// only have to use in atomic mode. They allow us to bind a texture and draw it
// in its entirety in a single pass.
struct ImageRectVertex
{
    float x;
    float y;
    float aaOffsetX;
    float aaOffsetY;
};

constexpr ImageRectVertex kImageRectVertices[12] = {
    {0, 0, .0, -1},
    {1, 0, .0, -1},
    {1, 0, +1, .0},
    {1, 1, +1, .0},
    {1, 1, .0, +1},
    {0, 1, .0, +1},
    {0, 1, -1, .0},
    {0, 0, -1, .0},
    {0, 0, +1, +1},
    {1, 0, -1, +1},
    {1, 1, -1, -1},
    {0, 1, +1, -1},
};

constexpr uint16_t kImageRectIndices[14 * 3] = {
    8,  0, 9, 9, 0, 1,  1,  2, 9, 9, 2, 10, 10, 2, 3, 3, 4,  10, 10, 4, 11,
    11, 4, 5, 5, 6, 11, 11, 6, 8, 8, 6, 7,  7,  0, 8, 9, 10, 8,  10, 8, 11,
};

enum class PaintType : uint32_t
{
    clipUpdate, // Update the clip buffer instead of drawing to the framebuffer.
    solidColor,
    linearGradient,
    radialGradient,
    image,
};

// Specifies the location of a simple or complex horizontal color ramp within
// the gradient texture. A simple color ramp is two texels wide, beginning at
// the specified row and column. A complex color ramp spans the entire width of
// the gradient texture, on the row:
//     "GradTextureLayout::complexOffsetY + ColorRampLocation::row".
struct ColorRampLocation
{
    constexpr static uint16_t kComplexGradientMarker = 0xffff;
    bool isComplex() const { return col == kComplexGradientMarker; }
    uint16_t row;
    uint16_t col;
};

// Most of a paint's information can be described in a single value. Gradients
// and images reference an additional Gradient* and Texture* respectively.
union SimplePaintValue
{
    ColorInt color = 0xff000000;         // PaintType::solidColor
    ColorRampLocation colorRampLocation; // Paintype::linear/radialGradient
    float imageOpacity;                  // PaintType::image
    uint32_t outerClipID;                // Paintype::clipUpdate
};
static_assert(sizeof(SimplePaintValue) == 4);

// This class encapsulates a matrix that maps from _fragCoord to a space where
// the clipRect is the normalized rectangle: [-1, -1, +1, +1]
class ClipRectInverseMatrix
{
public:
    // When the clipRect inverse matrix is singular (e.g., all 0 in scale and
    // skew), the shader uses tx and ty as fixed clip coverage values instead of
    // finding edge distances.
    constexpr static ClipRectInverseMatrix WideOpen()
    {
        return Mat2D{0, 0, 0, 0, 1, 1};
    }
    constexpr static ClipRectInverseMatrix Empty()
    {
        return Mat2D{0, 0, 0, 0, 0, 0};
    }

    ClipRectInverseMatrix() = default;

    ClipRectInverseMatrix(const Mat2D& clipMatrix, const AABB& clipRect)
    {
        reset(clipMatrix, clipRect);
    }

    void reset(const Mat2D& clipMatrix, const AABB& clipRect);

    const Mat2D& inverseMatrix() const { return m_inverseMatrix; }

private:
    constexpr ClipRectInverseMatrix(const Mat2D& inverseMatrix) :
        m_inverseMatrix(inverseMatrix)
    {}
    Mat2D m_inverseMatrix;
};

// Specifies the height of the gradient texture, and the row at which we
// transition from simple color ramps to complex.
//
// This information is computed at flush time, once we know exactly how many
// color ramps of each type will be in the gradient texture.
struct GradTextureLayout
{
    uint32_t complexOffsetY; // Row of the first complex gradient.
    float inverseHeight;     // 1 / textureHeight
};

// Once all curves in a contour have been tessellated, we render the tessellated
// vertices in "patches" (aka specific instanced geometry).
//
// See:
// https://docs.google.com/document/d/19Uk9eyFxav6dNSYsI2ZyiX9zHU1YOaJsMB2sdDFVz6s/edit#heading=h.fa4kubk3vimk
//
// With strokes:
// https://docs.google.com/document/d/1CRKihkFjbd1bwT08ErMCP4fwSR7D4gnHvgdw_esY9GM/edit#heading=h.dcd0c58pxfs5
//
// A single patch spans N tessellation segments, connecting N + 1 tessellation
// vertices. It is composed of a an AA border and fan triangles. The specifics
// of the fan triangles depend on the PatchType.
enum class PatchType
{
    // Patches fan around the contour midpoint. Outer edges are inset by ~1px,
    // followed by a ~1px AA ramp.
    midpointFan,

    // Similar to midpointFan, except AA ramps are split down the center and
    // drawn with a ~1/2px outset AA ramp and a ~1/2px inset AA ramp that
    // overlaps the inner tessellation and has negative coverage.
    midpointFanCenterAA,

    // Patches only cover the AA ramps and interiors of bezier curves. The
    // interior path triangles that connect the outer curves are triangulated on
    // the CPU to eliminate overlap, and are drawn in a separate call. AA ramps
    // are split down the center (on the same lines as the interior
    // triangulation), and drawn with a ~1/2px outset AA ramp and a ~1/2px inset
    // AA ramp that overlaps the inner tessellation and has negative coverage. A
    // lone bowtie join is emitted at the end of the patch to tie the outer
    // curves together.
    outerCurves,
};

// When tessellating path vertices, we have the ability to generate the
// triangles wound in forward or reverse order. Depending on the path and the
// rendering algorithm, we will either want the triangles wound forward,
// reverse, or BOTH.
enum class ContourDirections
{
    forward,
    reverse,
    // Generate two tessellations of the contour: reverse first, then forward.
    reverseThenForward,
    // Generate two tessellations of the contour: forward first, then reverse.
    forwardThenReverse,
};
constexpr static bool ContourDirectionsAreDoubleSided(
    ContourDirections contourDirections)
{
    return contourDirections >= ContourDirections::reverseThenForward;
}

struct PatchVertex
{
    void set(float localVertexID_,
             float outset_,
             float fillCoverage_,
             float params_)
    {
        localVertexID = localVertexID_;
        outset = outset_;
        fillCoverage = fillCoverage_;
        params = params_;
        setMirroredPosition(localVertexID_, outset_, fillCoverage_);
    }

    // Patch vertices can have an optional, alternate position when mirrored.
    // This is so we can ensure the diagonals inside the stroke line up on both
    // versions of the patch (mirrored and not).
    void setMirroredPosition(float localVertexID_,
                             float outset_,
                             float fillCoverage_)
    {
        mirroredVertexID = localVertexID_;
        mirroredOutset = outset_;
        mirroredFillCoverage = fillCoverage_;
    }

    float localVertexID; // 0 or 1 -- which tessellated vertex of the two that
                         // we are connecting?
    float outset; // Outset from the tessellated position, in the direction of
                  // the normal.
    float fillCoverage; // 0..1 for the stroke. 1 all around for the triangles.
                        // (Coverage will be negated later for counterclockwise
                        // triangles.)
    int32_t params;     // "(patchSize << 2) | [flags::kStrokeVertex,
                        //                      flags::kFanVertex,
                        //                      flags::kFanMidpointVertex]"
    float mirroredVertexID;
    float mirroredOutset;
    float mirroredFillCoverage;
    int32_t padding = 0;
};
static_assert(sizeof(PatchVertex) == sizeof(float) * 8);

// # of tessellation segments spanned by the midpoint fan patch.
constexpr static uint32_t kMidpointFanPatchSegmentSpan = 8;

// # of tessellation segments spanned by the outer curve patch. (In this
// particular instance, the final segment is a bowtie join with zero length and
// no fan triangle.)
constexpr static uint32_t kOuterCurvePatchSegmentSpan = 17;

// Define vertex and index buffers that contain all the triangles in every
// PatchType.
constexpr static uint32_t kMidpointFanPatchVertexCount =
    kMidpointFanPatchSegmentSpan * 4 /*Stroke and/or AA outer ramp*/ +
    (kMidpointFanPatchSegmentSpan + 1) /*Curve fan*/ +
    1 /*Triangle from path midpoint*/;
constexpr static uint32_t kMidpointFanPatchBorderIndexCount =
    kMidpointFanPatchSegmentSpan * 6 /*Stroke and/or AA outer ramp*/;
constexpr static uint32_t kMidpointFanPatchIndexCount =
    kMidpointFanPatchBorderIndexCount /*Stroke and/or AA outer ramp*/ +
    (kMidpointFanPatchSegmentSpan - 1) * 3 /*Curve fan*/ +
    3 /*Triangle from path midpoint*/;
constexpr static uint32_t kMidpointFanPatchBaseIndex = 0;
static_assert((kMidpointFanPatchBaseIndex * sizeof(uint16_t)) % 4 == 0);

constexpr static uint32_t kMidpointFanCenterAAPatchVertexCount =
    kMidpointFanPatchSegmentSpan * 4 * 2 /*Stroke and/or AA outer ramp*/ +
    (kMidpointFanPatchSegmentSpan + 1) /*Curve fan*/ +
    1 /*Triangle from path midpoint*/;
constexpr static uint32_t kMidpointFanCenterAAPatchBorderIndexCount =
    kMidpointFanPatchSegmentSpan * 12 /*Stroke and/or AA outer ramp*/;
constexpr static uint32_t kMidpointFanCenterAAPatchIndexCount =
    kMidpointFanCenterAAPatchBorderIndexCount /*Stroke and/or AA outer ramp*/ +
    (kMidpointFanPatchSegmentSpan - 1) * 3 /*Curve fan*/ +
    3 /*Triangle from path midpoint*/;
constexpr static uint32_t kMidpointFanCenterAAPatchBaseIndex =
    kMidpointFanPatchBaseIndex + kMidpointFanPatchIndexCount;
static_assert((kMidpointFanCenterAAPatchBaseIndex * sizeof(uint16_t)) % 4 == 0);

constexpr static uint32_t kOuterCurvePatchVertexCount =
    kOuterCurvePatchSegmentSpan * 8 /*AA center ramp with bowtie*/ +
    kOuterCurvePatchSegmentSpan /*Curve fan*/;
constexpr static uint32_t kOuterCurvePatchBorderIndexCount =
    kOuterCurvePatchSegmentSpan * 12 /*AA center ramp with bowtie*/;
constexpr static uint32_t kOuterCurvePatchIndexCount =
    kOuterCurvePatchBorderIndexCount /*AA center ramp with bowtie*/ +
    (kOuterCurvePatchSegmentSpan - 2) * 3 /*Curve fan*/;
constexpr static uint32_t kOuterCurvePatchBaseIndex =
    kMidpointFanCenterAAPatchBaseIndex + kMidpointFanCenterAAPatchIndexCount;
static_assert((kOuterCurvePatchBaseIndex * sizeof(uint16_t)) % 4 == 0);

constexpr static uint32_t kPatchVertexBufferCount =
    kMidpointFanPatchVertexCount + kMidpointFanCenterAAPatchVertexCount +
    kOuterCurvePatchVertexCount;
constexpr static uint32_t kPatchIndexBufferCount =
    kMidpointFanPatchIndexCount + kMidpointFanCenterAAPatchIndexCount +
    kOuterCurvePatchIndexCount;
void GeneratePatchBufferData(PatchVertex[kPatchVertexBufferCount],
                             uint16_t indices[kPatchIndexBufferCount]);

enum class DrawType : uint8_t
{
    // Fills, strokes, feathered strokes.
    midpointFanPatches,

    // Feathered fills.
    midpointFanCenterAAPatches,

    // Just the outer curves of a path; the interior will be triangulated.
    outerCurvePatches,

    interiorTriangulation,
    atlasBlit,
    imageRect,
    imageMesh,

    // MSAA strokes can't be merged with fills because they require their own
    // dedicated stencil settings.
    msaaStrokes,

    // MSAA "fast" path: (effectively) single pass rendering.
    msaaMidpointFanBorrowedCoverage,
    msaaMidpointFans,
    msaaMidpointFanStencilReset,

    // MSAA "slow" path: stencil-then-cover.
    msaaMidpointFanPathsStencil,
    msaaMidpointFanPathsCover,

    // MSAA interior triangulation is not currently supported, but this one draw
    // type is included in order to support the "retrofittedcubictriangles" GM.
    msaaOuterCubics,

    // Clear or intersect (based on DrawContents) the stencil clip bit.
    msaaStencilClipReset,

    // Clear/init render pass data with a fullscreen draw when we can't do it
    // with existing clear/load APIs. (e.g., for pixel local storage in buffers
    // that don't have copy/clear commands, or preserving existing color data in
    // a transient MSAA arrachment).
    renderPassInitialize,

    // Resolve render pass data (e.g., by applying the final deferred color in
    // atomic mode, or copying an offscreen attachment to the final
    // renderTarget).
    renderPassResolve,

};

constexpr static bool DrawTypeIsImageDraw(DrawType drawType)
{
    switch (drawType)
    {
        case DrawType::imageRect:
        case DrawType::imageMesh:
            return true;
        case DrawType::midpointFanPatches:
        case DrawType::midpointFanCenterAAPatches:
        case DrawType::outerCurvePatches:
        case DrawType::interiorTriangulation:
        case DrawType::atlasBlit:
        case DrawType::msaaStrokes:
        case DrawType::msaaMidpointFanBorrowedCoverage:
        case DrawType::msaaMidpointFans:
        case DrawType::msaaMidpointFanStencilReset:
        case DrawType::msaaMidpointFanPathsStencil:
        case DrawType::msaaMidpointFanPathsCover:
        case DrawType::msaaOuterCubics:
        case DrawType::msaaStencilClipReset:
        case DrawType::renderPassInitialize:
        case DrawType::renderPassResolve:
            return false;
    }
    RIVE_UNREACHABLE();
}

constexpr static uint32_t PatchIndexCount(DrawType drawType)
{
    switch (drawType)
    {
        case DrawType::midpointFanPatches:
            return kMidpointFanPatchIndexCount;
        case DrawType::midpointFanCenterAAPatches:
            return kMidpointFanCenterAAPatchIndexCount;
        case DrawType::outerCurvePatches:
            return kOuterCurvePatchIndexCount;
        case DrawType::msaaStrokes:
            return kMidpointFanPatchBorderIndexCount;
        case DrawType::msaaMidpointFanBorrowedCoverage:
        case DrawType::msaaMidpointFans:
        case DrawType::msaaMidpointFanStencilReset:
        case DrawType::msaaMidpointFanPathsStencil:
        case DrawType::msaaMidpointFanPathsCover:
            return kMidpointFanPatchIndexCount -
                   kMidpointFanPatchBorderIndexCount;
        case DrawType::msaaOuterCubics:
            return kOuterCurvePatchIndexCount -
                   kOuterCurvePatchBorderIndexCount;
        case DrawType::interiorTriangulation:
        case DrawType::atlasBlit:
        case DrawType::imageRect:
        case DrawType::imageMesh:
        case DrawType::msaaStencilClipReset:
        case DrawType::renderPassInitialize:
        case DrawType::renderPassResolve:
            RIVE_UNREACHABLE();
    }
    RIVE_UNREACHABLE();
}

constexpr static uint32_t PatchBaseIndex(DrawType drawType)
{
    switch (drawType)
    {
        case DrawType::midpointFanPatches:
        case DrawType::msaaStrokes:
            return kMidpointFanPatchBaseIndex;
        case DrawType::midpointFanCenterAAPatches:
            return kMidpointFanCenterAAPatchBaseIndex;
        case DrawType::outerCurvePatches:
            return kOuterCurvePatchBaseIndex;
        case DrawType::msaaMidpointFanBorrowedCoverage:
        case DrawType::msaaMidpointFans:
        case DrawType::msaaMidpointFanStencilReset:
        case DrawType::msaaMidpointFanPathsStencil:
        case DrawType::msaaMidpointFanPathsCover:
            return kMidpointFanPatchBaseIndex +
                   kMidpointFanPatchBorderIndexCount;
        case DrawType::msaaOuterCubics:
            return kOuterCurvePatchBaseIndex + kOuterCurvePatchBorderIndexCount;
        case DrawType::interiorTriangulation:
        case DrawType::atlasBlit:
        case DrawType::imageRect:
        case DrawType::imageMesh:
        case DrawType::msaaStencilClipReset:
        case DrawType::renderPassInitialize:
        case DrawType::renderPassResolve:
            RIVE_UNREACHABLE();
    }
    RIVE_UNREACHABLE();
}

// Specifies what to do with the render target at the beginning of a flush.
enum class LoadAction
{
    clear,
    preserveRenderTarget,
    dontCare,
};

// Synchronization method for pixel local storage with overlapping fragments.
enum class InterlockMode
{
    rasterOrdering,
    atomics,
    // Overrides every path's fill rule with clockwise, and implements the
    // clockwise algorithm using raster ordering hardware.
    // TODO: Once polished, this mode can be mixed into "rasterOrdering" and
    // used selectively for clockwise paths.
    clockwise,
    // Use an experimental path rendering algorithm that utilizes atomics
    // without barriers. This requires that we override all paths' fill rules
    // (winding or even/odd) with a "clockwise" fill rule, where only regions
    // with a positive winding number get filled.
    clockwiseAtomic,
    msaa,
};
constexpr static size_t INTERLOCK_MODE_COUNT = 5;
// # of bits required to contain an InterlockMode.
constexpr static size_t INTERLOCK_MODE_BIT_COUNT = 3;
static_assert(INTERLOCK_MODE_COUNT <= (1 << INTERLOCK_MODE_BIT_COUNT));
static_assert(INTERLOCK_MODE_COUNT > (1 << (INTERLOCK_MODE_BIT_COUNT - 1)));

// Low-level batch of scissored geometry for rendering to the offscreen atlas.
struct AtlasDrawBatch
{
    TAABB<uint16_t> scissor;
    uint32_t patchCount;
    uint32_t basePatch;
};

// "Uber shader" features that can be #defined in a draw shader.
// This set is strictly limited to switches that don't *change* the behavior of
// the shader, i.e., turning them all on will enable all types Rive content, but
// simple content will still draw identically; we can turn a feature off if we
// know a batch doesn't need it for better performance.
enum class ShaderFeatures
{
    NONE = 0,

    // Whole program features.
    ENABLE_CLIPPING = 1 << 0,
    ENABLE_CLIP_RECT = 1 << 1,
    ENABLE_ADVANCED_BLEND = 1 << 2,
    ENABLE_FEATHER = 1 << 3,

    // Fragment-only features.
    ENABLE_EVEN_ODD = 1 << 4,
    ENABLE_NESTED_CLIPPING = 1 << 5,
    ENABLE_HSL_BLEND_MODES = 1 << 6,
};
RIVE_MAKE_ENUM_BITSET(ShaderFeatures)
constexpr static size_t kShaderFeatureCount = 7;
constexpr static ShaderFeatures kAllShaderFeatures =
    static_cast<gpu::ShaderFeatures>((1 << kShaderFeatureCount) - 1);
constexpr static ShaderFeatures kVertexShaderFeaturesMask =
    ShaderFeatures::ENABLE_CLIPPING | ShaderFeatures::ENABLE_CLIP_RECT |
    ShaderFeatures::ENABLE_ADVANCED_BLEND | ShaderFeatures::ENABLE_FEATHER;

// These shader features change the way atomic pipelines are set up (or cause
//  validation failures when enabled but not used)
constexpr static ShaderFeatures kExclusiveAtomicUbershaderFeaturesMask =
    ShaderFeatures::ENABLE_ADVANCED_BLEND;

constexpr static ShaderFeatures ShaderFeaturesMaskFor(
    InterlockMode interlockMode)
{
    switch (interlockMode)
    {
        case InterlockMode::rasterOrdering:
            return kAllShaderFeatures;
        case InterlockMode::atomics:
            return kAllShaderFeatures & ~ShaderFeatures::ENABLE_NESTED_CLIPPING;
        case InterlockMode::clockwise:
            return kAllShaderFeatures & ~ShaderFeatures::ENABLE_EVEN_ODD;
        case InterlockMode::clockwiseAtomic:
            // TODO: shader features aren't fully implemented yet in
            // clockwiseAtomic mode.
            return ShaderFeatures::ENABLE_FEATHER;
        case InterlockMode::msaa:
            return ShaderFeatures::ENABLE_CLIP_RECT |
                   ShaderFeatures::ENABLE_ADVANCED_BLEND |
                   ShaderFeatures::ENABLE_HSL_BLEND_MODES;
    }
    RIVE_UNREACHABLE();
}

// Miscellaneous switches that *do* affect the behavior of the fragment shader.
// The renderContext may add some of these, and a backend may also add them to a
// shader key if it wants to implement the behavior.
enum class ShaderMiscFlags : uint32_t
{
    none = 0,

    // InterlockMode::atomics only (without advanced blend). Render color to a
    // standard attachment instead of PLS. The backend implementation is
    // responsible to turn on src-over blending.
    fixedFunctionColorOutput = 1 << 0,

    // Override all paths' fill rules (winding or even/odd) with an experimental
    // "clockwise" fill rule, where only regions with a positive winding number
    // get filled.
    clockwiseFill = 1 << 1,

    // This shader only renders to the clip buffer. It doesn't output color.
    clipUpdateOnly = 1 << 2,

    // clockwise and clockwiseAtomic modes only. This shader renders a pass that
    // only subtracts (counterclockwise) borrowed coverage from the coverage
    // buffer. It doesn't output color or clip.
    // If drawing interior triangulations, every fragment will be the first of
    // the path at its pixel, so it can blindly overwrite coverage without
    // reading the buffer and subtracting.
    borrowedCoveragePass = 1 << 3,

    // DrawType::renderPassInitialize only. Also store the color clear value to
    // PLS when drawing a clear, in addition to clearing the other PLS planes.
    storeColorClear = 1 << 4,

    // DrawType::renderPassInitialize only. Swizzle the existing framebuffer
    // contents from BGRA to RGBA. (For when this data had to get copied from a
    // BGRA target.)
    swizzleColorBGRAToRGBA = 1 << 5,

    // DrawType::renderPassResolve only. Optimization for when rendering to an
    // offscreen texture.
    //
    // It renders the final "resolve" operation directly to the renderTarget in
    // a single pass, instead of (1) resolving the offscreen texture, and then
    // (2) copying the offscreen texture to back the renderTarget.
    coalescedResolveAndTransfer = 1 << 6,
};
RIVE_MAKE_ENUM_BITSET(ShaderMiscFlags)

constexpr static ShaderFeatures ShaderFeaturesMaskFor(
    DrawType drawType,
    InterlockMode interlockMode)
{
    ShaderFeatures mask = ShaderFeatures::NONE;
    switch (drawType)
    {
        case DrawType::imageRect:
        case DrawType::imageMesh:
        case DrawType::atlasBlit:
            if (interlockMode != InterlockMode::atomics)
            {
                mask = ShaderFeatures::ENABLE_CLIPPING |
                       ShaderFeatures::ENABLE_CLIP_RECT |
                       ShaderFeatures::ENABLE_ADVANCED_BLEND |
                       ShaderFeatures::ENABLE_HSL_BLEND_MODES;
                break;
            }
            // Since atomic mode has to resolve previous draws, images need to
            // consider the same shader features for path draws.
            [[fallthrough]];
        case DrawType::midpointFanPatches:
        case DrawType::midpointFanCenterAAPatches:
        case DrawType::outerCurvePatches:
        case DrawType::interiorTriangulation:
        case DrawType::msaaStrokes:
        case DrawType::msaaMidpointFanBorrowedCoverage:
        case DrawType::msaaMidpointFans:
        case DrawType::msaaMidpointFanStencilReset:
        case DrawType::msaaMidpointFanPathsStencil:
        case DrawType::msaaMidpointFanPathsCover:
        case DrawType::msaaOuterCubics:
            mask = kAllShaderFeatures;
            break;
        case DrawType::msaaStencilClipReset:
            mask = ShaderFeatures::NONE;
            break;
        case DrawType::renderPassInitialize:
            if (interlockMode == InterlockMode::atomics)
            {
                // Atomic mode initializes clipping and color (when advanced
                // blend is active).
                mask = ShaderFeatures::ENABLE_CLIPPING |
                       ShaderFeatures::ENABLE_ADVANCED_BLEND;
            }
            else
            {
                assert(interlockMode == InterlockMode::msaa);
                // MSAA mode only needs to initialize color, and only when
                // preserving the render target but using a transient MSAA
                // attachment.
                mask = ShaderFeatures::NONE;
            }
            break;
        case DrawType::renderPassResolve:
            if (interlockMode == InterlockMode::atomics)
            {
                mask = kAllShaderFeatures;
            }
            else
            {
                assert(interlockMode == InterlockMode::rasterOrdering ||
                       interlockMode == InterlockMode::msaa);
                mask = ShaderFeatures::NONE;
            }
            break;
    }
    return mask & ShaderFeaturesMaskFor(interlockMode);
}

// Returns the flags that are valid for an ubershader version of the currently-
//  requested shader feature set. There are some shader features that change
//  how the render passes are set up in atomic mode that need to be accounted
//  for beyond just using ShaderFeaturesMaskFor.
constexpr static ShaderFeatures UbershaderFeaturesMaskFor(
    ShaderFeatures requestedFeatures,
    DrawType drawType,
    InterlockMode interlockMode,
    const PlatformFeatures& platformFeatures)
{
    ShaderFeatures outFeatures = ShaderFeaturesMaskFor(drawType, interlockMode);
    if (interlockMode == InterlockMode::atomics)
    {
        // Turn off the exclusive atomic features unless they're set in our
        //  requested feature flags.
        outFeatures &=
            (requestedFeatures | ~kExclusiveAtomicUbershaderFeaturesMask);
    }

    // Ensure that we haven't dropped features we care about somehow
    assert((requestedFeatures & outFeatures) == requestedFeatures);

    // ENABLE_CLIP_RECT shouldn't be set if we're in MSAA mode without clip
    // plane support.
    if (interlockMode == InterlockMode::msaa &&
        !platformFeatures.supportsClipPlanes)
    {
        outFeatures &= ~ShaderFeatures::ENABLE_CLIP_RECT;
    }
    return outFeatures;
}

// Returns a unique value that can be used to key a shader.
uint32_t ShaderUniqueKey(DrawType,
                         ShaderFeatures,
                         InterlockMode,
                         ShaderMiscFlags);

extern const char* GetShaderFeatureGLSLName(ShaderFeatures feature);

// Flags indicating the contents of a draw. These don't affect shaders, but in
// msaa mode they are needed to break up batching. (msaa needs different
// stencil/blend state, depending on the DrawContents.)
//
// These also affect the draw sort order, so we attempt associate more expensive
// shader branch misses with higher flags.
enum class DrawContents
{
    none = 0,
    opaquePaint = 1 << 0,
    // Put feathered fills down low because they only need to draw different
    // geometry, which isn't really a context switch at all.
    featheredFill = 1 << 1,
    stroke = 1 << 2,
    clockwiseFill = 1 << 3,
    nonZeroFill = 1 << 4,
    evenOddFill = 1 << 5,
    activeClip = 1 << 6,
    advancedBlend = 1 << 7,
    // Put clip updates last because they use an entirely different shader in
    // clockwise mode.
    clipUpdate = 1 << 8,
};
RIVE_MAKE_ENUM_BITSET(DrawContents)

// A nestedClip draw updates the clip buffer while simultaneously clipping
// against the outerClip that is currently in the clip buffer.
constexpr static gpu::DrawContents kNestedClipUpdateMask =
    (gpu::DrawContents::activeClip | gpu::DrawContents::clipUpdate);

// Types of barriers that may be required between DrawBatches.
enum class BarrierFlags : uint8_t
{
    none = 0,

    // Pixel-local dependency in the PLS planes. (Atomic mode only.) Ensure
    // prior draws complete at each pixel before beginning new ones.
    plsAtomic = 1 << 0,
    plsAtomicPreResolve = 1 << 1, // Once before the final resolve.

    // MSAA needs a special barrier (e.g., subpass transition) after manually
    // loading the render target into the transient MSAA attachment.
    msaaPostInit = 1 << 2,

    // Pixel-local dependency in the coverage buffer. (clockwiseAtomic mode
    // only.) All "borrowed coverage" draws have now been issued. Ensure they
    // complete at each pixel before beginning the "forward coverage" draws.
    clockwiseBorrowedCoverage = 1 << 3,

    // The next DrawBatch needs to perform an advanced blend, but the current
    // hardware requires an implementation-dependent barrier before reading the
    // dstColor (pipeline barrier for input attachments, KHR blend barrier, or
    // even a full MSAA resolve & blit into a separate texture.)
    dstBlend = 1 << 4,

    // Special barrier (e.g., subpass transition) issued prior to a manual
    // render pass resolve. (Only applicable with
    // FlushDescriptor::manuallyResolved.)
    preManualResolve = 1 << 5,

    // Only prevent future DrawBatches from being combined with the current
    // drawList. (No GPU dependencies.)
    drawBatchBreak = 1 << 6,
};
RIVE_MAKE_ENUM_BITSET(BarrierFlags);

// Low-level batch of geometry to submit to the GPU.
struct DrawBatch
{
    DrawBatch(DrawType drawType_,
              ShaderMiscFlags shaderMiscFlags_,
              DrawContents drawContents_,
              uint32_t elementCount_,
              uint32_t baseElement_,
              rive::BlendMode blendMode_,
              rive::ImageSampler imageSampler_,
              BarrierFlags barrierFlags_) :
        drawType(drawType_),
        shaderMiscFlags(shaderMiscFlags_),
        drawContents(drawContents_),
        elementCount(elementCount_),
        baseElement(baseElement_),
        firstBlendMode(blendMode_),
        barriers(barrierFlags_),
        imageSampler(imageSampler_)
    {}

    const DrawType drawType;
    ShaderMiscFlags shaderMiscFlags;
    DrawContents drawContents;
    uint32_t elementCount; // Vertex, index, or instance count.
    uint32_t baseElement;  // Base vertex, index, or instance.
    rive::BlendMode firstBlendMode;
    BarrierFlags barriers; // Barriers to execute before drawing this batch.

    ShaderFeatures shaderFeatures = ShaderFeatures::NONE;

    // DrawType::imageRect and DrawType::imageMesh.
    uint32_t imageDrawDataOffset = 0;
    Texture* imageTexture = nullptr;
    const ImageSampler imageSampler = ImageSampler::LinearClamp();

    // DrawType::imageMesh.
    RenderBuffer* vertexBuffer;
    RenderBuffer* uvBuffer;
    RenderBuffer* indexBuffer;

    // When shaders don't have a mechanism to read the framebuffer (e.g.,
    // WebGL msaa), this is a linked list of all the draws in the batch whose
    // bounding boxes needs to be blitted to the "dstRead" texture before
    // drawing.
    const Draw* dstReadList = nullptr;

    // Pointer to the next DrawBatchatch in the list that has a
    // "BarrierFlags::dstBlend" barrier.
    // When we need advanced blend but the underlying graphics API doesn't
    // support reading the framebuffer, this can be helpful for breaking up the
    // drawList into multiple render passes with framebuffer copies in between.
    const DrawBatch* nextDstBlendBarrier = nullptr;
    // Link to the next batch to render in the drawList. DrawBatch always exists
    // in a linked list.

    const DrawBatch* next = nullptr;
};

// Simple gradients only have 2 texels, so we write them to mapped texture
// memory from the CPU instead of rendering them.
struct TwoTexelRamp
{
    ColorInt color0, color1;
};
static_assert(sizeof(TwoTexelRamp) == 8 * sizeof(uint8_t));

#ifdef WITH_RIVE_TOOLS

enum class SynthesizedFailureType
{
    none,
    ubershaderLoad,
    shaderCompilation,
    pipelineCreation,
};

#endif

// Detailed description of exactly how a RenderContextImpl should bind its
// buffers and draw a flush. A typical flush is done in 4 steps:
//
//  1. Render the complex gradients from the gradSpanBuffer to the gradient
//     texture (gradSpanCount, firstComplexGradSpan, complexGradRowsTop,
//     complexGradRowsHeight).
//
//  2. Transfer the simple gradient texels from the simpleColorRampsBuffer to
//     the top of the gradient texture (simpleGradTexelsWidth,
//     simpleGradTexelsHeight, simpleGradDataOffsetInBytes, tessDataHeight).
//
//  3. Render the tessellation texture from the tessVertexSpanBuffer
//     (tessVertexSpanCount, firstTessVertexSpan).
//
//  4. Execute the drawList, reading from the newly rendered resource textures.
//
struct FlushDescriptor
{
    RenderTarget* renderTarget = nullptr;
    ShaderFeatures combinedShaderFeatures = ShaderFeatures::NONE;
    InterlockMode interlockMode = InterlockMode::rasterOrdering;
    int msaaSampleCount = 0; // (0 unless interlockMode is msaa.)

    LoadAction colorLoadAction = LoadAction::clear;
    ColorInt colorClearValue = 0; // When loadAction == LoadAction::clear.
    uint32_t coverageClearValue = 0;
    float depthClearValue = DEPTH_MAX;
    uint8_t stencilClearValue = STENCIL_CLEAR;

    IAABB renderTargetUpdateBounds; // drawBounds, or renderTargetBounds if
                                    // loadAction == LoadAction::clear.

    // True if the drawList ends with a "renderPassResolve" draw, in which case
    // the backend may need to perform special setup for a custom resolve.
    bool manuallyResolved = false;

    // True if shaders will never read the color buffer, meaning, the render
    // pass can use a more efficient setup that renders to a standard color
    // attachment and handles all blending via built-in blend state.
    // NOTE: This may be false even if all paints use srcOver because some
    // rendering modes (e.g., rasterOrdering with evenOdd/nonZero) always read
    // the color buffer, regardless of blend mode.
    bool fixedFunctionColorOutput = false;

    // Physical size of the atlas texture.
    uint16_t atlasTextureWidth;
    uint16_t atlasTextureHeight;

    // Boundaries of the content for this specific flush within the atlas
    // texture.
    uint16_t atlasContentWidth;
    uint16_t atlasContentHeight;

    // Monotonically increasing prefix that gets appended to the most
    // significant "32 - CLOCKWISE_COVERAGE_BIT_COUNT" bits of coverage buffer
    // values.
    //
    // The coverage buffer is used in clockwiseAtomic mode.
    //
    // Increasing this prefix implicitly clears the entire coverage buffer to
    // zero.
    uint32_t coverageBufferPrefix = 0;

    // (clockwiseAtomic mode only.) We usually don't have to clear the coverage
    // buffer because of coverageBufferPrefix, but when this value is true, the
    // entire coverage buffer must be cleared to zero before rendering.
    bool needsCoverageBufferClear = false;

    size_t flushUniformDataOffsetInBytes = 0;
    uint32_t pathCount = 0;
    size_t firstPath = 0;
    size_t firstPaint = 0;
    size_t firstPaintAux = 0;
    uint32_t contourCount = 0;
    size_t firstContour = 0;
    uint32_t gradSpanCount = 0;
    size_t firstGradSpan = 0;
    uint32_t tessVertexSpanCount = 0;
    size_t firstTessVertexSpan = 0;
    uint32_t gradDataHeight = 0;
    uint32_t tessDataHeight = 0;
    // Override path fill rules with "clockwise".
    bool clockwiseFillOverride = false;
    bool hasTriangleVertices = false;
    bool wireframe = false;
#ifdef WITH_RIVE_TOOLS
    // Synthesize compilation failures to make sure the device handles them
    // gracefully. (e.g., by falling back on an uber shader or at least not
    // crashing.) Valid compilations may fail in the real world if the device is
    // pressed for resources or in a bad state.
    SynthesizedFailureType synthesizedFailureType =
        SynthesizedFailureType::none;
#endif

    // Command buffer that rendering commands will be added to.
    //  - VkCommandBuffer on Vulkan.
    //  - id<MTLCommandBuffer> on Metal.
    //  - Unused otherwise.
    void* externalCommandBuffer = nullptr;

    // List of feathered fills (if any) that must be rendered to the atlas
    // before the main render pass.
    const AtlasDrawBatch* atlasFillBatches = nullptr;
    size_t atlasFillBatchCount = 0;

    // List of feathered strokes (if any) that must be rendered to the atlas
    // before the main render pass.
    const AtlasDrawBatch* atlasStrokeBatches = nullptr;
    size_t atlasStrokeBatchCount = 0;

    // List of draws in the main render pass. These are rendered directly to the
    // renderTarget.
    const BlockAllocatedLinkedList<DrawBatch>* drawList = nullptr;
    const DrawBatch* firstDstBlendBarrier = nullptr;
};

// Returns the area of the (potentially non-rectangular) quadrilateral that
// results from transforming the given bounds by the given matrix.
float find_transformed_area(const AABB& bounds, const Mat2D&);

// Convert a BlendMode to the tightly-packed range used by PLS shaders.
uint32_t ConvertBlendModeToPLSBlendMode(BlendMode riveMode);

// Swizzles the byte order of ColorInt to litte-endian RGBA (the order expected
// by GLSL).
RIVE_ALWAYS_INLINE uint32_t SwizzleRiveColorToRGBA(ColorInt riveColor)
{
    return (riveColor & 0xff00ff00) |
           (math::rotateleft32(riveColor, 16) & 0x00ff00ff);
}

// Swizzles the byte order of ColorInt to litte-endian RGBA (the order expected
// by GLSL), and premultiplies alpha.
uint32_t SwizzleRiveColorToRGBAPremul(ColorInt riveColor);

// Used for fields that are used to layout write-only mapped GPU memory.
// "volatile" to discourage the compiler from generating code that reads these
// values (e.g., don't let the compiler generate "x ^= x" instead of "x = 0").
// "RIVE_MAYBE_UNUSED" to suppress -Wunused-private-field.
#define WRITEONLY RIVE_MAYBE_UNUSED volatile

// Per-flush shared uniforms used by all shaders.
struct FlushUniforms
{
public:
    FlushUniforms(const FlushDescriptor&, const PlatformFeatures&);

    FlushUniforms(const FlushUniforms& other) { *this = other; }

    void operator=(const FlushUniforms& rhs)
    {
        memcpy(static_cast<void*>(this),
               &rhs,
               sizeof(*this) - sizeof(m_padTo256Bytes));
    }

    bool operator!=(const FlushUniforms& rhs) const
    {
        return memcmp(this, &rhs, sizeof(*this) - sizeof(m_padTo256Bytes)) != 0;
    }

private:
    class InverseViewports
    {
    public:
        InverseViewports() = default;

        InverseViewports(const FlushDescriptor&, const PlatformFeatures&);

    private:
        // [complexGradientsY, tessDataY, renderTargetX,  renderTargetY]
        WRITEONLY float m_vals[4];
    };

    WRITEONLY InverseViewports m_inverseViewports;
    WRITEONLY uint32_t m_renderTargetWidth;
    WRITEONLY uint32_t m_renderTargetHeight;
    // Only used if clears are implemented as draws.
    WRITEONLY uint32_t m_colorClearValue;
    // Only used if clears are implemented as draws.
    WRITEONLY uint32_t m_coverageClearValue;
    // drawBounds, or renderTargetBounds if there is a clear. (Used by the
    // "@RESOLVE_PLS" step in InterlockMode::atomics.)
    WRITEONLY IAABB m_renderTargetUpdateBounds;
    WRITEONLY Vec2D m_atlasTextureInverseSize; // 1 / [atlasWidth, atlasHeight]
    WRITEONLY Vec2D m_atlasContentInverseViewport; // 2 / atlasContentBounds
    // Monotonically increasing prefix that gets appended to the most
    // significant "32 - CLOCKWISE_COVERAGE_BIT_COUNT" bits of coverage buffer
    // values. (clockwiseAtomic mode only.)
    WRITEONLY uint32_t m_coverageBufferPrefix;
    // Spacing between adjacent path IDs (1 if IEEE compliant).
    WRITEONLY uint32_t m_pathIDGranularity;
    WRITEONLY float m_vertexDiscardValue;
    WRITEONLY float m_mipMapLODBias;
    WRITEONLY uint32_t m_wireframeEnabled; // Forces coverage to solid.
    // Uniform blocks must be multiples of 256 bytes in size.
    WRITEONLY uint8_t m_padTo256Bytes[256 - 84];
};
static_assert(sizeof(FlushUniforms) == 256);

// Storage buffers are logically layed out as arrays of structs on the CPU, but
// the GPU shaders access them as arrays of basic types. We do it this way in
// order to be able to easily polyfill them with textures.
//
// This enum defines the underlying basic type that each storage buffer struct
// is layed on top of.
enum StorageBufferStructure
{
    uint32x4,
    uint32x2,
    float32x4,
};

constexpr static uint32_t StorageBufferElementSizeInBytes(
    StorageBufferStructure bufferStructure)
{
    switch (bufferStructure)
    {
        case StorageBufferStructure::uint32x4:
            return sizeof(uint32_t) * 4;
        case StorageBufferStructure::uint32x2:
            return sizeof(uint32_t) * 2;
        case StorageBufferStructure::float32x4:
            return sizeof(float) * 4;
    }
    RIVE_UNREACHABLE();
}

// Defines a transform from screen space into a region of the atlas.
// The atlas may have a different scale factor than the screen.
struct AtlasTransform
{
    float scaleFactor;
    float translateX;
    float translateY;
};

// Defines a sub-allocation for a path's coverage data within the
// renderContext's coverage buffer. (clockwiseAtomic mode only.)
struct CoverageBufferRange
{
    // Index of the first pixel of this allocation within the coverage buffer.
    // Must be a multiple of 32*32.
    uint32_t offset;
    // Line width in pixels of the image in this coverage allocation.
    // Must be a multiple of 32.
    uint32_t pitch;
    // Offset from screen space to image coords within the coverage allocation.
    float offsetX;
    float offsetY;
};

// High level structure of the "path" storage buffer. Each path has a unique
// data record on the GPU that is accessed from the vertex shader.
struct PathData
{
public:
    constexpr static StorageBufferStructure kBufferStructure =
        StorageBufferStructure::uint32x4;

    void set(const Mat2D&,
             float strokeRadius,
             float featherRadius,
             uint32_t zIndex,
             const AtlasTransform&,
             const CoverageBufferRange&);

private:
    WRITEONLY float m_matrix[6];
    // "0" indicates that the path is filled, not stroked.
    WRITEONLY float m_strokeRadius;
    WRITEONLY float m_featherRadius;
    // InterlockMode::msaa.
    WRITEONLY uint32_t m_zIndex;
    // Only used when rendering coverage via the atlas.
    WRITEONLY AtlasTransform m_atlasTransform;
    // InterlockMode::clockwiseAtomic.
    WRITEONLY CoverageBufferRange m_coverageBufferRange;
};
static_assert(sizeof(PathData) ==
              StorageBufferElementSizeInBytes(PathData::kBufferStructure) * 4);
static_assert(256 % sizeof(PathData) == 0);
constexpr static size_t kPathBufferAlignmentInElements = 256 / sizeof(PathData);

// High level structure of the "paint" storage buffer. Each path also has a data
// small record describing its paint at a high level. Complex paints (gradients,
// images, or any path with a clipRect) store additional rendering info in the
// PaintAuxData buffer.
struct PaintData
{
public:
    constexpr static StorageBufferStructure kBufferStructure =
        StorageBufferStructure::uint32x2;

    void set(DrawContents singleDrawContents,
             PaintType,
             SimplePaintValue,
             GradTextureLayout,
             uint32_t clipID,
             bool hasClipRect,
             BlendMode);

private:
    WRITEONLY uint32_t m_params; // [clipID, flags, paintType]
    union
    {
        WRITEONLY uint32_t m_color;     // PaintType::solidColor
        WRITEONLY float m_gradTextureY; // Paintype::linearGradient,
                                        // Paintype::radialGradient
        WRITEONLY float m_opacity;      // PaintType::image
        WRITEONLY uint32_t m_shiftedClipReplacementID; // PaintType::clipUpdate
    };
};
static_assert(sizeof(PaintData) ==
              StorageBufferElementSizeInBytes(PaintData::kBufferStructure));
static_assert(256 % sizeof(PaintData) == 0);
constexpr static size_t kPaintBufferAlignmentInElements =
    256 / sizeof(PaintData);

// Structure of the "paintAux" storage buffer. Gradients, images, and clipRects
// store their details here, indexed by pathID.
struct PaintAuxData
{
public:
    constexpr static StorageBufferStructure kBufferStructure =
        StorageBufferStructure::float32x4;

    void set(const Mat2D& viewMatrix,
             PaintType,
             SimplePaintValue,
             const Gradient*,
             const Texture*,
             const ClipRectInverseMatrix*,
             const RenderTarget*,
             const gpu::PlatformFeatures&);

private:
    WRITEONLY float m_matrix[6]; // Maps _fragCoord to paint coordinates.
    union
    {
        WRITEONLY float
            m_gradTextureHorizontalSpan[2]; // Paintype::linearGradient,
                                            // Paintype::radialGradient
        WRITEONLY float m_imageTextureLOD;  // PaintType::image
    };

    WRITEONLY float m_clipRectInverseMatrix[6]; // Maps _fragCoord to normalized
                                                // clipRect coords.
    WRITEONLY Vec2D m_inverseFwidth; // -1 / fwidth(matrix * _fragCoord) -- for
                                     // antialiasing.
};
static_assert(sizeof(PaintAuxData) ==
              StorageBufferElementSizeInBytes(PaintAuxData::kBufferStructure) *
                  4);
static_assert(256 % sizeof(PaintAuxData) == 0);
constexpr static size_t kPaintAuxBufferAlignmentInElements =
    256 / sizeof(PaintAuxData);

// High level structure of the "contour" storage buffer. Each contour of every
// path has a data record describing its info.
struct ContourData
{
public:
    constexpr static StorageBufferStructure kBufferStructure =
        StorageBufferStructure::uint32x4;

    ContourData(Vec2D midpoint, uint32_t pathID, uint32_t vertexIndex0) :
        m_midpoint(midpoint), m_pathID(pathID), m_vertexIndex0(vertexIndex0)
    {}

private:
    WRITEONLY Vec2D
        m_midpoint; // Midpoint of the curve endpoints in just this contour.
    WRITEONLY uint32_t m_pathID; // ID of the path this contour belongs to.
    WRITEONLY uint32_t m_vertexIndex0; // Index of the first tessellation vertex
                                       // of the contour.
};
static_assert(sizeof(ContourData) ==
              StorageBufferElementSizeInBytes(ContourData::kBufferStructure));
static_assert(256 % sizeof(ContourData) == 0);
constexpr static size_t kContourBufferAlignmentInElements =
    256 / sizeof(ContourData);

// Per-vertex data for shaders that draw triangles.
struct TriangleVertex
{
public:
    TriangleVertex() = default;
    TriangleVertex(Vec2D point, int16_t weight, uint16_t pathID) :
        m_point(point),
        m_weight_pathID((static_cast<int32_t>(weight) << 16) | pathID)
    {}

#ifdef TESTING
    Vec2D testing_point() const { return {m_point.x, m_point.y}; }
    int32_t testing_weight_pathID() const { return m_weight_pathID; }
#endif

private:
    WRITEONLY Vec2D m_point;
    WRITEONLY int32_t m_weight_pathID; // [(weight << 16 | pathID]
};
static_assert(sizeof(TriangleVertex) == sizeof(float) * 3);

// Per-draw uniforms used by image meshes.
struct ImageDrawUniforms
{
public:
    ImageDrawUniforms() = default;

    ImageDrawUniforms(const Mat2D&,
                      float opacity,
                      const ClipRectInverseMatrix*,
                      uint32_t clipID,
                      BlendMode,
                      uint32_t zIndex);

private:
    WRITEONLY float m_matrix[6];
    WRITEONLY float m_opacity;
    WRITEONLY float m_padding = 0;
    WRITEONLY float m_clipRectInverseMatrix[6];
    WRITEONLY uint32_t m_clipID;
    WRITEONLY uint32_t m_blendMode;
    WRITEONLY uint32_t m_zIndex; // gpu::InterlockMode::msaa only.
    // Uniform blocks must be multiples of 256 bytes in size.
    WRITEONLY uint8_t m_padTo256Bytes[256 - 68];

    constexpr void staticChecks()
    {
        static_assert(offsetof(ImageDrawUniforms, m_matrix) % 16 == 0);
        static_assert(
            offsetof(ImageDrawUniforms, m_clipRectInverseMatrix) % 16 == 0);
        static_assert(sizeof(ImageDrawUniforms) == 256);
    }
};

#undef WRITEONLY

// The maximum number of storage buffers we will ever use in a vertex or
// fragment shader.
constexpr static size_t kMaxStorageBuffers = 4;

// If the backend doesn't support "kMaxStorageBuffers" a shader, we polyfill
// with textures. This function returns the dimensions to use for these
// textures.
std::tuple<uint32_t, uint32_t> StorageTextureSize(size_t bufferSizeInBytes,
                                                  StorageBufferStructure);

// If the backend doesn't support "kMaxStorageBuffers" in a shader, we polyfill
// with textures. The polyfill texture needs to be updated in entire rows at a
// time, meaning, its transfer buffer might need to be larger than requested.
// This function returns a size that is large enough to service a worst-case
// texture update.
size_t StorageTextureBufferSize(size_t bufferSizeInBytes,
                                StorageBufferStructure);

// Should the triangulator emit triangles with negative winding, positive
// winding, or both?
enum class WindingFaces
{
    negative = 1 << 0,
    positive = 1 << 1,
    all = negative | positive,
};
RIVE_MAKE_ENUM_BITSET(WindingFaces)

// Represents a block of mapped GPU memory. Since it can be extremely expensive
// to read mapped memory, we use this class to enforce the write-only nature of
// this memory.
template <typename T> class WriteOnlyMappedMemory
{
public:
    WriteOnlyMappedMemory() { reset(); }
    WriteOnlyMappedMemory(T* ptr, size_t elementCount)
    {
        reset(ptr, elementCount);
    }

    void reset() { reset(nullptr, 0); }

    void reset(T* ptr, size_t elementCount)
    {
        m_mappedMemory = ptr;
        m_nextMappedItem = ptr;
        m_mappingEnd = ptr + elementCount;
    }

    using MapResourceBufferFn =
        void* (RenderContextImpl::*)(size_t mapSizeInBytes);
    void mapElements(RenderContextImpl* impl,
                     MapResourceBufferFn mapFn,
                     size_t elementCount)
    {
        assert(m_mappedMemory == nullptr);
        void* ptr = (impl->*mapFn)(elementCount * sizeof(T));
        reset(reinterpret_cast<T*>(ptr), elementCount);
    }

    using UnmapResourceBufferFn =
        void (RenderContextImpl::*)(size_t mapSizeInBytes);
    void unmapElements(RenderContextImpl* impl,
                       UnmapResourceBufferFn unmapFn,
                       size_t elementCount)
    {
        assert(m_mappedMemory != nullptr);
        assert(m_mappingEnd - m_mappedMemory == elementCount);
        (impl->*unmapFn)(elementCount * sizeof(T));
        reset();
    }

    operator bool() const { return m_mappedMemory; }

    // How many bytes have been written to the buffer?
    size_t bytesWritten() const
    {
        return reinterpret_cast<uintptr_t>(m_nextMappedItem) -
               reinterpret_cast<uintptr_t>(m_mappedMemory);
    }

    size_t elementsWritten() const { return bytesWritten() / sizeof(T); }

    // Is there room to push() itemCount items to the buffer?
    bool hasRoomFor(size_t itemCount)
    {
        return m_nextMappedItem + itemCount <= m_mappingEnd;
    }

    // Append and write a new item to the buffer. In order to enforce the
    // write-only requirement of a mapped buffer, these methods do not return
    // any pointers to the client.
    template <typename... Args>
    RIVE_ALWAYS_INLINE void emplace_back(Args&&... args)
    {
        new (&push()) T(std::forward<Args>(args)...);
    }
    template <typename... Args> RIVE_ALWAYS_INLINE void set_back(Args&&... args)
    {
        push().set(std::forward<Args>(args)...);
    }
    void push_back_n(const T* values, size_t count)
    {
        T* dst = push(count);
        if (values != nullptr)
        {
            memcpy(static_cast<void*>(dst), values, count * sizeof(T));
        }
    }
    void skip_back() { push(); }

private:
    RIVE_ALWAYS_INLINE T& push()
    {
        assert(hasRoomFor(1));
        return *m_nextMappedItem++;
    }
    RIVE_ALWAYS_INLINE T* push(size_t count)
    {
        assert(hasRoomFor(count));
        T* ret = m_nextMappedItem;
        m_nextMappedItem += count;
        return ret;
    }

    T* m_mappedMemory;
    T* m_nextMappedItem;
    const T* m_mappingEnd;
};

// Utility for tracking booleans that may be unknown (e.g., lazily computed
// values, GL state, etc.)
enum class TriState
{
    no,
    yes,
    unknown
};

enum class StencilOp : uint8_t
{
    keep,
    replace,
    zero,
    decrClamp,
    incrWrap,
    decrWrap
};

enum class StencilCompareOp : uint8_t
{
    less,
    equal,
    lessOrEqual,
    notEqual,
    always,
};

struct StencilFaceOps
{
    StencilOp failOp = StencilOp::keep;
    StencilOp passOp = StencilOp::keep;
    StencilOp depthFailOp = StencilOp::keep;
    StencilCompareOp compareOp = StencilCompareOp::always;
};

enum class CullFace : uint8_t
{
    none,
    clockwise,
    counterclockwise,
};

// Blend equation to select for the fixed-function GPU pipeline (not our own
// in-shader blending). For now, the backend is free to decide whether it will
// use premultiplied alpha or not.
enum class BlendEquation : uint8_t
{
    // Hardware blend is disabled.
    none = 0,

    // Core hardware blend equations supported on all platforms.
    srcOver = static_cast<int>(rive::BlendMode::srcOver),
    plus = srcOver + 1,
    max = plus + 1,

    // "Advanced" hardware blend equations.
    // PlatformFeatures::supportsKHRBlendEquations is required.
    screen = static_cast<int>(rive::BlendMode::screen),
    overlay = static_cast<int>(rive::BlendMode::overlay),
    darken = static_cast<int>(rive::BlendMode::darken),
    lighten = static_cast<int>(rive::BlendMode::lighten),
    colorDodge = static_cast<int>(rive::BlendMode::colorDodge),
    colorBurn = static_cast<int>(rive::BlendMode::colorBurn),
    hardLight = static_cast<int>(rive::BlendMode::hardLight),
    softLight = static_cast<int>(rive::BlendMode::softLight),
    difference = static_cast<int>(rive::BlendMode::difference),
    exclusion = static_cast<int>(rive::BlendMode::exclusion),
    multiply = static_cast<int>(rive::BlendMode::multiply),
    hue = static_cast<int>(rive::BlendMode::hue),
    saturation = static_cast<int>(rive::BlendMode::saturation),
    color = static_cast<int>(rive::BlendMode::color),
    luminosity = static_cast<int>(rive::BlendMode::luminosity),
};

// Common pipeline state that applies to every Rive draw and every backend.
struct PipelineState
{
    // Depth.
    bool depthTestEnabled = false;
    bool depthWriteEnabled = true;

    // Stencil.
    bool stencilTestEnabled = false;
    uint8_t stencilCompareMask = 0xff;
    uint8_t stencilWriteMask = 0xff;
    uint8_t stencilReference = 0;
    StencilFaceOps stencilFrontOps;
    StencilFaceOps stencilBackOps;
    bool stencilDoubleSided = false; // Use stencilFrontOps for both faces?

    CullFace cullFace = CullFace::none;
    BlendEquation blendEquation = BlendEquation::none;
    bool colorWriteEnabled = true;

    // 18-bit key that uniquely identifies the pipeline state.
    constexpr static int UNIQUE_KEY_BIT_COUNT = 18;
    uint32_t uniqueKey;
};

void get_pipeline_state(const DrawBatch&,
                        const FlushDescriptor&,
                        const PlatformFeatures&,
                        PipelineState*);

// Default PipelineState values as specified in OpenGL.
constexpr static PipelineState GL_DEFAULT_PIPELINE_STATE = {};

constexpr static PipelineState COLOR_ONLY_PIPELINE_STATE = {
    .depthTestEnabled = false,
    .depthWriteEnabled = false,
    .stencilTestEnabled = false,
    .stencilWriteMask = 0,
    .cullFace = CullFace::none,
    .blendEquation = BlendEquation::none,
    .colorWriteEnabled = true,
};

constexpr static PipelineState ATLAS_FILL_PIPELINE_STATE = {
    .depthTestEnabled = false,
    .depthWriteEnabled = false,
    .stencilTestEnabled = false,
    .stencilWriteMask = 0,
    .cullFace = CullFace::counterclockwise,
    .blendEquation = BlendEquation::plus,
    .colorWriteEnabled = true,
};

constexpr static PipelineState ATLAS_STROKE_PIPELINE_STATE = {
    .depthTestEnabled = false,
    .depthWriteEnabled = false,
    .stencilTestEnabled = false,
    .stencilWriteMask = 0,
    .cullFace = CullFace::counterclockwise,
    .blendEquation = BlendEquation::max,
    .colorWriteEnabled = true,
};

float4 cast_f16_to_f32(uint16x4 x);
uint16x4 cast_f32_to_f16(float4);

// These tables integrate the gaussian function, and its inverse, covering a
// spread of -FEATHER_TEXTURE_STDDEVS to +FEATHER_TEXTURE_STDDEVS.
constexpr static uint32_t GAUSSIAN_TABLE_SIZE = 512;
extern const uint16_t g_gaussianIntegralTableF16[GAUSSIAN_TABLE_SIZE];
extern const uint16_t g_inverseGaussianIntegralTableF16[GAUSSIAN_TABLE_SIZE];

// Code to generate g_gaussianIntegralTableF16 and
// g_inverseGaussianIntegralTableF32. This is left in the codebase but #ifdef'd
// out in case we ever want to change any parameters of the built-in tables.
#if 0
void generate_gausian_integral_table(float (&)[GAUSSIAN_TABLE_SIZE]);
void generate_inverse_gausian_integral_table(float (&)[GAUSSIAN_TABLE_SIZE]);
#endif
} // namespace rive::gpu
