| /* | |
| * Copyright 2024 Rive | |
| */ | |
| #pragma once | |
| #include "rive/math/simd.hpp" | |
| #include "rive/enums.hpp" | |
| #include <vector> | |
| namespace rive::gpu | |
| { | |
| // Adding rectangles allows a grouping type to be specified - this determines | |
| // whether a rectangle is allowed to share a group with another that it | |
| // overlaps. | |
| enum class GroupingType : bool | |
| { | |
| // The current rectangle cannot be grouped with any others for any reason, | |
| // so no additional testing/logic is necessary. (this is the default for | |
| // most render modes) | |
| disjoint, | |
| // The current rectangle is potentially allowed to group with others it | |
| // overlaps, assuming the test of "overlap bits" succeeds. | |
| overlapAllowed, | |
| }; | |
| // Tile within an intersection board that manages a set of rectangles and their | |
| // groupIndex. From a given rectangle, finds the max groupIndex in the set of | |
| // internal rectangles it intersects and, when needed, also finds the full set | |
| // of overlap bits from any interesections. The size is <= 255 so we can store | |
| // bounding box coordinates in 8 bits. | |
| class IntersectionTile | |
| { | |
| public: | |
| // The width and height of an intersection tile (must fit in an unsigned | |
| // byte). | |
| constexpr static int TILE_DIM = 255; | |
| static_assert(TILE_DIM <= std::numeric_limits<uint8_t>::max()); | |
| // Performance is better passing these two vectors as a struct than using | |
| // in/out parameters. | |
| struct FindResult | |
| { | |
| int16x8 maxGroupIndices = 0; | |
| uint16x8 overlapBits = 0; | |
| }; | |
| void reset(int left, | |
| int top, | |
| int16_t baselineGroupIndex = 0, | |
| uint16_t baselineOverlapBits = 0); | |
| template <GroupingType Type> | |
| void addRectangle(int4 ltrb, | |
| int16_t groupIndex, | |
| uint16_t currentRectangleOverlapBits); | |
| #ifdef WITH_RIVE_TOOLS | |
| // a testing-specific version that validates after doing the add | |
| template <GroupingType Type> | |
| void testingOnly_addRectangleAndValidate( | |
| int4 ltrb, | |
| int16_t groupIndex, | |
| uint16_t currentRectangleOverlapBits); | |
| #endif | |
| // Accumulate local maximum intersecting group indices for the given | |
| // rectangle in each channel of a int16x8. "runningMaxGroupIndices" is a | |
| // running set of local maximums if the IntersectionBoard also ran this same | |
| // test on other tile(s) that the rectangle touched. The absolute maximum | |
| // group index that this rectangle intersects with will be | |
| // simd::reduce_max(result.maxGroupIndices), and the relevant overlap bits | |
| // will be simd::reduce_or(result.overlapBits) | |
| template <GroupingType Type> | |
| FindResult findMaxIntersectingGroupIndex(int4 ltrb, | |
| FindResult runningResult) const; | |
| // The following were exposed for unit testing | |
| #ifdef WITH_RIVE_TOOLS | |
| size_t testingOnly_rectangleCount() const { return m_rectangleCount; } | |
| int16_t testingOnly_baselineGroupIndex() const | |
| { | |
| return m_baselineGroupIndex; | |
| } | |
| uint16_t testingOnly_baselineOverlapBits() const | |
| { | |
| return m_baselineOverlapBits; | |
| } | |
| int16_t testingOnly_maxGroupIndex() const { return m_maxGroupIndex; } | |
| uint16_t testingOnly_overlapBitsForMaxGroup() const | |
| { | |
| return m_overlapBitsForMaxGroup; | |
| } | |
| #endif | |
| private: | |
| // Set the baseline to equal the current max group index | |
| template <GroupingType Type> | |
| void updateBaselineToMaxGroupIndex(uint16_t additionalBaselineOverlapBits); | |
| int4 m_topLeft = {}; | |
| int16_t m_baselineGroupIndex = 0; | |
| uint16_t m_baselineOverlapBits = 0; | |
| int16_t m_maxGroupIndex = 0; | |
| uint16_t m_overlapBitsForMaxGroup = 0; | |
| size_t m_rectangleCount = 0; | |
| // We may need a bias to get the tile coordinates to fit within a signed | |
| // 8-bit value (because base-level SSE does not have unsigned comparisons) | |
| constexpr static int TILE_EDGE_BIAS = | |
| (TILE_DIM > std::numeric_limits<int8_t>::max()) ? -128 : 0; | |
| // How many rectangles/groupIndices are in each chunk of data? | |
| constexpr static size_t CHUNK_SIZE = 8; | |
| // Chunk of 8 rectangles encoded as [L, T, TILE_DIM - R, TILE_DIM - B], | |
| // relative to m_left and m_top. The data is also transposed: | |
| // [L0..L7, T0..T7, -R0..R7, -B0..B7]. | |
| std::vector<int8x32> m_edges; | |
| static_assert(sizeof(m_edges[0]) == CHUNK_SIZE * 4); | |
| // Chunk of 8 groupIndices corresponding to the above edges. | |
| std::vector<int16x8> m_groupIndices; | |
| static_assert(sizeof(m_groupIndices[0]) == CHUNK_SIZE * 2); | |
| // Chunk of 8 sets of overlap bits corresponding to the above edges. | |
| std::vector<uint16x8> m_overlapBits; | |
| static_assert(sizeof(m_overlapBits[0]) == CHUNK_SIZE * 2); | |
| }; | |
| // Manages a set of rectangles and their groupIndex across a variable-sized | |
| // viewport. Each time a rectangle is added, assigns and returns a groupIndex | |
| // that is one larger than the max groupIndex in the set of existing rectangles | |
| // it intersects. | |
| class IntersectionBoard | |
| { | |
| public: | |
| constexpr static auto TILE_DIM = IntersectionTile::TILE_DIM; | |
| IntersectionBoard(GroupingType groupingType) : m_groupingType(groupingType) | |
| {} | |
| void resizeAndReset(uint32_t viewportWidth, uint32_t viewportHeight); | |
| // Adds a rectangle to the internal set and assigns it "layerCount" | |
| // contiguous group indices, beginning one larger than the max groupIndex in | |
| // the set of existing rectangles it intersects. | |
| // | |
| // Returns the first newly assigned groupIndex for the added rectangle. | |
| // If it does not intersect with any other rectangles, this groupIndex is 1. | |
| // | |
| // It is the caller's responsibility to not insert more rectangles than can | |
| // fit in a signed 16-bit integer. (The result is signed because SSE doesn't | |
| // have an unsigned max instruction.) | |
| int16_t addRectangle( | |
| int4 ltrb, | |
| uint16_t | |
| currentRectangleOverlapBits, // "what is allowed to overlap me?" | |
| uint16_t | |
| disallowedOverlapBitsMask, // "what am I not allowed to overlap?" | |
| int16_t layerCount); | |
| int16_t addRectangle(int4 ltrb, int16_t layerCount = 1) | |
| { | |
| return addRectangle(ltrb, 0, 0, layerCount); | |
| } | |
| GroupingType groupingType() const { return m_groupingType; } | |
| private: | |
| template <GroupingType Type> | |
| int16_t addRectangle(int4 ltrb, | |
| uint16_t currentRectangleOverlapBits, | |
| uint16_t disallowedOverlapBitsMask, | |
| int16_t layerCount); | |
| GroupingType m_groupingType{}; | |
| int2 m_viewportSize{}; | |
| int32_t m_cols = 0; | |
| int32_t m_rows = 0; | |
| std::vector<IntersectionTile> m_tiles; | |
| }; | |
| } // namespace rive::gpu |