| /* |
| * Copyright 2022 Rive |
| */ |
| |
| #pragma once |
| |
| #include "rive/math/vec2d.hpp" |
| #include "rive/renderer/gpu.hpp" |
| #include "rive/renderer/rive_render_factory.hpp" |
| #include "rive/renderer/render_target.hpp" |
| #include "rive/renderer/sk_rectanizer_skyline.hpp" |
| #include "rive/renderer/trivial_block_allocator.hpp" |
| #include "rive/shapes/paint/color.hpp" |
| #include <array> |
| #include <unordered_map> |
| |
| class PushRetrofittedTrianglesGMDraw; |
| class RenderContextTest; |
| |
| namespace rive |
| { |
| class RawPath; |
| class RiveRenderPaint; |
| class RiveRenderPath; |
| } // namespace rive |
| |
| namespace rive::gpu |
| { |
| class GradientLibrary; |
| class IntersectionBoard; |
| class ImageMeshDraw; |
| class ImageRectDraw; |
| class StencilClipReset; |
| class Draw; |
| class Gradient; |
| class RenderContextImpl; |
| class PathDraw; |
| |
| enum class ShaderCompilationMode |
| { |
| allowAsynchronous, |
| alwaysSynchronous, |
| onlyUbershaders, |
| |
| // The default mode is to allow asynchronous compilation where available. |
| standard = allowAsynchronous, |
| }; |
| |
| // Used as a key for complex gradients. |
| class GradientContentKey |
| { |
| public: |
| inline GradientContentKey(rcp<const Gradient> gradient); |
| inline GradientContentKey(GradientContentKey&& other); |
| bool operator==(const GradientContentKey&) const; |
| const Gradient* gradient() const { return m_gradient.get(); } |
| |
| private: |
| rcp<const Gradient> m_gradient; |
| }; |
| |
| // Hashes all stops and all colors in a complex gradient. |
| class DeepHashGradient |
| { |
| public: |
| size_t operator()(const GradientContentKey&) const; |
| }; |
| |
| // Even though Draw 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 DrawReleaseRefs |
| { |
| void operator()(Draw* draw); |
| }; |
| using DrawUniquePtr = std::unique_ptr<Draw, DrawReleaseRefs>; |
| |
| // Top-level, API agnostic rendering context for RiveRenderer. This class |
| // manages all the GPU buffers, context state, and other resources required for |
| // Rive's pixel local storage path rendering algorithm. |
| class RenderContext : public RiveRenderFactory |
| { |
| public: |
| RenderContext(std::unique_ptr<RenderContextImpl>); |
| ~RenderContext(); |
| |
| RenderContextImpl* impl() { return m_impl.get(); } |
| template <typename T> T* static_impl_cast() |
| { |
| return static_cast<T*>(m_impl.get()); |
| } |
| |
| const gpu::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; |
| // If nonzero, the number of MSAA samples to use. |
| // Setting this to a nonzero value forces msaa mode. |
| uint32_t msaaSampleCount = 0; |
| // Use atomic mode (preferred) or msaa instead of rasterOrdering. |
| bool disableRasterOrdering = false; |
| |
| // Testing flags. |
| bool wireframe = false; |
| bool fillsDisabled = false; |
| bool strokesDisabled = false; |
| // Override all paths' fill rules (winding or even/odd) to emulate |
| // clockwiseAtomic mode. |
| bool clockwiseFillOverride = 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. |
| gpu::SynthesizedFailureType synthesizedFailureType = |
| gpu::SynthesizedFailureType::none; |
| #endif |
| }; |
| |
| // 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 gpu::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 Draws 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 pushDraws(DrawUniquePtr 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 |
| { |
| RenderTarget* renderTarget = nullptr; |
| |
| // Command buffer that rendering commands will be added to. |
| // - VkCommandBuffer on Vulkan. |
| // - id<MTLCommandBuffer> on Metal. |
| // - WGPUCommandEncoder on WebGPU. |
| // - Unused otherwise. |
| void* externalCommandBuffer = nullptr; |
| |
| // Resource lifetime counters. Resources used during the upcoming flush |
| // will belong to 'currentFrameNumber'. Resources last used on or before |
| // 'safeFrameNumber' are safe to be released or recycled. |
| uint64_t currentFrameNumber = 0; |
| uint64_t safeFrameNumber = 0; |
| }; |
| |
| // 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 RiveRenderFactory implementation. |
| rcp<RenderBuffer> makeRenderBuffer(RenderBufferType, |
| RenderBufferFlags, |
| size_t) override; |
| rcp<RenderImage> decodeImage(Span<const uint8_t>) override; |
| |
| private: |
| friend class Draw; |
| friend class PathDraw; |
| friend class ImageRectDraw; |
| friend class ImageMeshDraw; |
| friend class StencilClipReset; |
| friend class ::PushRetrofittedTrianglesGMDraw; // For testing. |
| friend class ::RenderContextTest; // For testing. |
| |
| // Resets the CPU-side STL containers so they don't have unbounded growth. |
| void resetContainers(); |
| |
| // Throttled width/height of the atlas texture. If drawing to a render |
| // target larger than this, we may create a larger atlas anyway. |
| uint32_t atlasMaxSize() const |
| { |
| constexpr static uint32_t MAX_ATLAS_MAX_SIZE = 4096; |
| return std::min(platformFeatures().maxTextureSize, MAX_ATLAS_MAX_SIZE); |
| } |
| |
| // Defines the exact size of each of our GPU resources. Computed during |
| // flush(), based on LogicalFlush::ResourceCounters and |
| // LogicalFlush::LayoutCounters. |
| struct ResourceAllocationCounts |
| { |
| constexpr static int NUM_ELEMENTS = 19; |
| using VecType = simd::gvec<size_t, NUM_ELEMENTS>; |
| |
| RIVE_ALWAYS_INLINE VecType toVec() const |
| { |
| static_assert(sizeof(*this) == sizeof(size_t) * NUM_ELEMENTS); |
| static_assert(sizeof(VecType) >= sizeof(*this)); |
| VecType vec; |
| RIVE_INLINE_MEMCPY(&vec, this, sizeof(*this)); |
| return vec; |
| } |
| |
| static RIVE_ALWAYS_INLINE ResourceAllocationCounts |
| FromVec(const VecType& vec) |
| { |
| ResourceAllocationCounts allocs; |
| static_assert(sizeof(allocs) == sizeof(size_t) * NUM_ELEMENTS); |
| static_assert(sizeof(VecType) >= sizeof(allocs)); |
| RIVE_INLINE_MEMCPY(&allocs, &vec, sizeof(allocs)); |
| return allocs; |
| } |
| |
| 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 gradSpanBufferCount = 0; |
| size_t tessSpanBufferCount = 0; |
| size_t triangleVertexBufferCount = 0; |
| size_t gradTextureHeight = 0; |
| size_t tessTextureHeight = 0; |
| size_t atlasTextureWidth = 0; |
| size_t atlasTextureHeight = 0; |
| size_t plsTransientBackingWidth = 0; |
| size_t plsTransientBackingHeight = 0; |
| size_t plsTransientBackingPlaneCount = 0; |
| size_t plsAtomicCoverageBackingWidth = 0; // atomic mode only. |
| size_t plsAtomicCoverageBackingHeight = 0; // atomic mode only. |
| size_t coverageBufferLength = 0; // clockwiseAtomic mode only. |
| }; |
| |
| // 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 ResourceAllocationCounts&); |
| |
| // Returns the next coverage buffer prefix to use in a logical flush. |
| // Sets needsCoverageBufferClear if the coverage buffer must be cleared in |
| // order to support the returned coverage buffer prefix. |
| // (clockwiseAtomic mode only.) |
| uint32_t incrementCoverageBufferPrefix(bool* needsCoverageBufferClear); |
| |
| const std::unique_ptr<RenderContextImpl> m_impl; |
| const size_t m_maxPathID; |
| |
| ResourceAllocationCounts m_currentResourceAllocations; |
| ResourceAllocationCounts m_maxRecentResourceRequirements; |
| double m_lastResourceTrimTimeInSeconds; |
| |
| // Per-frame state. |
| FrameDescriptor m_frameDescriptor; |
| gpu::InterlockMode m_frameInterlockMode; |
| gpu::ShaderFeatures m_frameShaderFeaturesMask; |
| RIVE_DEBUG_CODE(bool m_didBeginFrame = false;) |
| |
| // Clipping state. |
| uint32_t m_clipContentID = 0; |
| |
| // Monotonically increasing prefix that gets appended to the most |
| // significant "32 - CLOCKWISE_COVERAGE_BIT_COUNT" bits of coverage buffer |
| // values. |
| // |
| // Increasing this prefix implicitly clears the entire coverage buffer to |
| // zero. |
| // |
| // (clockwiseAtomic mode only.) |
| uint32_t m_coverageBufferPrefix = 0; |
| |
| // Used by LogicalFlushes for re-ordering high level draws. |
| std::vector<int64_t> m_indirectDrawList; |
| std::unique_ptr<IntersectionBoard> m_intersectionBoard; |
| |
| WriteOnlyMappedMemory<gpu::FlushUniforms> m_flushUniformData; |
| WriteOnlyMappedMemory<gpu::PathData> m_pathData; |
| WriteOnlyMappedMemory<gpu::PaintData> m_paintData; |
| WriteOnlyMappedMemory<gpu::PaintAuxData> m_paintAuxData; |
| WriteOnlyMappedMemory<gpu::ContourData> m_contourData; |
| WriteOnlyMappedMemory<gpu::GradientSpan> m_gradSpanData; |
| WriteOnlyMappedMemory<gpu::TessVertexSpan> m_tessSpanData; |
| WriteOnlyMappedMemory<gpu::TriangleVertex> m_triangleVertexData; |
| WriteOnlyMappedMemory<gpu::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. |
| |
| class TessellationWriter; |
| |
| // Manages a list of high-level Draws 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(RenderContext* 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(); |
| |
| const FrameDescriptor& frameDescriptor() const |
| { |
| return m_ctx->frameDescriptor(); |
| } |
| gpu::InterlockMode interlockMode() const |
| { |
| return m_ctx->frameInterlockMode(); |
| } |
| |
| // Access this flush's gpu::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 gpu::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 Draws 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 pushDraws(DrawUniquePtr draws[], size_t drawCount); |
| |
| // Running counts of data records required by Draws that need to be |
| // allocated in the render context's various GPU buffers. |
| struct ResourceCounters |
| { |
| constexpr static int NUM_ELEMENTS = 7; |
| using VecType = simd::gvec<size_t, NUM_ELEMENTS>; |
| |
| VecType toVec() const |
| { |
| static_assert(sizeof(*this) == sizeof(size_t) * NUM_ELEMENTS); |
| static_assert(sizeof(VecType) >= sizeof(*this)); |
| VecType vec; |
| RIVE_INLINE_MEMCPY(&vec, this, sizeof(*this)); |
| return vec; |
| } |
| |
| ResourceCounters(const VecType& vec) |
| { |
| static_assert(sizeof(*this) == sizeof(size_t) * NUM_ELEMENTS); |
| static_assert(sizeof(VecType) >= sizeof(*this)); |
| 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; |
| // lines, curves, lone joins, emulated caps, etc. |
| size_t maxTessellatedSegmentCount = 0; |
| size_t maxTriangleVertexCount = 0; |
| size_t imageDrawCount = 0; // imageRect or imageMesh. |
| }; |
| |
| // 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 gradSpanCount = 0; |
| uint32_t gradSpanPaddingCount = 0; |
| uint32_t maxGradTextureHeight = 0; |
| uint32_t maxTessTextureHeight = 0; |
| uint32_t maxAtlasWidth = 0; |
| uint32_t maxAtlasHeight = 0; |
| uint32_t maxPLSTransientBackingPlaneCount = 0; |
| size_t maxCoverageBufferLength = 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 Gradient*, |
| gpu::ColorRampLocation*); |
| |
| // Allocates a rectangular region in the atlas for this draw to use, and |
| // registers a future callback to PathDraw::pushAtlasTessellation() |
| // where it will render its coverage data to this same region in the |
| // atlas. |
| // |
| // Attempts to leave a border of "desiredPadding" pixels surrounding the |
| // rectangular region, but the allocation may not be padded if the path |
| // is up against an edge. |
| bool allocateAtlasDraw(PathDraw*, |
| uint16_t drawWidth, |
| uint16_t drawHeight, |
| uint16_t desiredPadding, |
| uint16_t* x, |
| uint16_t* y, |
| TAABB<uint16_t>* paddedRegion); |
| |
| // Reserves a range within the coverage buffer for a path to use in |
| // clockwiseAtomic mode. |
| // |
| // "length" is the length in pixels of this allocation and must be a |
| // multiple of 32*32, in order to support 32x32 tiling. |
| // |
| // Returns the offset of the allocated range within the coverage buffer, |
| // or -1 if there was not room. |
| size_t allocateCoverageBufferRange(size_t length); |
| |
| // 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, |
| 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(); |
| |
| // Reserves a span of "count" vertices from the "midpointFanPatches" |
| // section of the tessellation texture. |
| // |
| // This method must be called for a total count of precisely |
| // "m_resourceCounts.midpointFanTessVertexCount" vertices. |
| // |
| // The caller must fill these vertices in with TessellationWriter. |
| // |
| // Returns the index of the first vertex in the newly allocated span. |
| uint32_t allocateMidpointFanTessVertices(uint32_t count); |
| |
| // Reserves a span of "count" vertices from the "outerCurvePatches" |
| // section of the tessellation texture. |
| // |
| // This method must be called for a total count of precisely |
| // "m_resourceCounts.outerCubicTessVertexCount" vertices. |
| // |
| // The caller must fill these vertices in with TessellationWriter. |
| // |
| // Returns the index of the first vertex in the newly allocated span. |
| uint32_t allocateOuterCubicTessVertices(uint32_t count); |
| |
| // Allocates and initializes a record on the GPU for the given path. |
| // |
| // Returns a unique 16-bit "pathID" handle for this specific record. |
| // |
| // This method does not add the path to the draw list. The caller must |
| // define that draw specifically with a separate call to |
| // pushMidpointFanDraw() or pushOuterCubicsDraw(). |
| [[nodiscard]] uint32_t pushPath(const PathDraw* draw); |
| |
| // Pushes a contour record to the GPU that references the given path. |
| // |
| // "vertexIndex0" is the index within the tessellation where the first |
| // vertex of the contour resides. Shaders need this when the contour is |
| // closed. |
| // |
| // Returns a unique 16-bit "contourID" handle for this specific record. |
| // This ID may be or-ed with '*_CONTOUR_FLAG' bits from constants.glsl. |
| [[nodiscard]] uint32_t pushContour(uint32_t pathID, |
| Vec2D midpoint, |
| bool isStroke, |
| bool closed, |
| uint32_t vertexIndex0); |
| |
| // 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 count, uint32_t tessLocation); |
| |
| // Pushes a "midpointFanPatches" draw to the list. Path, contour, and |
| // cubic data are pushed separately. |
| // |
| // Also adds the PathDraw to a dstRead list if one is |
| // required, and if this is the path's first subpass. |
| void pushMidpointFanDraw( |
| const PathDraw*, |
| gpu::DrawType, |
| uint32_t tessVertexCount, |
| uint32_t tessLocation, |
| gpu::ShaderMiscFlags = gpu::ShaderMiscFlags::none); |
| |
| // Pushes an "outerCurvePatches" draw to the list. Path, contour, and |
| // cubic data are pushed separately. |
| // |
| // Also adds the PathDraw to a dstRead list if one is |
| // required, and if this is the path's first subpass. |
| void pushOuterCubicsDraw( |
| const PathDraw*, |
| gpu::DrawType, |
| uint32_t tessVertexCount, |
| uint32_t tessLocation, |
| gpu::ShaderMiscFlags = gpu::ShaderMiscFlags::none); |
| |
| // Writes out triangle verties for the desired WindingFaces and pushes |
| // an "interiorTriangulation" draw to the list. |
| // Returns the number of vertices actually written. |
| size_t pushInteriorTriangulationDraw( |
| const PathDraw*, |
| uint32_t pathID, |
| gpu::WindingFaces, |
| gpu::ShaderMiscFlags = gpu::ShaderMiscFlags::none); |
| |
| // Pushes a screen-space rectangle to the draw list, whose pixel |
| // coverage is determined by the atlas region associated with the given |
| // pathID. |
| void pushAtlasBlit(PathDraw*, uint32_t pathID); |
| |
| // Pushes an "imageRect" to the draw list. |
| // This should only be used when we in atomic mode. Otherwise, images |
| // should be drawn as rectangular paths with an image paint. |
| void pushImageRectDraw(ImageRectDraw*); |
| |
| // Pushes an "imageMesh" draw to the list. |
| void pushImageMeshDraw(ImageMeshDraw*); |
| |
| // Pushes a "stencilClipReset" draw to the list. |
| void pushStencilClipResetDraw(StencilClipReset*); |
| |
| private: |
| friend class TessellationWriter; |
| |
| ClipInfo& getWritableClipInfo(uint32_t clipID); |
| |
| // 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(const PathDraw*, |
| DrawType, |
| gpu::ShaderMiscFlags, |
| uint32_t vertexCount, |
| uint32_t baseVertex); |
| DrawBatch& pushDraw(const Draw*, |
| DrawType, |
| gpu::ShaderMiscFlags, |
| gpu::PaintType, |
| uint32_t elementCount, |
| uint32_t baseElement); |
| |
| // Instance pointer to the outer parent class. |
| RenderContext* const m_ctx; |
| |
| // Running counts of GPU data records that need to be allocated for |
| // draws. |
| ResourceCounters m_resourceCounts; |
| |
| // Running count of combined prepasses and subpasses from every draw in |
| // m_draws. |
| int m_drawPassCount; |
| |
| // 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<gpu::TwoTexelRamp> m_pendingSimpleGradDraws; |
| |
| // 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 Gradient*> m_pendingComplexGradDraws; |
| |
| // Simple and complex gradients both get uploaded to the GPU as sets of |
| // "GradientSpan" instances. |
| size_t m_pendingGradSpanCount; |
| |
| std::vector<ClipInfo> m_clips; |
| |
| // High-level draw list. These get built into a low-level list of |
| // gpu::DrawBatch objects during writeResources(). |
| std::vector<DrawUniquePtr> m_draws; |
| IAABB m_combinedDrawBounds; |
| gpu::DrawContents m_combinedDrawContents; |
| |
| // State computed during layout. |
| uint32_t m_pathPaddingCount; |
| uint32_t m_paintPaddingCount; |
| uint32_t m_paintAuxPaddingCount; |
| uint32_t m_contourPaddingCount; |
| uint32_t m_gradSpanPaddingCount; |
| uint32_t m_midpointFanTessEndLocation; |
| uint32_t m_outerCubicTessEndLocation; |
| uint32_t m_outerCubicTessVertexIdx; |
| uint32_t m_midpointFanTessVertexIdx; |
| gpu::GradTextureLayout m_gradTextureLayout; |
| gpu::ShaderMiscFlags m_baselineShaderMiscFlags; |
| |
| gpu::FlushDescriptor m_flushDesc; |
| |
| BlockAllocatedLinkedList<DrawBatch> m_drawList; |
| const DrawBatch* m_firstDstBlendBarrier; |
| // Final "next" pointer in the list of DrawBatches that have dstBlend |
| // barriers. |
| const DrawBatch** m_dstBlendBarrierListTail; |
| |
| gpu::ShaderFeatures m_combinedShaderFeatures; |
| |
| // Most recent path and contour state. |
| uint32_t m_currentPathID; |
| uint32_t m_currentContourID; |
| |
| // Atlas for offscreen feathering. |
| std::unique_ptr<skgpu::RectanizerSkyline> m_atlasRectanizer; |
| uint32_t m_atlasMaxX = 0; |
| uint32_t m_atlasMaxY = 0; |
| std::vector<PathDraw*> m_pendingAtlasDraws; |
| |
| // Total coverage allocated via allocateCoverageBufferRange(). |
| // (clockwiseAtomic mode only.) |
| uint32_t m_coverageBufferLength = 0; |
| |
| // Barriers that must execute before pushing the next DrawBatch |
| // (pushPathDraw()/pushDraw()). If any barriers are pending, this also |
| // prevents DrawBatches from being combined with the existing drawList. |
| BarrierFlags m_pendingBarriers; |
| |
| // Stateful Z index of the current draw being pushed. Used by msaa 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; |
| |
| // Writes out TessVertexSpans that are used to tessellate the vertices |
| // in a path. |
| class TessellationWriter |
| { |
| public: |
| // forwardTessLocation & mirroredTessLocation are allocated by |
| // allocate*TessVertices(). |
| // |
| // forwardTessLocation starts at the beginning of the vertex span |
| // and advances forward. |
| // |
| // mirroredTessLocation starts at the end of the vertex span and |
| // advances backward. |
| // |
| // If the ContourDirections are double sided, forwardTessVertexCount |
| // & mirroredTessVertexCount must both be equal, and |
| // forwardTessLocation & mirroredTessLocation must both be valid. |
| // Otherwise, one span or the other may be empty. |
| TessellationWriter(LogicalFlush* flush, |
| uint32_t pathID, |
| gpu::ContourDirections, |
| uint32_t forwardTessVertexCount, |
| uint32_t forwardTessLocation, |
| uint32_t mirroredTessVertexCount = 0, |
| uint32_t mirroredTessLocation = 0); |
| |
| ~TessellationWriter(); |
| |
| // Returns the index of the next vertex to be written. |
| // |
| // In the case of double-sided tessellations the next vertex gets |
| // tessellated twice, and either index will be identical. So we just |
| // return the next *forward* tessellation index when it's double sided. |
| uint32_t nextVertexIndex() |
| { |
| return m_contourDirections != gpu::ContourDirections::reverse |
| ? m_pathTessLocation |
| : m_pathMirroredTessLocation - 1; |
| } |
| |
| // Wrapper around LogicalFlush::pushContour(), with an additional |
| // padding option. |
| // |
| // 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 gpu::PaddingToAlignUp().) |
| [[nodiscard]] uint32_t pushContour(Vec2D midpoint, |
| bool isStroke, |
| bool closed, |
| uint32_t paddingVertexCount); |
| |
| // Wites out (potentially wrapped) TessVertexSpan(s) that tessellate |
| // a cubic curve & join at the current tessellation location(s). |
| // Advances the tessellation location(s). |
| // |
| // The bottom 16 bits of contourIDWithFlags must match the most |
| // recent contourID returned by pushContour(), but it may also have |
| // extra '*_CONTOUR_FLAG' bits from constants.glsl |
| // |
| // 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], |
| gpu::ContourDirections, |
| Vec2D joinTangent, |
| uint32_t parametricSegmentCount, |
| uint32_t polarSegmentCount, |
| uint32_t joinSegmentCount, |
| uint32_t contourIDWithFlags); |
| |
| // pushCubic() impl for forward tessellations. |
| 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); |
| |
| // pushCubic() impl for mirrored tessellations. |
| 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 gpu::TessVertexSpan. |
| RIVE_ALWAYS_INLINE void pushDoubleSidedTessellationSpans( |
| const Vec2D pts[4], |
| Vec2D joinTangent, |
| uint32_t totalVertexCount, |
| uint32_t parametricSegmentCount, |
| uint32_t polarSegmentCount, |
| uint32_t joinSegmentCount, |
| uint32_t contourIDWithFlags); |
| |
| private: |
| LogicalFlush* const m_flush; |
| WriteOnlyMappedMemory<gpu::TessVertexSpan>& m_tessSpanData; |
| const uint32_t m_pathID; |
| const gpu::ContourDirections m_contourDirections; |
| uint32_t m_pathTessLocation; |
| uint32_t m_pathMirroredTessLocation; |
| // Padding to add to the next curve. |
| uint32_t m_nextCubicPaddingVertexCount = 0; |
| RIVE_DEBUG_CODE(uint32_t m_expectedPathTessEndLocation;) |
| RIVE_DEBUG_CODE(uint32_t m_expectedPathMirroredTessEndLocation;) |
| }; |
| }; |
| } // namespace rive::gpu |