blob: 376322e527316b01e16a4ba70174ab18e83074f1 [file] [log] [blame]
/*
* Copyright 2025 Rive
*/
#include "gm.hpp"
#include "gmutils.hpp"
#include "rive/renderer.hpp"
#include <set>
using namespace rivegm;
using namespace rive;
constexpr static float R = 490;
static void add_circle(PathBuilder&,
float x,
float y,
float radius,
rivegm::PathDirection);
static void draw_spirograph(Renderer*,
RenderPaint*,
RenderPath* circles,
float2 translate,
float2 c,
int depth,
std::set<std::tuple<int, int>>* drawnCenters);
// Stress tests the renderer by trying to fit as many tessellation spans into a
// single flush as possible. This will hopefully detect any potential driver
// issues associated with tess span overload.
class LotsOfTessSpansGM : public GM
{
public:
LotsOfTessSpansGM(RenderPaintStyle style) : GM(1024, 1024), m_style(style)
{}
private:
void onDraw(Renderer* renderer) override
{
PathBuilder pathBuilder;
pathBuilder.fillRule(FillRule::clockwise);
for (uint32_t i = (m_style == RenderPaintStyle::fill) ? 25 : 1; i < 50;
++i)
{
add_circle(pathBuilder, 0, 0, i * 10, rivegm::PathDirection::cw);
if (m_style == RenderPaintStyle::fill)
{
add_circle(pathBuilder,
0,
0,
i * 10 - 1,
rivegm::PathDirection::ccw);
}
}
Path circles = pathBuilder.detach();
float2 translate = {512, 512};
Paint paint;
paint->style(m_style);
// Use round joins because they use fewer segments when nearly flat, and
// we want to generate as many tessellation *segments* (not spokes) as
// possible in a single flush.
paint->join(StrokeJoin::round);
paint->color((m_style == RenderPaintStyle::fill) ? 0xff7500ff
: 0xfffe0089);
renderer->drawPath(circles, paint);
std::set<std::tuple<int, int>> drawnCenters;
draw_spirograph(renderer,
paint,
circles,
translate,
float2{-R, 0},
3,
&drawnCenters);
draw_spirograph(renderer,
paint,
circles,
translate,
float2{0, R},
3,
&drawnCenters);
draw_spirograph(renderer,
paint,
circles,
translate,
float2{R, 0},
3,
&drawnCenters);
draw_spirograph(renderer,
paint,
circles,
translate,
float2{0, -R},
3,
&drawnCenters);
}
const RenderPaintStyle m_style;
};
static void add_circle(PathBuilder& pathBuilder,
float x,
float y,
float radius,
rivegm::PathDirection dir)
{
const int n = std::max(static_cast<int>(2 * math::PI * radius / 32), 3);
pathBuilder.moveTo(x + radius, y);
for (int i = 1; i <= n; ++i)
{
float c0 = 2 * math::PI * static_cast<float>(i - (2.f / 3.f)) / n;
float c1 = 2 * math::PI * static_cast<float>(i - (1.f / 3.f)) / n;
float theta = 2 * math::PI * static_cast<float>(i) / n;
if (dir == rivegm::PathDirection::ccw)
{
c0 = -c0;
c1 = -c1;
theta = -theta;
}
// NOTE: this is not a true approximation of the arc. The segment is so
// tiny that any simple cubic will work for our testing purposes. A true
// arc approximation would take more complex math to figure out the
// tangents on both ends, and then scale them such that the height of
// the cubic matched the arc.
pathBuilder.cubicTo(x + radius * cosf(c0),
y + radius * sinf(c0),
x + radius * cosf(c1),
y + radius * sinf(c1),
x + radius * cosf(theta),
y + radius * sinf(theta));
}
pathBuilder.close();
}
static void draw_spirograph(Renderer* renderer,
RenderPaint* paint,
RenderPath* circles,
float2 translate,
float2 c,
int depth,
std::set<std::tuple<int, int>>* drawnCenters)
{
if (depth <= 0)
{
return;
}
translate += c;
// Only draw the circles once, centered within any given pixel. When we draw
// overlapping circles, coverage-based AA darkens but MSAA does not, leading
// to divergence in golden images.
int2 icenter = simd::cast<int>(simd::floor(translate + .5f));
if (drawnCenters->insert({int(icenter.x), int(icenter.y)}).second)
{
AutoRestore ar(renderer, /*doSave=*/true);
renderer->translate(translate.x, translate.y);
renderer->drawPath(circles, paint);
}
// Next draw circles centered on both points of intersection between the
// circle we just drew and the one before it!
// y = mx + b
float2 c0 = -c;
if (fabsf(c.x) > fabsf(c.y))
{
c0 = c0.yx;
}
const float m = -c0.x / c0.y;
const float b = simd::dot(c0, c0) / (2 * c0.y);
// Ax^2 + Bx + C = 0
const float A = 1 + m * m;
const float B = 2 * m * b;
const float C = b * b - R * R;
float2 left;
left.x = (-B + sqrtf(B * B - 4 * A * C)) / (2 * A);
left.y = m * left.x + b;
float2 right;
right.x = (-B - sqrtf(B * B - 4 * A * C)) / (2 * A);
right.y = m * right.x + b;
if (fabsf(c.x) > fabsf(c.y))
{
left = left.yx;
right = right.yx;
}
draw_spirograph(renderer,
paint,
circles,
translate,
left,
depth - 1,
drawnCenters);
draw_spirograph(renderer,
paint,
circles,
translate,
right,
depth - 1,
drawnCenters);
}
GMREGISTER(lots_of_tess_spans_stroke,
return new LotsOfTessSpansGM(RenderPaintStyle::stroke);)
// TODO: The "_fill" variant is wildly fill-heavy and triggers a
// VK_ERROR_DEVICE_LOST on some android devices. The error is probably
// appropriate for the capabilities of those devices, so we will need to figure
// out how to step around it if we want to use this test.
// GMREGISTER(lots_of_tess_spans_fill,
// return new LotsOfTessSpansGM(RenderPaintStyle::fill);)