| /* |
| * 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 |