blob: 8426ec8443e77dc38e87498e3dad680c3f3f53b8 [file]
/*
* Copyright 2023 Rive
*/
#include "gm.hpp"
#include "gr_inner_fan_triangulator.hpp"
#include "common/testing_window.hpp"
#include "rive/math/math_types.hpp"
#include "rive/renderer.hpp"
#include "rive/renderer/draw.hpp"
#include "rive/renderer/render_context.hpp"
#include "../src/rive_render_paint.hpp"
#include "../src/rive_render_path.hpp"
#include "../src/shaders/constants.glsl"
#include <cmath>
using namespace rivegm;
using namespace rive;
using namespace rive::gpu;
// Generates the vertices of a regular n-gon (circumradius r, centered at c,
// with vertex 0 at startAngleDegrees) in triangle-strip order. The zig-zag
// sequence 0, 1, n-1, 2, n-2, ... tessellates any convex polygon as a single
// strip.
static std::vector<Vec2D> regular_polygon_strip(int n,
Vec2D c,
float r,
float startAngleDegrees)
{
std::vector<Vec2D> perimeter;
for (int i = 0; i < n; ++i)
{
float a = math::degrees_to_radians(startAngleDegrees) +
i * (2.f * math::PI / n);
perimeter.push_back({c.x + r * std::cos(a), c.y + r * std::sin(a)});
}
std::vector<Vec2D> strip = {perimeter[0]};
for (int lo = 1, hi = n - 1; lo <= hi;)
{
strip.push_back(perimeter[lo++]);
if (lo <= hi)
{
strip.push_back(perimeter[hi--]);
}
}
return strip;
}
// A 1x3 column of regular polygons, each drawn as a single retrofitted cubic
// triangle strip: an equilateral triangle, a square, then a regular pentagon.
static const std::vector<std::vector<Vec2D>> TriangleStrips = {
regular_polygon_strip(3, {200, 200}, 150, -90.f),
regular_polygon_strip(4, {200, 600}, 150, -135.f),
regular_polygon_strip(5, {200, 1000}, 150, -90.f),
};
rcp<RiveRenderPath> make_nonempty_placeholder_path()
{
auto path = make_rcp<RiveRenderPath>();
path->moveTo(0, 0);
return path;
}
class PushRetrofitTriStripsGMDraw : public PathDraw
{
public:
PushRetrofitTriStripsGMDraw(RenderContext* context,
const Mat2D& matrix,
const RiveRenderPaint* paint) :
PathDraw(FULLSCREEN_PIXEL_BOUNDS,
matrix,
nullptr,
make_nonempty_placeholder_path(),
context->frameDescriptor().clockwiseFillOverride
? FillRule::clockwise
: FillRule::nonZero,
paint,
1.0f, // modulatedOpacity
SelectCoverageType(paint,
1,
context->platformFeatures(),
context->frameInterlockMode()),
context->frameDescriptor())
{
m_resourceCounts.pathCount = 1;
m_resourceCounts.contourCount = 1;
m_resourceCounts.maxTessellatedSegmentCount = std::size(TriangleStrips);
m_resourceCounts.outerCubicTessVertexCount =
context->frameInterlockMode() != gpu::InterlockMode::depthStencil
? gpu::OuterCubicPatchSegmentSpanPlusJoin *
std::size(TriangleStrips) * 2
: gpu::OuterCubicPatchSegmentSpanPlusJoin *
std::size(TriangleStrips);
m_triangulator = context->make<GrInnerFanTriangulator>(
RawPath(),
AABB{},
&context->perFrameAllocator());
}
void countSubpasses(const gpu::PlatformFeatures&) override
{
assert(m_prepassCount == 0);
assert(m_subpassCount == 1);
}
bool allocateResources(RenderContext::LogicalFlush* flush) override
{
if (!PathDraw::allocateResources(flush))
{
return false;
}
return true;
}
gpu::DrawBatch* pushToRenderContext(RenderContext::LogicalFlush* flush,
int subpassIndex) override
{
// Make sure the rawPath in our path reference hasn't changed since we
// began holding!
assert(m_rawPathMutationID == m_pathRef->getRawPathMutationID());
assert(!m_pathRef->getRawPath().empty());
assert(subpassIndex == 0);
uint32_t tessVertexCount = math::lossless_numeric_cast<uint32_t>(
m_resourceCounts.outerCubicTessVertexCount);
if (tessVertexCount > 0)
{
m_pathID = flush->pushPath(this);
uint32_t tessLocation =
flush->allocateOuterCubicTessVertices(tessVertexCount);
uint32_t forwardTessVertexCount, forwardTessLocation,
mirroredTessVertexCount, mirroredTessLocation;
if (m_contourDirections ==
gpu::ContourDirections::reverseThenForward)
{
forwardTessVertexCount = mirroredTessVertexCount =
tessVertexCount / 2;
forwardTessLocation = mirroredTessLocation =
tessLocation + tessVertexCount / 2;
}
else
{
assert(m_contourDirections == gpu::ContourDirections::forward);
forwardTessVertexCount = tessVertexCount;
forwardTessLocation = tessLocation;
mirroredTessVertexCount = mirroredTessLocation = 0;
}
RenderContext::TessellationWriter tessWriter(
flush,
m_pathID,
m_contourDirections,
forwardTessVertexCount,
forwardTessLocation,
mirroredTessVertexCount,
mirroredTessLocation);
uint32_t contourID = tessWriter.pushContour(
{0, 0},
/*isStroke=*/false,
/*closed=*/true,
0 /* gpu::OuterCubicPatchSegmentSpan - 1 */);
for (const auto& strips : TriangleStrips)
{
tessWriter.pushRetrofitCubicTriStrip(strips.data(),
strips.size(),
m_contourDirections,
contourID);
}
if (flush->frameDescriptor().clockwiseFillOverride)
{
m_drawContents |= gpu::DrawContents::clockwiseFill;
}
return &flush->pushOuterCubicsDraw(
this,
m_coverageType == CoverageType::depthStencil
? gpu::DrawType::stencilOuterCubics
: gpu::DrawType::outerCurvePatches,
tessVertexCount,
tessLocation,
gpu::ShaderMiscFlags::none);
}
return nullptr;
}
};
// Checks that RenderContext properly draws triangle strips when using the
// RETROFIT_TRI_STRIP_CONTOUR_FLAG flag.
class RetrofitCubicTriStripsGM : public GM
{
public:
RetrofitCubicTriStripsGM() : GM(800, 1200) {}
protected:
virtual rive::ColorInt clearColor() const override { return 0xff000000; }
void onDraw(Renderer* renderer) override
{
gpu::RenderContext* renderContext =
TestingWindow::Get()->renderContext();
if (renderContext != nullptr)
{
RiveRenderPaint paint;
paint.color(0xffffffff);
DrawUniquePtr draw(
renderContext->make<PushRetrofitTriStripsGMDraw>(renderContext,
Mat2D(),
&paint));
bool success RIVE_MAYBE_UNUSED = renderContext->pushDraws(&draw, 1);
assert(success);
rive::gpu::RenderContext::FrameDescriptor frameDescriptor =
renderContext->frameDescriptor();
frameDescriptor.loadAction =
rive::gpu::LoadAction::preserveRenderTarget;
#ifndef RIVE_ANDROID
// Wireframe is not always supported on Android (e.g. Mali). Skip it
// here so every Android device produces the same gold.
frameDescriptor.wireframe = true;
#endif
TestingWindow::Get()->flushPLSContext();
renderContext->beginFrame(std::move(frameDescriptor));
renderer->translate(400, 0);
DrawUniquePtr wireframeDraw(
renderContext->make<PushRetrofitTriStripsGMDraw>(
renderContext,
Mat2D::fromTranslation({400, 0}),
&paint));
success = renderContext->pushDraws(&wireframeDraw, 1);
assert(success);
}
}
};
GMREGISTER(retrofitcubictristrips, return new RetrofitCubicTriStripsGM;)