| /* | |
| * 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 "rive/renderer/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 | |
| 0xFFFFFFFF, // modulatedColor | |
| 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::OuterCubicPatchSegmentSpanPlusBowtie * | |
| std::size(TriangleStrips) * 2 | |
| : gpu::OuterCubicPatchSegmentSpanPlusBowtie * | |
| std::size(TriangleStrips); | |
| m_triangulator = context->make<GrInnerFanTriangulator>( | |
| RawPath(), | |
| AABB{}, | |
| &context->perFrameAllocator()); | |
| } | |
| void countSubpasses(const gpu::PlatformFeatures& platformFeatures) override | |
| { | |
| // Skip PathDraw's version of countSubpasses, which works out pass | |
| // counts based on path and coverage types. | |
| // This GM is a handwritten hack to specifically draw retrofitted | |
| // triangle strips, which aren't a thing that PathDraw renders in | |
| // isolation. | |
| Draw::countSubpasses(platformFeatures); | |
| } | |
| bool allocateResources(RenderContext::LogicalFlush* flush) override | |
| { | |
| if (!PathDraw::allocateResources(flush)) | |
| { | |
| return false; | |
| } | |
| return true; | |
| } | |
| gpu::DrawBatch* pushToRenderContext( | |
| RenderContext::LogicalFlush* flush, | |
| int signedSubpassIdx, | |
| uint32_t zIndex) 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(signedSubpassIdx == 0); | |
| uint32_t tessVertexCount = math::lossless_numeric_cast<uint32_t>( | |
| m_resourceCounts.outerCubicTessVertexCount); | |
| if (tessVertexCount > 0) | |
| { | |
| gpu::DrawContents drawContents = subpassDrawContents(signedSubpassIdx); | |
| m_pathID = flush->pushPath(this, zIndex); | |
| 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) | |
| { | |
| drawContents |= gpu::DrawContents::clockwiseFill; | |
| } | |
| return &flush->pushOuterCubicsDraw( | |
| this, | |
| m_coverageType == CoverageType::depthStencil | |
| ? gpu::DrawType::stencilOuterCubics | |
| : gpu::DrawType::outerCurvePatches, | |
| drawContents, | |
| 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;) |