| /* |
| * Copyright 2023 Rive |
| */ |
| |
| #include "gm.hpp" |
| #include "gr_inner_fan_triangulator.hpp" |
| #include "common/testing_window.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" |
| |
| using namespace rivegm; |
| using namespace rive; |
| using namespace rive::gpu; |
| |
| constexpr static std::array<Vec2D, 3> kTris[] = { |
| {Vec2D{10, 10}, Vec2D{50, 10}, Vec2D{10, 120}}, |
| {Vec2D{10, 290}, Vec2D{290, 10}, Vec2D{270, 280}}, |
| {Vec2D{60, 60}, Vec2D{30, 190}, Vec2D{250, 20}}, |
| }; |
| constexpr static size_t kNumTriangles = sizeof(kTris) / sizeof(kTris[0]); |
| |
| rcp<RiveRenderPath> make_nonempty_placeholder_path() |
| { |
| auto path = make_rcp<RiveRenderPath>(); |
| path->moveTo(0, 0); |
| return path; |
| } |
| |
| class PushRetrofittedTrianglesGMDraw : public PathDraw |
| { |
| public: |
| PushRetrofittedTrianglesGMDraw(RenderContext* context, |
| const RiveRenderPaint* paint) : |
| PathDraw(FULLSCREEN_PIXEL_BOUNDS, |
| Mat2D(), |
| make_nonempty_placeholder_path(), |
| context->frameDescriptor().clockwiseFillOverride |
| ? FillRule::clockwise |
| : FillRule::nonZero, |
| paint, |
| SelectCoverageType(paint, |
| 1, |
| context->platformFeatures(), |
| context->frameInterlockMode()), |
| context->frameDescriptor()) |
| { |
| m_resourceCounts.pathCount = 1; |
| m_resourceCounts.contourCount = 1; |
| m_resourceCounts.maxTessellatedSegmentCount = kNumTriangles; |
| m_resourceCounts.outerCubicTessVertexCount = |
| context->frameInterlockMode() != gpu::InterlockMode::msaa |
| ? gpu::kOuterCurvePatchSegmentSpan * kNumTriangles * 2 |
| : gpu::kOuterCurvePatchSegmentSpan * kNumTriangles; |
| m_triangulator = context->make<GrInnerFanTriangulator>( |
| RawPath(), |
| Mat2D(), |
| GrTriangulator::Comparator::Direction::kHorizontal, |
| FillRule::nonZero, |
| &context->perFrameAllocator()); |
| } |
| |
| void countSubpasses() override |
| { |
| assert(m_prepassCount == 0); |
| assert(m_subpassCount == 1); |
| } |
| |
| bool allocateResources(RenderContext::LogicalFlush* flush) override |
| { |
| if (!PathDraw::allocateResources(flush)) |
| { |
| return false; |
| } |
| return true; |
| } |
| |
| void 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::kOuterCurvePatchSegmentSpan - 2 */); |
| for (const auto& pts : kTris) |
| { |
| Vec2D tri[4] = {pts[0], pts[1], {0, 0}, pts[2]}; |
| tessWriter.pushCubic(tri, |
| m_contourDirections, |
| {0, 0}, |
| gpu::kOuterCurvePatchSegmentSpan - 1, |
| 1, |
| 1, |
| contourID | |
| RETROFITTED_TRIANGLE_CONTOUR_FLAG); |
| } |
| |
| if (flush->frameDescriptor().clockwiseFillOverride) |
| { |
| m_drawContents |= gpu::DrawContents::clockwiseFill; |
| } |
| |
| flush->pushOuterCubicsDraw(this, |
| m_coverageType == CoverageType::msaa |
| ? gpu::DrawType::msaaOuterCubics |
| : gpu::DrawType::outerCurvePatches, |
| tessVertexCount, |
| tessLocation, |
| gpu::ShaderMiscFlags::none); |
| } |
| } |
| }; |
| |
| // Checks that RenderContext properly draws single triangles when using the |
| // "kRetrofittedTriangle" flag. |
| class RetrofittedCubicTrianglesGM : public GM |
| { |
| public: |
| RetrofittedCubicTrianglesGM() : GM(300, 300) {} |
| |
| protected: |
| void onDraw(Renderer* renderer) override |
| { |
| TestingWindow::Get()->endFrame(nullptr); |
| gpu::RenderContext* renderContext = |
| TestingWindow::Get()->renderContext(); |
| if (!renderContext) |
| { |
| TestingWindow::Get()->beginFrame({.clearColor = 0xffff0000}); |
| } |
| else |
| { |
| TestingWindow::Get()->beginFrame({.clearColor = 0xff000000}); |
| RiveRenderPaint paint; |
| paint.color(0xffffffff); |
| DrawUniquePtr draw( |
| renderContext->make<PushRetrofittedTrianglesGMDraw>( |
| renderContext, |
| &paint)); |
| bool success RIVE_MAYBE_UNUSED = renderContext->pushDraws(&draw, 1); |
| assert(success); |
| } |
| } |
| }; |
| |
| GMREGISTER(retrofittedcubictriangles, return new RetrofittedCubicTrianglesGM;) |