blob: 7dc48afba2ebb0a6157a898fa500af728644e108 [file] [log] [blame]
/*
* Copyright 2024 Rive
*/
#pragma once
#include "rive/math/simd.hpp"
#include "rive/enum_bitset.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