blob: 54334e0cb10fc82ea56ca269e56ecd0ca152343a [file]
/*
* Copyright 2025 Rive
*/
#include <catch.hpp>
#include "rive/renderer/range_chunker.hpp"
#include "shaders/constants.glsl"
using namespace rive;
using namespace rive::gpu;
TEST_CASE("maxPerDrawCommand==2, odd", "[RangeChunker]")
{
RangeChunker c(5, 0, 2);
auto it = c.begin();
CHECK((*it).first == 0);
CHECK((*it).count == 2);
CHECK(it != c.end());
++it;
CHECK((*it).first == 2);
CHECK((*it).count == 2);
CHECK(it != c.end());
++it;
CHECK((*it).first == 4);
CHECK((*it).count == 1);
CHECK(it != c.end());
for (size_t i = 0; i < 10; ++i)
{
++it;
CHECK((*it).first == 5);
CHECK((*it).count == 0);
CHECK(it == c.end());
}
}
TEST_CASE("maxPerDrawCommand==2, even", "[RangeChunker]")
{
RangeChunker c(6, 0, 2);
auto it = c.begin();
CHECK((*it).first == 0);
CHECK((*it).count == 2);
CHECK(it != c.end());
++it;
CHECK((*it).first == 2);
CHECK((*it).count == 2);
CHECK(it != c.end());
++it;
CHECK((*it).first == 4);
CHECK((*it).count == 2);
CHECK(it != c.end());
for (size_t i = 0; i < 10; ++i)
{
++it;
CHECK((*it).first == 6);
CHECK((*it).count == 0);
CHECK(it == c.end());
}
}
TEST_CASE("maxPerDrawCommand==0xffffffff", "[RangeChunker]")
{
RangeChunker c(5, 0, 0xffffffff);
auto it = c.begin();
CHECK((*it).first == 0);
CHECK((*it).count == 5);
CHECK(it != c.end());
for (size_t i = 0; i < 10; ++i)
{
++it;
CHECK((*it).first == 5);
CHECK((*it).count == 0);
CHECK(it == c.end());
}
}
TEST_CASE("maxPerDrawCommand==0", "[RangeChunker]")
{
RangeChunker c(5, 0, 0);
auto it = c.begin();
for (size_t i = 0; i < 10; ++i)
{
CHECK(it != c.end());
CHECK((*it).first == 0);
CHECK((*it).count == 0);
++it;
}
}
TEST_CASE("empty", "[RangeChunker]")
{
RangeChunker c(0, 0, 5);
auto it = c.begin();
for (size_t i = 0; i < 10; ++i)
{
CHECK(it == c.end());
++it;
}
}
TEST_CASE("empty,maxPerDrawCommand==0", "[RangeChunker]")
{
RangeChunker c(0, 0, 0);
auto it = c.begin();
for (size_t i = 0; i < 10; ++i)
{
CHECK(it == c.end());
++it;
}
}
// DSIndexRangeChunker turns each chunk into draw arguments. The two patch types
// differ in index count, patch stride, and reps per buffer, so every case runs
// both: transposing any of the three inside the iterator would still compile.
static void checkDraw(DSIndexRangeChunker::Draw draw,
bool outerCubic,
uint32_t patchCount,
uint32_t firstPatch,
int32_t extraFlags = 0)
{
CHECK(draw.indexCount == patchCount * dsFillPatchIndexCount(outerCubic));
CHECK(draw.baseVertex ==
((static_cast<int32_t>(firstPatch)
<< DS_PATCH_STRIDE_LOG2(outerCubic)) |
extraFlags | (outerCubic ? VERTEX_FLAG_OUTER_CUBIC : 0)));
}
static DrawType dsFillDrawType(bool outerCubic)
{
return outerCubic ? DrawType::stencilOuterCubics
: DrawType::stencilMidpointFans;
}
TEST_CASE("one chunk", "[DSIndexRangeChunker]")
{
for (bool outerCubic : {false, true})
{
DSIndexRangeChunker iter(dsFillDrawType(outerCubic), 3, 7);
auto it = iter.begin();
REQUIRE(it != iter.end());
checkDraw(*it, outerCubic, 3, 7);
++it;
CHECK(it == iter.end());
}
}
TEST_CASE("base patch zero", "[DSIndexRangeChunker]")
{
for (bool outerCubic : {false, true})
{
DSIndexRangeChunker iter(dsFillDrawType(outerCubic), 1, 0);
auto it = iter.begin();
REQUIRE(it != iter.end());
// Nothing but the patch-type flag, so a midpoint fan draws from 0.
CHECK((*it).baseVertex == (outerCubic ? VERTEX_FLAG_OUTER_CUBIC : 0));
checkDraw(*it, outerCubic, 1, 0);
}
}
TEST_CASE("empty batch", "[DSIndexRangeChunker]")
{
for (bool outerCubic : {false, true})
{
DSIndexRangeChunker iter(dsFillDrawType(outerCubic), 0, 4);
CHECK(iter.begin() == iter.end());
}
}
TEST_CASE("splits at the buffer's rep count", "[DSIndexRangeChunker]")
{
for (bool outerCubic : {false, true})
{
const uint32_t maxReps = dsFillPatchMaxReps(outerCubic);
const uint32_t tail = 13;
DSIndexRangeChunker iter(dsFillDrawType(outerCubic), maxReps + tail, 0);
auto it = iter.begin();
REQUIRE(it != iter.end());
checkDraw(*it, outerCubic, maxReps, 0);
++it;
REQUIRE(it != iter.end());
// The second draw restarts at the buffer's first patch; only
// baseVertex advances.
checkDraw(*it, outerCubic, tail, maxReps);
++it;
CHECK(it == iter.end());
}
}
TEST_CASE("carries the color-write flag", "[DSIndexRangeChunker]")
{
for (bool outerCubic : {false, true})
{
DSIndexRangeChunker iter(dsFillDrawType(outerCubic),
2,
5,
VERTEX_FLAG_DISABLE_COLOR_WRITE);
auto it = iter.begin();
REQUIRE(it != iter.end());
checkDraw(*it, outerCubic, 2, 5, VERTEX_FLAG_DISABLE_COLOR_WRITE);
CHECK(((*it).baseVertex & VERTEX_FLAG_DISABLE_COLOR_WRITE) != 0);
}
}
TEST_CASE("range-for covers every patch exactly once", "[DSIndexRangeChunker]")
{
for (bool outerCubic : {false, true})
{
const uint32_t maxReps = dsFillPatchMaxReps(outerCubic);
const uint32_t patchCount = maxReps * 2 + 3;
uint32_t totalIndices = 0;
uint32_t draws = 0;
for (auto [indexCount, baseVertex] :
DSIndexRangeChunker(dsFillDrawType(outerCubic), patchCount, 0))
{
totalIndices += indexCount;
++draws;
}
CHECK(draws == 3);
CHECK(totalIndices == patchCount * dsFillPatchIndexCount(outerCubic));
}
}