blob: 0288ce3f97b76f09d49c9eee3c060f3c7ad244c0 [file] [log] [blame] [edit]
/*
* Copyright 2023 Rive
*/
#pragma once
#include "rive/math/raw_path.hpp"
#include "rive/math/wangs_formula.hpp"
#include "rive/pls/pls.hpp"
#include "rive/pls/pls_render_context.hpp"
#include "rive/pls/fixed_queue.hpp"
#include "rive/shapes/paint/stroke_cap.hpp"
#include "rive/shapes/paint/stroke_join.hpp"
#include "rive/refcnt.hpp"
namespace rive::pls
{
class PLSDraw;
class PLSPath;
class PLSPaint;
class PLSRenderContext;
class PLSGradient;
// High level abstraction of a single object to be drawn (path, imageRect, or imageMesh). These get
// built up for an entire frame in order to count GPU resource allocation sizes, and then sorted,
// batched, and drawn.
class PLSDraw
{
public:
// Use a "fullscreen" bounding box that is reasonably larger than any screen, but not so big
// that it runs the risk of overflowing.
constexpr static IAABB kFullscreenPixelBounds = {0, 0, 1 << 24, 1 << 24};
enum class Type : uint8_t
{
midpointFanPath,
interiorTriangulationPath,
imageRect,
imageMesh,
stencilClipReset,
};
PLSDraw(IAABB pixelBounds, const Mat2D&, BlendMode, rcp<const PLSTexture> imageTexture, Type);
const PLSTexture* imageTexture() const { return m_imageTextureRef; }
const IAABB& pixelBounds() const { return m_pixelBounds; }
const Mat2D& matrix() const { return m_matrix; }
BlendMode blendMode() const { return m_blendMode; }
Type type() const { return m_type; }
pls::DrawContents drawContents() const { return m_drawContents; }
bool isStroked() const { return m_drawContents & pls::DrawContents::stroke; }
bool isEvenOddFill() const { return m_drawContents & pls::DrawContents::evenOddFill; }
bool isOpaque() const { return m_drawContents & pls::DrawContents::opaquePaint; }
uint32_t clipID() const { return m_clipID; }
bool hasClipRect() const { return m_clipRectInverseMatrix != nullptr; }
const pls::ClipRectInverseMatrix* clipRectInverseMatrix() const
{
return m_clipRectInverseMatrix;
}
pls::SimplePaintValue simplePaintValue() const { return m_simplePaintValue; }
const PLSGradient* gradient() const { return m_gradientRef; }
// Clipping setup.
void setClipID(uint32_t clipID);
void setClipRect(const pls::ClipRectInverseMatrix* m) { m_clipRectInverseMatrix = m; }
// Used to allocate GPU resources for a collection of draws.
using ResourceCounters = PLSRenderContext::LogicalFlush::ResourceCounters;
const ResourceCounters& resourceCounts() const { return m_resourceCounts; }
// Linked list of all PLSDraws within a pls::DrawBatch.
void setBatchInternalNeighbor(const PLSDraw* neighbor)
{
assert(m_batchInternalNeighbor == nullptr);
m_batchInternalNeighbor = neighbor;
};
const PLSDraw* batchInternalNeighbor() const { return m_batchInternalNeighbor; }
// Adds the gradient (if any) for this draw to the render context's gradient texture.
// Returns false if this draw needed a gradient but there wasn't room for it in the texture, at
// which point the gradient texture will need to be re-rendered mid flight.
bool allocateGradientIfNeeded(PLSRenderContext::LogicalFlush*, ResourceCounters*);
// Pushes the data for this draw to the render context. Called once the GPU buffers have been
// counted and allocated, and the draws have been sorted.
virtual void pushToRenderContext(PLSRenderContext::LogicalFlush*) = 0;
// We can't have a destructor because we're block-allocated. Instead, the client calls this
// method before clearing the drawList to release all our held references.
virtual void releaseRefs();
protected:
const PLSTexture* const m_imageTextureRef;
const IAABB m_pixelBounds;
const Mat2D m_matrix;
const BlendMode m_blendMode;
const Type m_type;
uint32_t m_clipID = 0;
const pls::ClipRectInverseMatrix* m_clipRectInverseMatrix = nullptr;
pls::DrawContents m_drawContents = pls::DrawContents::none;
// Filled in by the subclass constructor.
ResourceCounters m_resourceCounts;
// Gradient data used by some draws. Stored in the base class so allocateGradientIfNeeded()
// doesn't have to be virtual.
const PLSGradient* m_gradientRef = nullptr;
pls::SimplePaintValue m_simplePaintValue;
// Linked list of all PLSDraws within a pls::DrawBatch.
const PLSDraw* m_batchInternalNeighbor = nullptr;
};
// Implement PLSDrawReleaseRefs (defined in pls_render_context.hpp) now that PLSDraw is defined.
inline void PLSDrawReleaseRefs::operator()(PLSDraw* draw) { draw->releaseRefs(); }
// High level abstraction of a single path to be drawn (midpoint fan or interior triangulation).
class PLSPathDraw : public PLSDraw
{
public:
// Creates either a normal path draw or an interior triangulation if the path is large enough.
static PLSDrawUniquePtr Make(PLSRenderContext*,
const Mat2D&,
rcp<const PLSPath>,
FillRule,
const PLSPaint*,
RawPath* scratchPath);
FillRule fillRule() const { return m_fillRule; }
pls::PaintType paintType() const { return m_paintType; }
float strokeRadius() const { return m_strokeRadius; }
pls::ContourDirections contourDirections() const { return m_contourDirections; }
void pushToRenderContext(PLSRenderContext::LogicalFlush*) final;
void releaseRefs() override;
public:
PLSPathDraw(IAABB pathBounds,
const Mat2D&,
rcp<const PLSPath>,
FillRule,
const PLSPaint*,
Type,
pls::InterlockMode);
virtual void onPushToRenderContext(PLSRenderContext::LogicalFlush*) = 0;
const PLSPath* const m_pathRef;
const FillRule m_fillRule; // Bc PLSPath fillRule can mutate during the artboard draw process.
const pls::PaintType m_paintType;
float m_strokeRadius = 0;
pls::ContourDirections m_contourDirections;
// Used to guarantee m_pathRef doesn't change for the entire time we hold it.
RIVE_DEBUG_CODE(size_t m_rawPathMutationID;)
};
// Draws a path by fanning tessellation patches around the midpoint of each contour.
class MidpointFanPathDraw : public PLSPathDraw
{
public:
MidpointFanPathDraw(PLSRenderContext*,
IAABB pixelBounds,
const Mat2D&,
rcp<const PLSPath>,
FillRule,
const PLSPaint*);
protected:
void onPushToRenderContext(PLSRenderContext::LogicalFlush*) override;
// Emulates a stroke cap before the given cubic by pushing a copy of the cubic, reversed, with 0
// tessellation segments leading up to the join section, and a 180-degree join that looks like
// the desired stroke cap.
void pushEmulatedStrokeCapAsJoinBeforeCubic(PLSRenderContext::LogicalFlush*,
const Vec2D cubic[],
uint32_t emulatedCapAsJoinFlags,
uint32_t strokeCapSegmentCount);
float m_strokeMatrixMaxScale;
StrokeJoin m_strokeJoin;
StrokeCap m_strokeCap;
struct ContourInfo
{
RawPath::Iter endOfContour;
size_t endLineIdx;
size_t firstCurveIdx;
size_t endCurveIdx;
size_t firstRotationIdx; // We measure rotations on both curves and round joins.
size_t endRotationIdx;
Vec2D midpoint;
bool closed;
size_t strokeJoinCount;
uint32_t strokeCapSegmentCount;
uint32_t paddingVertexCount;
RIVE_DEBUG_CODE(uint32_t tessVertexCount;)
};
ContourInfo* m_contours;
FixedQueue<uint8_t> m_numChops;
FixedQueue<Vec2D> m_chopVertices;
std::array<Vec2D, 2>* m_tangentPairs = nullptr;
uint32_t* m_polarSegmentCounts = nullptr;
uint32_t* m_parametricSegmentCounts = nullptr;
// Consistency checks for onPushToRenderContext().
RIVE_DEBUG_CODE(size_t m_pendingLineCount;)
RIVE_DEBUG_CODE(size_t m_pendingCurveCount;)
RIVE_DEBUG_CODE(size_t m_pendingRotationCount;)
RIVE_DEBUG_CODE(size_t m_pendingStrokeJoinCount;)
RIVE_DEBUG_CODE(size_t m_pendingStrokeCapCount;)
// Counts how many additional curves were pushed by pushEmulatedStrokeCapAsJoinBeforeCubic().
RIVE_DEBUG_CODE(size_t m_pendingEmptyStrokeCountForCaps;)
};
// Draws a path by triangulating the interior into non-overlapping triangles and tessellating the
// outer curves.
class InteriorTriangulationDraw : public PLSPathDraw
{
public:
enum class TriangulatorAxis
{
horizontal,
vertical,
dontCare,
};
InteriorTriangulationDraw(PLSRenderContext*,
IAABB pixelBounds,
const Mat2D&,
rcp<const PLSPath>,
FillRule,
const PLSPaint*,
RawPath* scratchPath,
TriangulatorAxis);
GrInnerFanTriangulator* triangulator() const { return m_triangulator; }
protected:
void onPushToRenderContext(PLSRenderContext::LogicalFlush*) override;
// The final segment in an outerCurve patch is a bowtie join.
constexpr static size_t kJoinSegmentCount = 1;
constexpr static size_t kPatchSegmentCountExcludingJoin =
kOuterCurvePatchSegmentSpan - kJoinSegmentCount;
// Maximum # of outerCurve patches a curve on the path can be subdivided into.
constexpr static size_t kMaxCurveSubdivisions =
(kMaxParametricSegments + kPatchSegmentCountExcludingJoin - 1) /
kPatchSegmentCountExcludingJoin;
static size_t FindSubdivisionCount(const Vec2D pts[],
const wangs_formula::VectorXform& vectorXform)
{
size_t numSubdivisions =
ceilf(wangs_formula::cubic(pts, kParametricPrecision, vectorXform) *
(1.f / kPatchSegmentCountExcludingJoin));
return std::clamp<size_t>(numSubdivisions, 1, kMaxCurveSubdivisions);
}
enum class PathOp : bool
{
countDataAndTriangulate,
submitOuterCubics,
};
// For now, we just iterate and subdivide the path twice (once for each enum in PathOp).
// Since we only do this for large paths, and since we're triangulating the path interior
// anyway, adding complexity to only run Wang's formula and chop once would save about ~5%
// of the total CPU time. (And large paths are GPU-bound anyway.)
void processPath(PathOp op,
TrivialBlockAllocator*,
RawPath* scratchPath,
TriangulatorAxis,
PLSRenderContext::LogicalFlush*);
GrInnerFanTriangulator* m_triangulator = nullptr;
};
// Pushes an imageRect to the render context.
// This should only be used when we don't have bindless textures in atomic mode. Otherwise, images
// should be drawn as rectangular paths with an image paint.
class ImageRectDraw : public PLSDraw
{
public:
ImageRectDraw(PLSRenderContext*,
IAABB pixelBounds,
const Mat2D&,
BlendMode,
rcp<const PLSTexture>,
float opacity);
float opacity() const { return m_opacity; }
void pushToRenderContext(PLSRenderContext::LogicalFlush*) override;
protected:
const float m_opacity;
};
// Pushes an imageMesh to the render context.
class ImageMeshDraw : public PLSDraw
{
public:
ImageMeshDraw(IAABB pixelBounds,
const Mat2D&,
BlendMode,
rcp<const PLSTexture>,
rcp<const RenderBuffer> vertexBuffer,
rcp<const RenderBuffer> uvBuffer,
rcp<const RenderBuffer> indexBuffer,
uint32_t indexCount,
float opacity);
const RenderBuffer* vertexBuffer() const { return m_vertexBufferRef; }
const RenderBuffer* uvBuffer() const { return m_uvBufferRef; }
const RenderBuffer* indexBuffer() const { return m_indexBufferRef; }
uint32_t indexCount() const { return m_indexCount; }
float opacity() const { return m_opacity; }
void pushToRenderContext(PLSRenderContext::LogicalFlush*) override;
void releaseRefs() override;
protected:
const RenderBuffer* const m_vertexBufferRef;
const RenderBuffer* const m_uvBufferRef;
const RenderBuffer* const m_indexBufferRef;
const uint32_t m_indexCount;
const float m_opacity;
};
// Resets the stencil clip by either entirely erasing the existing clip, or intersecting it with a
// nested clip (i.e., erasing the region outside the nested clip).
class StencilClipReset : public PLSDraw
{
public:
enum class ResetAction
{
clearPreviousClip,
intersectPreviousClip,
};
StencilClipReset(PLSRenderContext*, uint32_t previousClipID, ResetAction);
uint32_t previousClipID() const { return m_previousClipID; }
void pushToRenderContext(PLSRenderContext::LogicalFlush*) override;
protected:
const uint32_t m_previousClipID;
};
} // namespace rive::pls