| /* |
| * Copyright 2022 Rive |
| */ |
| |
| #pragma once |
| |
| #include "rive/math/vec2d.hpp" |
| #include "rive/pls/pls.hpp" |
| #include "rive/pls/pls_factory.hpp" |
| #include "rive/pls/pls_render_target.hpp" |
| #include "rive/pls/trivial_block_allocator.hpp" |
| #include "rive/shapes/paint/color.hpp" |
| #include <array> |
| #include <unordered_map> |
| |
| class PushRetrofittedTrianglesGMDraw; |
| class PLSRenderContextTest; |
| |
| namespace rive |
| { |
| class RawPath; |
| } // namespace rive |
| |
| namespace rive::pls |
| { |
| class GradientLibrary; |
| class IntersectionBoard; |
| class ImageMeshDraw; |
| class ImageRectDraw; |
| class InteriorTriangulationDraw; |
| class MidpointFanPathDraw; |
| class StencilClipReset; |
| class PLSDraw; |
| class PLSGradient; |
| class PLSPaint; |
| class PLSPath; |
| class PLSPathDraw; |
| class PLSRenderContextImpl; |
| |
| // Used as a key for complex gradients. |
| class GradientContentKey |
| { |
| public: |
| inline GradientContentKey(rcp<const PLSGradient> gradient); |
| inline GradientContentKey(GradientContentKey&& other); |
| bool operator==(const GradientContentKey&) const; |
| const PLSGradient* gradient() const { return m_gradient.get(); } |
| |
| private: |
| rcp<const PLSGradient> m_gradient; |
| }; |
| |
| // Hashes all stops and all colors in a complex gradient. |
| class DeepHashGradient |
| { |
| public: |
| size_t operator()(const GradientContentKey&) const; |
| }; |
| |
| // Even though PLSDraw is block-allocated, we still need to call releaseRefs() on each individual |
| // instance before releasing the block. This smart pointer guarantees we always call releaseRefs() |
| // (implementation in pls_draw.hpp). |
| struct PLSDrawReleaseRefs |
| { |
| void operator()(PLSDraw* draw); |
| }; |
| using PLSDrawUniquePtr = std::unique_ptr<PLSDraw, PLSDrawReleaseRefs>; |
| |
| // Top-level, API agnostic rendering context for PLSRenderer. This class manages all the GPU |
| // buffers, context state, and other resources required for Rive's pixel local storage path |
| // rendering algorithm. |
| // |
| // Intended usage pattern of this class: |
| // |
| // context->beginFrame(...); |
| // for (path : paths) |
| // { |
| // context->pushPath(...); |
| // for (contour : path.contours) |
| // { |
| // context->pushContour(...); |
| // for (cubic : contour.cubics) |
| // { |
| // context->pushCubic(...); |
| // } |
| // } |
| // } |
| // context->flush(); |
| class PLSRenderContext : public PLSFactory |
| { |
| public: |
| PLSRenderContext(std::unique_ptr<PLSRenderContextImpl>); |
| ~PLSRenderContext(); |
| |
| PLSRenderContextImpl* impl() { return m_impl.get(); } |
| template <typename T> T* static_impl_cast() { return static_cast<T*>(m_impl.get()); } |
| |
| const pls::PlatformFeatures& platformFeatures() const; |
| |
| // Options for controlling how and where a frame is rendered. |
| struct FrameDescriptor |
| { |
| uint32_t renderTargetWidth = 0; |
| uint32_t renderTargetHeight = 0; |
| LoadAction loadAction = LoadAction::clear; |
| ColorInt clearColor = 0; |
| int msaaSampleCount = 0; // If nonzero, the number of MSAA samples to use. |
| // Setting this to a nonzero value forces depthStencil mode. |
| bool disableRasterOrdering = false; // Use atomic mode in place of rasterOrdering, even if |
| // rasterOrdering is supported. |
| |
| // Testing flags. |
| bool wireframe = false; |
| bool fillsDisabled = false; |
| bool strokesDisabled = false; |
| }; |
| |
| // Called at the beginning of a frame and establishes where and how it will be rendered. |
| // |
| // All rendering related calls must be made between beginFrame() and flush(). |
| void beginFrame(const FrameDescriptor&); |
| |
| const FrameDescriptor& frameDescriptor() const |
| { |
| assert(m_didBeginFrame); |
| return m_frameDescriptor; |
| } |
| |
| // True if bounds is empty or outside [0, 0, renderTargetWidth, renderTargetHeight]. |
| bool isOutsideCurrentFrame(const IAABB& pixelBounds); |
| |
| // True if the current frame supports draws with clipRects (clipRectInverseMatrix != null). |
| // If false, all clipping must be done with clipPaths. |
| bool frameSupportsClipRects() const; |
| |
| // If the frame doesn't support image paints, the client must draw images with pushImageRect(). |
| // If it DOES support image paints, the client CANNOT use pushImageRect(); it should draw images |
| // as rectangular paths with an image paint. |
| bool frameSupportsImagePaintForPaths() const; |
| |
| const pls::InterlockMode frameInterlockMode() const { return m_frameInterlockMode; } |
| |
| // Generates a unique clip ID that is guaranteed to not exist in the current clip buffer, and |
| // assigns a contentBounds to it. |
| // |
| // Returns 0 if a unique ID could not be generated, at which point the caller must issue a |
| // logical flush and try again. |
| uint32_t generateClipID(const IAABB& contentBounds); |
| |
| // Screen-space bounding box of the region inside the given clip. |
| const IAABB& getClipContentBounds(uint32_t clipID) |
| { |
| assert(m_didBeginFrame); |
| assert(!m_logicalFlushes.empty()); |
| return m_logicalFlushes.back()->getClipInfo(clipID).contentBounds; |
| } |
| |
| // Mark the given clip as being read from within a screen-space bounding box. |
| void addClipReadBounds(uint32_t clipID, const IAABB& bounds) |
| { |
| assert(m_didBeginFrame); |
| assert(!m_logicalFlushes.empty()); |
| return m_logicalFlushes.back()->addClipReadBounds(clipID, bounds); |
| } |
| |
| // Union of screen-space bounding boxes from all draws that read the given clip element. |
| const IAABB& getClipReadBounds(uint32_t clipID) |
| { |
| assert(m_didBeginFrame); |
| assert(!m_logicalFlushes.empty()); |
| return m_logicalFlushes.back()->getClipInfo(clipID).readBounds; |
| } |
| |
| // Get/set a "clip content ID" that uniquely identifies the current contents of the clip buffer. |
| // This ID is reset to 0 on every logical flush. |
| void setClipContentID(uint32_t clipID) |
| { |
| assert(m_didBeginFrame); |
| m_clipContentID = clipID; |
| } |
| |
| uint32_t getClipContentID() |
| { |
| assert(m_didBeginFrame); |
| return m_clipContentID; |
| } |
| |
| // Appends a list of high-level PLSDraws to the current frame. |
| // Returns false if the draws don't fit within the current resource constraints, at which point |
| // the caller must issue a logical flush and try again. |
| [[nodiscard]] bool pushDrawBatch(PLSDrawUniquePtr draws[], size_t drawCount); |
| |
| // Records a "logical" flush, in that it builds up commands to break up the render pass and |
| // re-render the resource textures, but it won't submit any command buffers or |
| // rotate/synchronize the buffer rings. |
| void logicalFlush(); |
| |
| // GPU resources required to execute the GPU commands for a frame. |
| struct FlushResources |
| { |
| PLSRenderTarget* renderTarget = nullptr; |
| void* externalCommandBuffer = nullptr; // Required on Metal. |
| }; |
| |
| // Submits all GPU commands that have been built up since beginFrame(). |
| void flush(const FlushResources&); |
| |
| // Called when the client will stop rendering. Releases all CPU and GPU resources associated |
| // with this render context. |
| void releaseResources(); |
| |
| // Returns the context's TrivialBlockAllocator, which is automatically reset at the end of every |
| // frame. (Memory in this allocator is preserved between logical flushes.) |
| TrivialBlockAllocator& perFrameAllocator() |
| { |
| assert(m_didBeginFrame); |
| return m_perFrameAllocator; |
| } |
| |
| // Allocators for intermediate path processing buffers. |
| TrivialArrayAllocator<uint8_t>& numChopsAllocator() { return m_numChopsAllocator; } |
| TrivialArrayAllocator<Vec2D>& chopVerticesAllocator() { return m_chopVerticesAllocator; } |
| TrivialArrayAllocator<std::array<Vec2D, 2>>& tangentPairsAllocator() |
| { |
| return m_tangentPairsAllocator; |
| } |
| TrivialArrayAllocator<uint32_t, alignof(float4)>& polarSegmentCountsAllocator() |
| { |
| return m_polarSegmentCountsAllocator; |
| } |
| TrivialArrayAllocator<uint32_t, alignof(float4)>& parametricSegmentCountsAllocator() |
| { |
| return m_parametricSegmentCountsAllocator; |
| } |
| |
| // Allocates a trivially destructible object that will be automatically dropped at the end of |
| // the current frame. |
| template <typename T, typename... Args> T* make(Args&&... args) |
| { |
| assert(m_didBeginFrame); |
| return m_perFrameAllocator.make<T>(std::forward<Args>(args)...); |
| } |
| |
| // Backend-specific PLSFactory implementation. |
| rcp<RenderBuffer> makeRenderBuffer(RenderBufferType, RenderBufferFlags, size_t) override; |
| rcp<RenderImage> decodeImage(Span<const uint8_t>) override; |
| |
| private: |
| friend class PLSDraw; |
| friend class PLSPathDraw; |
| friend class MidpointFanPathDraw; |
| friend class InteriorTriangulationDraw; |
| friend class ImageRectDraw; |
| friend class ImageMeshDraw; |
| friend class StencilClipReset; |
| friend class ::PushRetrofittedTrianglesGMDraw; // For testing. |
| friend class ::PLSRenderContextTest; // For testing. |
| |
| // Resets the CPU-side STL containers so they don't have unbounded growth. |
| void resetContainers(); |
| |
| // Defines the exact size of each of our GPU resources. Computed during flush(), based on |
| // LogicalFlush::ResourceCounters and LogicalFlush::LayoutCounters. |
| struct ResourceAllocationCounts |
| { |
| using VecType = simd::gvec<size_t, 12>; |
| |
| RIVE_ALWAYS_INLINE VecType toVec() const |
| { |
| static_assert(sizeof(VecType) >= sizeof(*this)); |
| VecType vec; |
| RIVE_INLINE_MEMCPY(&vec, this, sizeof(*this)); |
| return vec; |
| } |
| |
| RIVE_ALWAYS_INLINE ResourceAllocationCounts(const VecType& vec) |
| { |
| static_assert(sizeof(VecType) >= sizeof(*this)); |
| RIVE_INLINE_MEMCPY(this, &vec, sizeof(*this)); |
| } |
| |
| ResourceAllocationCounts() = default; |
| |
| size_t flushUniformBufferCount = 0; |
| size_t imageDrawUniformBufferCount = 0; |
| size_t pathBufferCount = 0; |
| size_t paintBufferCount = 0; |
| size_t paintAuxBufferCount = 0; |
| size_t contourBufferCount = 0; |
| size_t simpleGradientBufferCount = 0; |
| size_t complexGradSpanBufferCount = 0; |
| size_t tessSpanBufferCount = 0; |
| size_t triangleVertexBufferCount = 0; |
| size_t gradTextureHeight = 0; |
| size_t tessTextureHeight = 0; |
| }; |
| |
| // Reallocates GPU resources and updates m_currentResourceAllocations. |
| // If forceRealloc is true, every GPU resource is allocated, even if the size would not change. |
| void setResourceSizes(ResourceAllocationCounts, bool forceRealloc = false); |
| |
| void mapResourceBuffers(const ResourceAllocationCounts&); |
| void unmapResourceBuffers(); |
| |
| const std::unique_ptr<PLSRenderContextImpl> m_impl; |
| const size_t m_maxPathID; |
| |
| ResourceAllocationCounts m_currentResourceAllocations; |
| ResourceAllocationCounts m_maxRecentResourceRequirements; |
| double m_lastResourceTrimTimeInSeconds; |
| |
| // Per-frame state. |
| FrameDescriptor m_frameDescriptor; |
| pls::InterlockMode m_frameInterlockMode; |
| pls::ShaderFeatures m_frameShaderFeaturesMask; |
| RIVE_DEBUG_CODE(bool m_didBeginFrame = false;) |
| |
| // Clipping state. |
| uint32_t m_clipContentID = 0; |
| |
| // Used by LogicalFlushes for re-ordering high level draws. |
| std::vector<int64_t> m_indirectDrawList; |
| std::unique_ptr<IntersectionBoard> m_intersectionBoard; |
| |
| WriteOnlyMappedMemory<pls::FlushUniforms> m_flushUniformData; |
| WriteOnlyMappedMemory<pls::PathData> m_pathData; |
| WriteOnlyMappedMemory<pls::PaintData> m_paintData; |
| WriteOnlyMappedMemory<pls::PaintAuxData> m_paintAuxData; |
| WriteOnlyMappedMemory<pls::ContourData> m_contourData; |
| // Simple gradients get written by the CPU. |
| WriteOnlyMappedMemory<pls::TwoTexelRamp> m_simpleColorRampsData; |
| // Complex gradients get rendered by the GPU. |
| WriteOnlyMappedMemory<pls::GradientSpan> m_gradSpanData; |
| WriteOnlyMappedMemory<pls::TessVertexSpan> m_tessSpanData; |
| WriteOnlyMappedMemory<pls::TriangleVertex> m_triangleVertexData; |
| WriteOnlyMappedMemory<pls::ImageDrawUniforms> m_imageDrawUniformData; |
| |
| // Simple allocator for trivially-destructible data that needs to persist until the current |
| // frame has completed. All memory in this allocator is dropped at the end of the every frame. |
| constexpr static size_t kPerFlushAllocatorInitialBlockSize = 1024 * 1024; // 1 MiB. |
| TrivialBlockAllocator m_perFrameAllocator{kPerFlushAllocatorInitialBlockSize}; |
| |
| // Allocators for intermediate path processing buffers. |
| constexpr static size_t kIntermediateDataInitialStrokes = 8192; // * 84 == 688 KiB. |
| constexpr static size_t kIntermediateDataInitialFillCurves = 32768; // * 4 == 128 KiB. |
| TrivialArrayAllocator<uint8_t> m_numChopsAllocator{kIntermediateDataInitialStrokes * |
| 4}; // 4 byte per stroke curve. |
| TrivialArrayAllocator<Vec2D> m_chopVerticesAllocator{kIntermediateDataInitialStrokes * |
| 4}; // 32 bytes per stroke curve. |
| TrivialArrayAllocator<std::array<Vec2D, 2>> m_tangentPairsAllocator{ |
| kIntermediateDataInitialStrokes * 2}; // 32 bytes per stroke curve. |
| TrivialArrayAllocator<uint32_t, alignof(float4)> m_polarSegmentCountsAllocator{ |
| kIntermediateDataInitialStrokes * 4}; // 16 bytes per stroke curve. |
| TrivialArrayAllocator<uint32_t, alignof(float4)> m_parametricSegmentCountsAllocator{ |
| kIntermediateDataInitialFillCurves}; // 4 bytes per fill curve. |
| |
| // Manages a list of high-level PLSDraws and their required resources. |
| // |
| // Since textures have hard size limits, we can't always fit an entire frame into one flush. |
| // It's rare for us to require more than one flush in a single frame, but for the times that we |
| // do, this flush logic is encapsulated in a nested class that can be built up into a list and |
| // executed the end of a frame. |
| class LogicalFlush |
| { |
| public: |
| LogicalFlush(PLSRenderContext* parent); |
| |
| // Rewinds this flush object back to an empty state without shrinking any internal |
| // allocations held by CPU-side STL containers. |
| void rewind(); |
| |
| // Resets the CPU-side STL containers so they don't have unbounded growth. |
| void resetContainers(); |
| |
| // Access this flush's pls::FlushDescriptor (which is not valid until layoutResources()). |
| // NOTE: Some fields in the FlushDescriptor (tessVertexSpanCount, hasTriangleVertices, |
| // drawList, and combinedShaderFeatures) do not become valid until after writeResources(). |
| const pls::FlushDescriptor& desc() |
| { |
| assert(m_hasDoneLayout); |
| return m_flushDesc; |
| } |
| |
| // Generates a unique clip ID that is guaranteed to not exist in the current clip buffer. |
| // |
| // Returns 0 if a unique ID could not be generated, at which point the caller must issue a |
| // logical flush and try again. |
| uint32_t generateClipID(const IAABB& contentBounds); |
| |
| struct ClipInfo |
| { |
| ClipInfo(const IAABB& contentBounds_) : contentBounds(contentBounds_) {} |
| |
| // Screen-space bounding box of the region inside the clip. |
| const IAABB contentBounds; |
| |
| // Union of screen-space bounding boxes from all draws that read the clip. |
| // |
| // (Initialized with a maximally negative rectangle whose union with any other rectangle |
| // will be equal to that same rectangle.) |
| IAABB readBounds = {std::numeric_limits<int32_t>::max(), |
| std::numeric_limits<int32_t>::max(), |
| std::numeric_limits<int32_t>::min(), |
| std::numeric_limits<int32_t>::min()}; |
| }; |
| |
| const ClipInfo& getClipInfo(uint32_t clipID) { return getWritableClipInfo(clipID); } |
| |
| // Mark the given clip as being read from within a screen-space bounding box. |
| void addClipReadBounds(uint32_t clipID, const IAABB& bounds); |
| |
| // Appends a list of high-level PLSDraws to the flush. |
| // Returns false if the draws don't fit within the current resource constraints, at which |
| // point the context must append a new logical flush and try again. |
| [[nodiscard]] bool pushDrawBatch(PLSDrawUniquePtr draws[], size_t drawCount); |
| |
| // Running counts of data records required by PLSDraws that need to be allocated in the |
| // render context's various GPU buffers. |
| struct ResourceCounters |
| { |
| using VecType = simd::gvec<size_t, 8>; |
| |
| VecType toVec() const |
| { |
| static_assert(sizeof(VecType) == sizeof(*this)); |
| VecType vec; |
| RIVE_INLINE_MEMCPY(&vec, this, sizeof(VecType)); |
| return vec; |
| } |
| |
| ResourceCounters(const VecType& vec) |
| { |
| static_assert(sizeof(*this) == sizeof(VecType)); |
| RIVE_INLINE_MEMCPY(this, &vec, sizeof(*this)); |
| } |
| |
| ResourceCounters() = default; |
| |
| size_t midpointFanTessVertexCount = 0; |
| size_t outerCubicTessVertexCount = 0; |
| size_t pathCount = 0; |
| size_t contourCount = 0; |
| size_t maxTessellatedSegmentCount = 0; // lines, curves, lone joins, emulated caps, etc. |
| size_t maxTriangleVertexCount = 0; |
| size_t imageDrawCount = 0; // imageRect or imageMesh. |
| size_t complexGradientSpanCount = 0; |
| }; |
| |
| // Additional counters for layout state that don't need to be tracked by individual draws. |
| struct LayoutCounters |
| { |
| uint32_t pathPaddingCount = 0; |
| uint32_t paintPaddingCount = 0; |
| uint32_t paintAuxPaddingCount = 0; |
| uint32_t contourPaddingCount = 0; |
| uint32_t simpleGradCount = 0; |
| uint32_t maxGradTextureHeight = 0; |
| uint32_t maxTessTextureHeight = 0; |
| }; |
| |
| // Allocates a horizontal span of texels in the gradient texture and schedules either a |
| // texture upload or a draw that fills it with the given gradient's color ramp. |
| // |
| // Fills out a ColorRampLocation record that tells the shader how to access the gradient. |
| // |
| // Returns false if the gradient texture is out of space, at which point the caller must |
| // issue a logical flush and try again. |
| [[nodiscard]] bool allocateGradient(const PLSGradient*, |
| ResourceCounters*, |
| pls::ColorRampLocation*); |
| |
| // Carves out space for this specific flush within the total frame's resource buffers and |
| // lays out the flush-specific resource textures. Updates the total frame running conters |
| // based on layout. |
| void layoutResources(const FlushResources&, |
| size_t logicalFlushIdx, |
| bool isFinalFlushOfFrame, |
| ResourceCounters* runningFrameResourceCounts, |
| LayoutCounters* runningFrameLayoutCounts); |
| |
| // Called after all flushes in a frame have done their layout and the render context has |
| // allocated and mapped its resource buffers. Writes the GPU data for this flush to the |
| // context's actively mapped resource buffers. |
| void writeResources(); |
| |
| // Pushes a record to the GPU for the given path, which will be referenced by future calls |
| // to pushContour() and pushCubic(). |
| void pushPath(PLSPathDraw*, pls::PatchType, uint32_t tessVertexCount); |
| |
| // Pushes a contour record to the GPU for the given contour, which references the |
| // most-recently pushed path and will be referenced by future calls to pushCubic(). |
| // |
| // The first curve of the contour will be pre-padded with 'paddingVertexCount' tessellation |
| // vertices, colocated at T=0. The caller must use this argument to align the end of the |
| // contour on a boundary of the patch size. (See pls::PaddingToAlignUp().) |
| void pushContour(Vec2D midpoint, bool closed, uint32_t paddingVertexCount); |
| |
| // Appends a cubic curve and join to the most-recently pushed contour, and reserves the |
| // appropriate number of tessellated vertices in the tessellation texture. |
| // |
| // An instance consists of a cubic curve with "parametricSegmentCount + polarSegmentCount" |
| // segments, followed by a join with "joinSegmentCount" segments, for a grand total of |
| // "parametricSegmentCount + polarSegmentCount + joinSegmentCount - 1" vertices. |
| // |
| // If a cubic has already been pushed to the current contour, pts[0] must be equal to the |
| // former cubic's pts[3]. |
| // |
| // "joinTangent" is the ending tangent of the join that follows the cubic. |
| void pushCubic(const Vec2D pts[4], |
| Vec2D joinTangent, |
| uint32_t additionalContourFlags, |
| uint32_t parametricSegmentCount, |
| uint32_t polarSegmentCount, |
| uint32_t joinSegmentCount); |
| |
| // Pushes triangles to be drawn using the data records from the most recent calls to |
| // pushPath() and pushPaint(). |
| void pushInteriorTriangulation(InteriorTriangulationDraw*); |
| |
| // Pushes an imageRect to the draw list. |
| // 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. |
| void pushImageRect(ImageRectDraw*); |
| |
| void pushImageMesh(ImageMeshDraw*); |
| |
| void pushStencilClipReset(StencilClipReset*); |
| |
| // Adds a barrier to the end of the draw list that prevents further combining/batching and |
| // instructs the backend to issue a graphics barrier, if necessary. |
| void pushBarrier(); |
| |
| private: |
| ClipInfo& getWritableClipInfo(uint32_t clipID); |
| |
| // Writes padding vertices to the tessellation texture, with an invalid contour ID that is |
| // guaranteed to not be the same ID as any neighbors. |
| void pushPaddingVertices(uint32_t tessLocation, uint32_t count); |
| |
| // Allocates a (potentially wrapped) span in the tessellation texture and pushes an instance |
| // to render it. If the span does wraps, pushes multiple instances to render each horizontal |
| // segment. |
| RIVE_ALWAYS_INLINE void pushTessellationSpans(const Vec2D pts[4], |
| Vec2D joinTangent, |
| uint32_t totalVertexCount, |
| uint32_t parametricSegmentCount, |
| uint32_t polarSegmentCount, |
| uint32_t joinSegmentCount, |
| uint32_t contourIDWithFlags); |
| |
| // Same as pushTessellationSpans(), but pushes a reflection of the span, rendered right to |
| // left, whose triangles have reverse winding directions and negated coverage. |
| RIVE_ALWAYS_INLINE void pushMirroredTessellationSpans(const Vec2D pts[4], |
| Vec2D joinTangent, |
| uint32_t totalVertexCount, |
| uint32_t parametricSegmentCount, |
| uint32_t polarSegmentCount, |
| uint32_t joinSegmentCount, |
| uint32_t contourIDWithFlags); |
| |
| // Functionally equivalent to "pushMirroredTessellationSpans(); pushTessellationSpans();", |
| // but packs each forward and mirrored pair into a single pls::TessVertexSpan. |
| RIVE_ALWAYS_INLINE void pushMirroredAndForwardTessellationSpans( |
| const Vec2D pts[4], |
| Vec2D joinTangent, |
| uint32_t totalVertexCount, |
| uint32_t parametricSegmentCount, |
| uint32_t polarSegmentCount, |
| uint32_t joinSegmentCount, |
| uint32_t contourIDWithFlags); |
| |
| // Either appends a new drawBatch to m_drawList or merges into m_drawList.tail(). |
| // Updates the batch's ShaderFeatures according to the passed parameters. |
| DrawBatch& pushPathDraw(PLSPathDraw*, DrawType, uint32_t vertexCount, uint32_t baseVertex); |
| DrawBatch& pushDraw(PLSDraw*, |
| DrawType, |
| pls::PaintType, |
| uint32_t elementCount, |
| uint32_t baseElement); |
| |
| // Instance pointer to the outer parent class. |
| PLSRenderContext* const m_ctx; |
| |
| // Running counts of GPU data records that need to be allocated for draws. |
| ResourceCounters m_resourceCounts; |
| |
| // Simple gradients have one stop at t=0 and one stop at t=1. They're implemented with 2 |
| // texels. |
| std::unordered_map<uint64_t, uint32_t> m_simpleGradients; // [color0, color1] -> texelsIdx. |
| std::vector<pls::TwoTexelRamp> m_pendingSimpleGradientWrites; |
| |
| // Complex gradients have stop(s) between t=0 and t=1. In theory they should be scaled to a |
| // ramp where every stop lands exactly on a pixel center, but for now we just always scale |
| // them to the entire gradient texture width. |
| std::unordered_map<GradientContentKey, uint16_t, DeepHashGradient> |
| m_complexGradients; // [colors[0..n], stops[0..n]] -> rowIdx |
| std::vector<const PLSGradient*> m_pendingComplexColorRampDraws; |
| |
| std::vector<ClipInfo> m_clips; |
| |
| // High-level draw list. These get built into a low-level list of pls::DrawBatch objects |
| // during writeResources(). |
| std::vector<PLSDrawUniquePtr> m_plsDraws; |
| IAABB m_combinedDrawBounds; |
| |
| // Layout state. |
| uint32_t m_pathPaddingCount; |
| uint32_t m_paintPaddingCount; |
| uint32_t m_paintAuxPaddingCount; |
| uint32_t m_contourPaddingCount; |
| uint32_t m_midpointFanTessEndLocation; |
| uint32_t m_outerCubicTessEndLocation; |
| uint32_t m_outerCubicTessVertexIdx; |
| uint32_t m_midpointFanTessVertexIdx; |
| |
| pls::FlushDescriptor m_flushDesc; |
| pls::GradTextureLayout m_gradTextureLayout; // Not determined until writeResources(). |
| |
| BlockAllocatedLinkedList<DrawBatch> m_drawList; |
| pls::ShaderFeatures m_combinedShaderFeatures; |
| |
| // Most recent path and contour state. |
| bool m_currentPathIsStroked; |
| pls::ContourDirections m_currentPathContourDirections; |
| uint32_t m_currentPathID; |
| uint32_t m_currentContourID; |
| uint32_t m_currentContourPaddingVertexCount; // Padding to add to the first curve. |
| uint32_t m_pathTessLocation; |
| uint32_t m_pathMirroredTessLocation; // Used for back-face culling and mirrored patches. |
| RIVE_DEBUG_CODE(uint32_t m_expectedPathTessLocationAtEndOfPath;) |
| RIVE_DEBUG_CODE(uint32_t m_expectedPathMirroredTessLocationAtEndOfPath;) |
| RIVE_DEBUG_CODE(uint32_t m_pathCurveCount;) |
| |
| // Stateful Z index of the current draw being pushed. Used by depthStencil mode to avoid |
| // double hits and to reverse-sort opaque paths front to back. |
| uint32_t m_currentZIndex; |
| |
| RIVE_DEBUG_CODE(bool m_hasDoneLayout = false;) |
| }; |
| |
| std::vector<std::unique_ptr<LogicalFlush>> m_logicalFlushes; |
| }; |
| } // namespace rive::pls |