mirror of
https://github.com/rive-app/rive-cpp.git
synced 2026-01-18 21:21:17 +01:00
* Add profiling macros and optick profiler * Update premake5_optick.lua * Added more profiling markers * Remove microprofiler option for now as not implemented * Added more markers ro rendering code * Update Optick build premake only for windows builds * More build file changes for optick with windows * Change how optick is built on windows * Update premake5_v2.lua * Update build files to use with_optick option * Added ScopeName macro to appease ASAN * Push to rekick jobs * Delete ProfilerMacros.h * Create profiler_macros.h * Update path_fiddle.cpp * Update fiddle_context.hpp * Update for rename of header * More header changes * Update fiddle_context.hpp * Update profiler_macros.h * Update for clang format * Update fiddle_context.hpp * Update profiler_macros.h * Update profiler_macros.h * Changed premake values on Chris' comments * Removed multiple define fro RIVE_OPTICK and now in rive_build_config.lua * Added Optick URL and Version to rive_build_config Co-authored-by: John White <aliasbinman@gmail.com>
642 lines
21 KiB
C++
642 lines
21 KiB
C++
/*
|
|
* Copyright 2022 Rive
|
|
*/
|
|
|
|
#include "rive/renderer/rive_renderer.hpp"
|
|
|
|
#include "rive_render_paint.hpp"
|
|
#include "rive_render_path.hpp"
|
|
#include "rive/math/math_types.hpp"
|
|
#include "rive/math/simd.hpp"
|
|
#include "rive/renderer/rive_render_image.hpp"
|
|
#include "rive/profiler/profiler_macros.h"
|
|
|
|
namespace rive
|
|
{
|
|
bool RiveRenderer::IsAABB(const RawPath& path, AABB* result)
|
|
{
|
|
RIVE_PROF_SCOPE()
|
|
// Any quadrilateral begins with a move plus 3 lines.
|
|
constexpr static size_t kAABBVerbCount = 4;
|
|
constexpr static PathVerb aabbVerbs[kAABBVerbCount] = {PathVerb::move,
|
|
PathVerb::line,
|
|
PathVerb::line,
|
|
PathVerb::line};
|
|
Span<const PathVerb> verbs = path.verbs();
|
|
if (verbs.count() < kAABBVerbCount ||
|
|
memcmp(verbs.data(), aabbVerbs, sizeof(aabbVerbs)) != 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Only accept extra verbs and points if every point after the quadrilateral
|
|
// is equal to p0.
|
|
Span<const Vec2D> pts = path.points();
|
|
for (size_t i = 4; i < pts.count(); ++i)
|
|
{
|
|
if (pts[i] != pts[0])
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// We have a quadrilateral! Now check if it is an axis-aligned rectangle.
|
|
float4 corners = {pts[0].x, pts[0].y, pts[2].x, pts[2].y};
|
|
float4 oppositeCorners = {pts[1].x, pts[1].y, pts[3].x, pts[3].y};
|
|
if (simd::all(corners == oppositeCorners.zyxw) ||
|
|
simd::all(corners == oppositeCorners.xwzy))
|
|
{
|
|
float4 r = simd::join(simd::min(corners.xy, corners.zw),
|
|
simd::max(corners.xy, corners.zw));
|
|
simd::store(result, r);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
RiveRenderer::ClipElement::ClipElement(const Mat2D& matrix_,
|
|
const RiveRenderPath* path_,
|
|
FillRule fillRule_)
|
|
{
|
|
reset(matrix_, path_, fillRule_);
|
|
}
|
|
|
|
RiveRenderer::ClipElement::~ClipElement() {}
|
|
|
|
void RiveRenderer::ClipElement::reset(const Mat2D& matrix_,
|
|
const RiveRenderPath* path_,
|
|
FillRule fillRule_)
|
|
{
|
|
matrix = matrix_;
|
|
rawPathMutationID = path_->getRawPathMutationID();
|
|
pathBounds = path_->getBounds();
|
|
path = ref_rcp(path_);
|
|
fillRule = fillRule_;
|
|
clipID = 0; // This gets initialized lazily.
|
|
}
|
|
|
|
bool RiveRenderer::ClipElement::isEquivalent(const Mat2D& matrix_,
|
|
const RiveRenderPath* path_) const
|
|
{
|
|
return matrix_ == matrix &&
|
|
path_->getRawPathMutationID() == rawPathMutationID &&
|
|
path_->getFillRule() == fillRule;
|
|
}
|
|
|
|
RiveRenderer::RiveRenderer(gpu::RenderContext* context) : m_context(context) {}
|
|
|
|
RiveRenderer::~RiveRenderer() {}
|
|
|
|
void RiveRenderer::save()
|
|
{
|
|
// Copy the back of the stack before pushing, in case the vector grows and
|
|
// invalidates the reference.
|
|
RenderState copy = m_stack.back();
|
|
m_stack.push_back(copy);
|
|
}
|
|
|
|
void RiveRenderer::restore()
|
|
{
|
|
assert(m_stack.size() > 1);
|
|
assert(m_stack.back().clipStackHeight >=
|
|
m_stack[m_stack.size() - 2].clipStackHeight);
|
|
m_stack.pop_back();
|
|
}
|
|
|
|
void RiveRenderer::transform(const Mat2D& matrix)
|
|
{
|
|
m_stack.back().matrix = m_stack.back().matrix * matrix;
|
|
}
|
|
|
|
void RiveRenderer::drawPath(RenderPath* renderPath, RenderPaint* renderPaint)
|
|
{
|
|
RIVE_PROF_SCOPE()
|
|
LITE_RTTI_CAST_OR_RETURN(path, RiveRenderPath*, renderPath);
|
|
LITE_RTTI_CAST_OR_RETURN(paint, RiveRenderPaint*, renderPaint);
|
|
|
|
if (path->getRawPath().empty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (paint->getIsStroked() && m_context->frameDescriptor().strokesDisabled)
|
|
{
|
|
return;
|
|
}
|
|
if (!paint->getIsStroked() && m_context->frameDescriptor().fillsDisabled)
|
|
{
|
|
return;
|
|
}
|
|
if (paint->getIsStroked() &&
|
|
// Use inverse logic to ensure we abort when stroke thickness is NaN.
|
|
!(paint->getThickness() > 0))
|
|
{
|
|
return;
|
|
}
|
|
// Use inverse logic to ensure we abort when stroke thickness is NaN.
|
|
if (!(paint->getFeather() >= 0))
|
|
{
|
|
return;
|
|
}
|
|
if (m_stack.back().clipIsEmpty)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (paint->getFeather() != 0 && !paint->getIsStroked())
|
|
{
|
|
if (path->getFillRule() != FillRule::clockwise &&
|
|
!m_context->frameDescriptor().clockwiseFillOverride)
|
|
{
|
|
// Don't draw feathered fills that aren't clockwise.
|
|
return;
|
|
}
|
|
float matrixMaxScale = m_stack.back().matrix.findMaxScale();
|
|
if (paint->getFeather() * matrixMaxScale > 1)
|
|
{
|
|
clipAndPushDraw(gpu::PathDraw::Make(
|
|
m_context,
|
|
m_stack.back().matrix,
|
|
path->makeSoftenedCopyForFeathering(paint->getFeather(),
|
|
matrixMaxScale),
|
|
path->getFillRule(),
|
|
paint,
|
|
&m_scratchPath));
|
|
return;
|
|
}
|
|
}
|
|
|
|
clipAndPushDraw(gpu::PathDraw::Make(m_context,
|
|
m_stack.back().matrix,
|
|
ref_rcp(path),
|
|
path->getFillRule(),
|
|
paint,
|
|
&m_scratchPath));
|
|
}
|
|
|
|
void RiveRenderer::clipPath(RenderPath* renderPath)
|
|
{
|
|
RIVE_PROF_SCOPE()
|
|
LITE_RTTI_CAST_OR_RETURN(path, RiveRenderPath*, renderPath);
|
|
|
|
if (m_context->frameInterlockMode() == gpu::InterlockMode::clockwiseAtomic)
|
|
{
|
|
// Just discard clips in clockwiseAtomic mode for now.
|
|
// TODO: Implement clipping in clockwiseAtomic mode.
|
|
return;
|
|
}
|
|
|
|
if (m_stack.back().clipIsEmpty)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (path->getRawPath().empty())
|
|
{
|
|
m_stack.back().clipIsEmpty = true;
|
|
return;
|
|
}
|
|
|
|
// First try to handle axis-aligned rectangles using the "ENABLE_CLIP_RECT"
|
|
// shader feature. Multiple axis-aligned rectangles can be intersected into
|
|
// a single rectangle if their matrices are compatible.
|
|
AABB clipRectCandidate;
|
|
if (m_context->frameSupportsClipRects() &&
|
|
IsAABB(path->getRawPath(), &clipRectCandidate))
|
|
{
|
|
clipRectImpl(clipRectCandidate, path);
|
|
}
|
|
else
|
|
{
|
|
clipPathImpl(path);
|
|
}
|
|
}
|
|
|
|
// Finds a new rect, if such a rect exists, such that:
|
|
//
|
|
// currentMatrix * rect == newMatrix * newRect
|
|
//
|
|
// Returns true if *rect was replaced with newRect.
|
|
static bool transform_rect_to_new_space(AABB* rect,
|
|
const Mat2D& currentMatrix,
|
|
const Mat2D& newMatrix)
|
|
{
|
|
if (currentMatrix == newMatrix)
|
|
{
|
|
return true;
|
|
}
|
|
Mat2D currentToNew;
|
|
if (!newMatrix.invert(¤tToNew))
|
|
{
|
|
return false;
|
|
}
|
|
currentToNew = currentToNew * currentMatrix;
|
|
float maxSkew = fmaxf(fabsf(currentToNew.xy()), fabsf(currentToNew.yx()));
|
|
float maxScale = fmaxf(fabsf(currentToNew.xx()), fabsf(currentToNew.yy()));
|
|
if (maxSkew > math::EPSILON && maxScale > math::EPSILON)
|
|
{
|
|
// Transforming this rect to the new view matrix would turn it into
|
|
// something that isn't a rect.
|
|
return false;
|
|
}
|
|
Vec2D pts[2] = {{rect->left(), rect->top()},
|
|
{rect->right(), rect->bottom()}};
|
|
currentToNew.mapPoints(pts, pts, 2);
|
|
float4 p = simd::load4f(pts);
|
|
float2 topLeft = simd::min(p.xy, p.zw);
|
|
float2 botRight = simd::max(p.xy, p.zw);
|
|
*rect = {topLeft.x, topLeft.y, botRight.x, botRight.y};
|
|
return true;
|
|
}
|
|
|
|
void RiveRenderer::clipRectImpl(AABB rect, const RiveRenderPath* originalPath)
|
|
{
|
|
RIVE_PROF_SCOPE()
|
|
bool hasClipRect = m_stack.back().clipRectInverseMatrix != nullptr;
|
|
if (rect.isEmptyOrNaN())
|
|
{
|
|
m_stack.back().clipIsEmpty = true;
|
|
return;
|
|
}
|
|
|
|
// If there already is a clipRect, we can only accept another one by
|
|
// intersecting it with the existing one. This means the new rect must be
|
|
// axis-aligned with the existing clipRect.
|
|
if (hasClipRect &&
|
|
!transform_rect_to_new_space(&rect,
|
|
m_stack.back().matrix,
|
|
m_stack.back().clipRectMatrix))
|
|
{
|
|
// 'rect' is not axis-aligned with the existing clipRect. Fall back to
|
|
// clipPath.
|
|
clipPathImpl(originalPath);
|
|
return;
|
|
}
|
|
|
|
if (!hasClipRect)
|
|
{
|
|
// There wasn't an existing clipRect. This is the one!
|
|
m_stack.back().clipRect = rect;
|
|
m_stack.back().clipRectMatrix = m_stack.back().matrix;
|
|
}
|
|
else
|
|
{
|
|
// Both rects are in the same space now. Intersect the two
|
|
// geometrically.
|
|
float4 a = simd::load4f(&m_stack.back().clipRect);
|
|
float4 b = simd::load4f(&rect);
|
|
float4 intersection =
|
|
simd::join(simd::max(a.xy, b.xy), simd::min(a.zw, b.zw));
|
|
simd::store(&m_stack.back().clipRect, intersection);
|
|
}
|
|
|
|
m_stack.back().clipRectInverseMatrix =
|
|
m_context->make<gpu::ClipRectInverseMatrix>(
|
|
m_stack.back().clipRectMatrix,
|
|
m_stack.back().clipRect);
|
|
}
|
|
|
|
void RiveRenderer::clipPathImpl(const RiveRenderPath* path)
|
|
{
|
|
RIVE_PROF_SCOPE()
|
|
if (path->getBounds().isEmptyOrNaN())
|
|
{
|
|
m_stack.back().clipIsEmpty = true;
|
|
return;
|
|
}
|
|
// Only write a new clip element if this path isn't already on the stack
|
|
// from before. e.g.:
|
|
//
|
|
// clipPath(samePath);
|
|
// restore();
|
|
// save();
|
|
// clipPath(samePath); // <-- reuse the ClipElement (and clipID!)
|
|
// already in m_clipStack.
|
|
//
|
|
const size_t clipStackHeight = m_stack.back().clipStackHeight;
|
|
assert(m_clipStack.size() >= clipStackHeight);
|
|
if (m_clipStack.size() == clipStackHeight ||
|
|
!m_clipStack[clipStackHeight].isEquivalent(m_stack.back().matrix, path))
|
|
{
|
|
m_clipStack.resize(clipStackHeight);
|
|
m_clipStack.emplace_back(m_stack.back().matrix,
|
|
path,
|
|
path->getFillRule());
|
|
}
|
|
m_stack.back().clipStackHeight = clipStackHeight + 1;
|
|
}
|
|
|
|
void RiveRenderer::drawImage(const RenderImage* renderImage,
|
|
ImageSampler imageSampler,
|
|
BlendMode blendMode,
|
|
float opacity)
|
|
{
|
|
RIVE_PROF_SCOPE()
|
|
LITE_RTTI_CAST_OR_RETURN(image, const RiveRenderImage*, renderImage);
|
|
|
|
rcp<gpu::Texture> imageTexture = image->refTexture();
|
|
if (imageTexture == nullptr)
|
|
{
|
|
// imageTexture may be null if the backend uses a custom factory and/or
|
|
// updates out-of-band assets asynchronously. If there's no texture yet,
|
|
// just don't draw anything.
|
|
return;
|
|
}
|
|
|
|
// Scale the view matrix so we can draw this image as the rect [0, 0, 1, 1].
|
|
save();
|
|
scale(image->width(), image->height());
|
|
|
|
if (!m_context->frameSupportsImagePaintForPaths())
|
|
{
|
|
// Fall back on ImageRectDraw if the current frame doesn't support
|
|
// drawing paths with image paints.
|
|
if (!m_stack.back().clipIsEmpty)
|
|
{
|
|
const Mat2D& m = m_stack.back().matrix;
|
|
clipAndPushDraw(
|
|
gpu::DrawUniquePtr(m_context->make<gpu::ImageRectDraw>(
|
|
m_context,
|
|
m.mapBoundingBox(AABB{0, 0, 1, 1}).roundOut(),
|
|
m,
|
|
blendMode,
|
|
std::move(imageTexture),
|
|
imageSampler,
|
|
opacity)));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Implement drawImage() as drawPath() with a rectangular path and an
|
|
// image paint.
|
|
if (m_unitRectPath == nullptr)
|
|
{
|
|
m_unitRectPath = make_rcp<RiveRenderPath>();
|
|
m_unitRectPath->line({1, 0});
|
|
m_unitRectPath->line({1, 1});
|
|
m_unitRectPath->line({0, 1});
|
|
}
|
|
|
|
RiveRenderPaint paint;
|
|
paint.image(std::move(imageTexture), opacity);
|
|
paint.blendMode(blendMode);
|
|
paint.imageSampler(imageSampler);
|
|
drawPath(m_unitRectPath.get(), &paint);
|
|
}
|
|
|
|
restore();
|
|
}
|
|
|
|
void RiveRenderer::drawImageMesh(const RenderImage* renderImage,
|
|
ImageSampler imageSampler,
|
|
rcp<RenderBuffer> vertices_f32,
|
|
rcp<RenderBuffer> uvCoords_f32,
|
|
rcp<RenderBuffer> indices_u16,
|
|
uint32_t vertexCount,
|
|
uint32_t indexCount,
|
|
BlendMode blendMode,
|
|
float opacity)
|
|
{
|
|
RIVE_PROF_SCOPE()
|
|
LITE_RTTI_CAST_OR_RETURN(image, const RiveRenderImage*, renderImage);
|
|
|
|
rcp<gpu::Texture> imageTexture = image->refTexture();
|
|
if (imageTexture == nullptr)
|
|
{
|
|
// imageTexture may be null if the backend uses a custom factory and/or
|
|
// updates out-of-band assets asynchronously. If there's no texture yet,
|
|
// just don't draw anything.
|
|
return;
|
|
}
|
|
|
|
assert(vertices_f32);
|
|
assert(uvCoords_f32);
|
|
assert(indices_u16);
|
|
|
|
if (m_stack.back().clipIsEmpty)
|
|
{
|
|
return;
|
|
}
|
|
|
|
clipAndPushDraw(gpu::DrawUniquePtr(
|
|
m_context->make<gpu::ImageMeshDraw>(gpu::Draw::FULLSCREEN_PIXEL_BOUNDS,
|
|
m_stack.back().matrix,
|
|
blendMode,
|
|
std::move(imageTexture),
|
|
imageSampler,
|
|
std::move(vertices_f32),
|
|
std::move(uvCoords_f32),
|
|
std::move(indices_u16),
|
|
indexCount,
|
|
opacity)));
|
|
}
|
|
|
|
void RiveRenderer::clipAndPushDraw(gpu::DrawUniquePtr draw)
|
|
{
|
|
RIVE_PROF_SCOPE()
|
|
assert(!m_stack.back().clipIsEmpty);
|
|
if (draw.get() == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
if (m_context->isOutsideCurrentFrame(draw->pixelBounds()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Make two attempts to issue the draw: once on the context as-is and once
|
|
// with a clean flush.
|
|
for (int i = 0; i < 2; ++i)
|
|
{
|
|
// Always make sure we begin this loop with the internal draw batch
|
|
// empty, and clear it when we're done.
|
|
struct AutoResetInternalDrawBatch
|
|
{
|
|
public:
|
|
AutoResetInternalDrawBatch(RiveRenderer* renderer) :
|
|
m_renderer(renderer)
|
|
{
|
|
assert(m_renderer->m_internalDrawBatch.empty());
|
|
}
|
|
~AutoResetInternalDrawBatch()
|
|
{
|
|
m_renderer->m_internalDrawBatch.clear();
|
|
}
|
|
|
|
private:
|
|
RiveRenderer* m_renderer;
|
|
};
|
|
|
|
AutoResetInternalDrawBatch aridb(this);
|
|
|
|
auto applyClipResult = applyClip(draw.get());
|
|
if (applyClipResult == ApplyClipResult::failure)
|
|
{
|
|
// There wasn't room in the GPU buffers for this path draw. Flush
|
|
// and try again.
|
|
m_context->logicalFlush();
|
|
continue;
|
|
}
|
|
else if (applyClipResult == ApplyClipResult::clipEmpty)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_internalDrawBatch.push_back(std::move(draw));
|
|
if (!m_context->pushDraws(m_internalDrawBatch.data(),
|
|
m_internalDrawBatch.size()))
|
|
{
|
|
// There wasn't room in the GPU buffers for this path draw. Flush
|
|
// and try again.
|
|
m_context->logicalFlush();
|
|
// Reclaim "draw" because we will use it again on the next
|
|
// iteration.
|
|
draw = std::move(m_internalDrawBatch.back());
|
|
assert(draw != nullptr);
|
|
m_internalDrawBatch.pop_back();
|
|
continue;
|
|
}
|
|
|
|
// Success!
|
|
return;
|
|
}
|
|
|
|
// We failed to process the draw. Release its refs.
|
|
fprintf(stderr,
|
|
"RiveRenderer::clipAndPushDraw failed. The draw and/or clip stack "
|
|
"are too complex.\n");
|
|
}
|
|
|
|
RiveRenderer::ApplyClipResult RiveRenderer::applyClip(gpu::Draw* draw)
|
|
{
|
|
RIVE_PROF_SCOPE()
|
|
if (m_stack.back().clipIsEmpty)
|
|
{
|
|
return ApplyClipResult::clipEmpty;
|
|
}
|
|
draw->setClipRect(m_stack.back().clipRectInverseMatrix);
|
|
|
|
const size_t clipStackHeight = m_stack.back().clipStackHeight;
|
|
if (clipStackHeight == 0)
|
|
{
|
|
assert(draw->clipID() == 0);
|
|
return ApplyClipResult::success;
|
|
}
|
|
|
|
// Find which clip element in the stack (if any) is currently rendered to
|
|
// the clip buffer.
|
|
size_t clipIdxCurrentlyInClipBuffer = -1; // i.e., "none".
|
|
if (m_context->getClipContentID() != 0)
|
|
{
|
|
for (size_t i = clipStackHeight - 1; i != -1; --i)
|
|
{
|
|
if (m_clipStack[i].clipID == m_context->getClipContentID())
|
|
{
|
|
clipIdxCurrentlyInClipBuffer = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw the necessary updates to the clip buffer (i.e., draw every clip
|
|
// element after clipIdxCurrentlyInClipBuffer).
|
|
uint32_t lastClipID =
|
|
clipIdxCurrentlyInClipBuffer == -1
|
|
? 0 // The next clip to be drawn is not nested.
|
|
: m_clipStack[clipIdxCurrentlyInClipBuffer].clipID;
|
|
if (m_context->frameInterlockMode() == gpu::InterlockMode::msaa)
|
|
{
|
|
if (lastClipID == 0 && m_context->getClipContentID() != 0)
|
|
{
|
|
// Time for a new stencil clip! Erase the clip currently in the
|
|
// stencil buffer before we draw the new one.
|
|
auto stencilClipClear =
|
|
gpu::DrawUniquePtr(m_context->make<gpu::StencilClipReset>(
|
|
m_context,
|
|
m_context->getClipContentID(),
|
|
gpu::DrawContents::none,
|
|
gpu::StencilClipReset::ResetAction::clearPreviousClip));
|
|
if (!m_context->isOutsideCurrentFrame(
|
|
stencilClipClear->pixelBounds()))
|
|
{
|
|
m_internalDrawBatch.push_back(std::move(stencilClipClear));
|
|
}
|
|
}
|
|
}
|
|
|
|
for (size_t i = clipIdxCurrentlyInClipBuffer + 1; i < clipStackHeight; ++i)
|
|
{
|
|
ClipElement& clip = m_clipStack[i];
|
|
assert(clip.pathBounds == clip.path->getBounds());
|
|
|
|
IAABB clipDrawBounds;
|
|
RiveRenderPaint clipUpdatePaint;
|
|
clipUpdatePaint.clipUpdate(/*clip THIS clipDraw against:*/ lastClipID);
|
|
|
|
gpu::DrawUniquePtr clipDraw = gpu::PathDraw::Make(m_context,
|
|
clip.matrix,
|
|
clip.path,
|
|
clip.fillRule,
|
|
&clipUpdatePaint,
|
|
&m_scratchPath);
|
|
|
|
if (clipDraw == nullptr)
|
|
{
|
|
return ApplyClipResult::clipEmpty;
|
|
}
|
|
clipDrawBounds = clipDraw->pixelBounds();
|
|
|
|
// Generate a new clipID every time we (re-)render an element to the
|
|
// clip buffer. (Each embodiment of the element needs its own
|
|
// separate readBounds.)
|
|
clip.clipID = m_context->generateClipID(clipDrawBounds);
|
|
assert(clip.clipID != m_context->getClipContentID());
|
|
if (clip.clipID == 0)
|
|
{
|
|
return ApplyClipResult::failure; // The context is out of
|
|
// clipIDs. We will flush and
|
|
// try again.
|
|
}
|
|
clipDraw->setClipID(clip.clipID);
|
|
|
|
gpu::DrawContents clipDrawContents = clipDraw->drawContents();
|
|
if (!m_context->isOutsideCurrentFrame(clipDrawBounds))
|
|
{
|
|
m_internalDrawBatch.push_back(std::move(clipDraw));
|
|
}
|
|
|
|
if (lastClipID != 0)
|
|
{
|
|
m_context->addClipReadBounds(lastClipID, clipDrawBounds);
|
|
if (m_context->frameInterlockMode() == gpu::InterlockMode::msaa)
|
|
{
|
|
// When drawing nested stencil clips, we need to intersect them,
|
|
// which involves erasing the region of the current clip in the
|
|
// stencil buffer that is outside the the one we just drew.
|
|
auto stencilClipIntersect =
|
|
gpu::DrawUniquePtr(m_context->make<gpu::StencilClipReset>(
|
|
m_context,
|
|
lastClipID,
|
|
clipDrawContents,
|
|
gpu::StencilClipReset::ResetAction::
|
|
intersectPreviousClip));
|
|
if (!m_context->isOutsideCurrentFrame(
|
|
stencilClipIntersect->pixelBounds()))
|
|
{
|
|
m_internalDrawBatch.push_back(
|
|
std::move(stencilClipIntersect));
|
|
}
|
|
}
|
|
}
|
|
|
|
lastClipID = clip.clipID; // Nest the next clip (if any) inside the one
|
|
// we just rendered.
|
|
}
|
|
assert(lastClipID == m_clipStack[clipStackHeight - 1].clipID);
|
|
draw->setClipID(lastClipID);
|
|
m_context->addClipReadBounds(lastClipID, draw->pixelBounds());
|
|
m_context->setClipContentID(lastClipID);
|
|
return ApplyClipResult::success;
|
|
}
|
|
} // namespace rive
|