From 235ed47d9d01fd5b9206454223617e8f01968dfc Mon Sep 17 00:00:00 2001 From: Sergii Liebodkin Date: Sat, 28 Jun 2025 18:28:59 +0300 Subject: [PATCH] common: share stroke dasher MOve stroke dasher to the common code space to create an abillity to use it on the cross API renderers (wg and gl) stroke dasher is a path-to-path operation, same as path trim, so can be placed to the common space --- src/renderer/gl_engine/tvgGlGeometry.cpp | 16 +- src/renderer/gl_engine/tvgGlTessellator.cpp | 245 +---------------- src/renderer/gl_engine/tvgGlTessellator.h | 31 +-- src/renderer/tvgRender.cpp | 275 +++++++++++++++++++- src/renderer/tvgRender.h | 2 + 5 files changed, 291 insertions(+), 278 deletions(-) diff --git a/src/renderer/gl_engine/tvgGlGeometry.cpp b/src/renderer/gl_engine/tvgGlGeometry.cpp index 3f1c932a..e56f9d3e 100644 --- a/src/renderer/gl_engine/tvgGlGeometry.cpp +++ b/src/renderer/gl_engine/tvgGlGeometry.cpp @@ -28,18 +28,16 @@ bool GlGeometry::tesselate(const RenderShape& rshape, RenderUpdateFlag flag) { - const RenderPath* path = nullptr; - RenderPath trimmedPath; - if (rshape.trimpath()) { - if (!rshape.stroke->trim.trim(rshape.path, trimmedPath)) return true; - path = &trimmedPath; - } else path = &rshape.path; - if (flag & (RenderUpdateFlag::Color | RenderUpdateFlag::Gradient | RenderUpdateFlag::Transform | RenderUpdateFlag::Path)) { fill.clear(); BWTessellator bwTess{&fill}; - bwTess.tessellate(*path, matrix); + if (rshape.trimpath()) { + RenderPath trimmedPath; + if (rshape.stroke->trim.trim(rshape.path, trimmedPath)) bwTess.tessellate(trimmedPath, matrix); + else return true; + } else bwTess.tessellate(rshape.path, matrix); + fillRule = rshape.rule; bounds = bwTess.bounds(); } @@ -48,7 +46,7 @@ bool GlGeometry::tesselate(const RenderShape& rshape, RenderUpdateFlag flag) stroke.clear(); Stroker stroker{&stroke, matrix}; - stroker.stroke(&rshape, *path); + stroker.stroke(&rshape); bounds = stroker.bounds(); } diff --git a/src/renderer/gl_engine/tvgGlTessellator.cpp b/src/renderer/gl_engine/tvgGlTessellator.cpp index 459c7cf8..ad7357ae 100644 --- a/src/renderer/gl_engine/tvgGlTessellator.cpp +++ b/src/renderer/gl_engine/tvgGlTessellator.cpp @@ -39,7 +39,7 @@ Stroker::Stroker(GlGeometryBuffer* buffer, const Matrix& matrix) : mBuffer(buffe } -void Stroker::stroke(const RenderShape *rshape, const RenderPath& path) +void Stroker::stroke(const RenderShape *rshape) { mMiterLimit = rshape->strokeMiterlimit(); mStrokeCap = rshape->strokeCap(); @@ -52,9 +52,12 @@ void Stroker::stroke(const RenderShape *rshape, const RenderPath& path) mStrokeWidth = strokeWidth / mMatrix.e11; } - auto& dash = rshape->stroke->dash; - if (dash.length < DASH_PATTERN_THRESHOLD) doStroke(path); - else doDashStroke(path, dash.pattern, dash.count, dash.offset, dash.length); + RenderPath dashed; + if (rshape->strokeDash(dashed)) doStroke(dashed); + else if (rshape->trimpath()) { + RenderPath trimmedPath; + if (rshape->stroke->trim.trim(rshape->path, trimmedPath)) doStroke(trimmedPath); + } else doStroke(rshape->path); } @@ -109,19 +112,6 @@ void Stroker::doStroke(const RenderPath& path) } -void Stroker::doDashStroke(const RenderPath& path, const float *patterns, uint32_t patternCnt, float offset, float length) -{ - RenderPath dpath; - - dpath.cmds.reserve(20 * path.cmds.count); - dpath.pts.reserve(20 * path.pts.count); - - DashStroke dash(&dpath.cmds, &dpath.pts, patterns, patternCnt, offset, length); - dash.doStroke(path, mStrokeCap != StrokeCap::Butt); - doStroke(dpath); -} - - void Stroker::strokeCap() { if (mStrokeCap == StrokeCap::Butt) return; @@ -442,227 +432,6 @@ void Stroker::strokeRound(const Point& p, const Point& outDir) } -DashStroke::DashStroke(Array *cmds, Array *pts, const float *patterns, uint32_t patternCnt, float offset, float length) - : mCmds(cmds), - mPts(pts), - mDashPattern(patterns), - mDashCount(patternCnt), - mDashOffset(offset), - mDashLength(length) -{ -} - - -void DashStroke::doStroke(const RenderPath& path, bool validPoint) -{ - //validPoint: zero length segment with non-butt cap still should be rendered as a point - only the caps are visible - int32_t idx = 0; - auto offset = mDashOffset; - bool gap = false; - if (!tvg::zero(mDashOffset)) { - auto length = (mDashCount % 2) ? mDashLength * 2 : mDashLength; - offset = fmodf(offset, length); - if (offset < 0) offset += length; - - for (uint32_t i = 0; i < mDashCount * (mDashCount % 2 + 1); ++i, ++idx) { - auto curPattern = mDashPattern[i % mDashCount]; - if (offset < curPattern) break; - offset -= curPattern; - gap = !gap; - } - idx = idx % mDashCount; - } - - auto pts = path.pts.data; - ARRAY_FOREACH(cmd, path.cmds) { - switch (*cmd) { - case PathCommand::Close: { - this->dashLineTo(mPtStart, validPoint); - break; - } - case PathCommand::MoveTo: { - // reset the dash state - mCurrIdx = idx; - mCurrLen = mDashPattern[idx] - offset; - mCurOpGap = gap; - mMove = true; - mPtStart = mPtCur = *pts; - pts++; - break; - } - case PathCommand::LineTo: { - this->dashLineTo(*pts, validPoint); - pts++; - break; - } - case PathCommand::CubicTo: { - this->dashCubicTo(pts[0], pts[1], pts[2], validPoint); - pts += 3; - break; - } - default: break; - } - } -} - - -void DashStroke::drawPoint(const Point& p) -{ - if (mMove || mDashPattern[mCurrIdx] < FLOAT_EPSILON) { - this->moveTo(p); - mMove = false; - } - this->lineTo(p); -} - - -void DashStroke::dashLineTo(const Point& to, bool validPoint) -{ - auto len = length(mPtCur - to); - - if (tvg::zero(len)) { - this->moveTo(mPtCur); - } else if (len <= mCurrLen) { - mCurrLen -= len; - if (!mCurOpGap) { - if (mMove) { - this->moveTo(mPtCur); - mMove = false; - } - this->lineTo(to); - } - } else { - Line curr = {mPtCur, to}; - - while (len - mCurrLen > DASH_PATTERN_THRESHOLD) { - Line right; - if (mCurrLen > 0.0f) { - Line left; - curr.split(mCurrLen, left, right); - len -= mCurrLen; - if (!mCurOpGap) { - if (mMove || mDashPattern[mCurrIdx] - mCurrLen < FLOAT_EPSILON) { - this->moveTo(left.pt1); - mMove = false; - } - this->lineTo(left.pt2); - } - } else { - if (validPoint && !mCurOpGap) drawPoint(curr.pt1); - right = curr; - } - mCurrIdx = (mCurrIdx + 1) % mDashCount; - mCurrLen = mDashPattern[mCurrIdx]; - mCurOpGap = !mCurOpGap; - curr = right; - mPtCur = curr.pt1; - mMove = true; - } - mCurrLen -= len; - if (!mCurOpGap) { - if (mMove) { - this->moveTo(curr.pt1); - mMove = false; - } - this->lineTo(curr.pt2); - } - - if (mCurrLen < 0.1f) { - mCurrIdx = (mCurrIdx + 1) % mDashCount; - mCurrLen = mDashPattern[mCurrIdx]; - mCurOpGap = !mCurOpGap; - } - } - - mPtCur = to; -} - - -void DashStroke::dashCubicTo(const Point& cnt1, const Point& cnt2, const Point& end, bool validPoint) -{ - Bezier cur{ mPtCur, cnt1, cnt2, end }; - - auto len = cur.length(); - - if (tvg::zero(len)) { - this->moveTo(mPtCur); - } else if (len <= mCurrLen) { - mCurrLen -= len; - if (!mCurOpGap) { - if (mMove) { - this->moveTo(mPtCur); - mMove = false; - } - this->cubicTo(cnt1, cnt2, end); - } - } else { - while (len - mCurrLen > DASH_PATTERN_THRESHOLD) { - Bezier right; - if (mCurrLen > 0.0f) { - Bezier left; - cur.split(mCurrLen, left, right); - len -= mCurrLen; - if (!mCurOpGap) { - if (mMove || mDashPattern[mCurrIdx] - mCurrLen < FLOAT_EPSILON) { - this->moveTo(left.start); - mMove = false; - } - this->cubicTo(left.ctrl1, left.ctrl2, left.end); - } - } else { - if (validPoint && !mCurOpGap) drawPoint(cur.start); - right = cur; - } - mCurrIdx = (mCurrIdx + 1) % mDashCount; - mCurrLen = mDashPattern[mCurrIdx]; - mCurOpGap = !mCurOpGap; - cur = right; - mPtCur = cur.start; - mMove = true; - } - - mCurrLen -= len; - if (!mCurOpGap) { - if (mMove) { - this->moveTo(cur.start); - mMove = false; - } - this->cubicTo(cur.ctrl1, cur.ctrl2, cur.end); - } - - if (mCurrLen < 0.1f) { - mCurrIdx = (mCurrIdx + 1) % mDashCount; - mCurrLen = mDashPattern[mCurrIdx]; - mCurOpGap = !mCurOpGap; - } - } - mPtCur = end; -} - - -void DashStroke::moveTo(const Point& pt) -{ - mPts->push(pt); - mCmds->push(PathCommand::MoveTo); -} - - -void DashStroke::lineTo(const Point& pt) -{ - mPts->push(pt); - mCmds->push(PathCommand::LineTo); -} - - -void DashStroke::cubicTo(const Point& cnt1, const Point& cnt2, const Point& end) -{ - mPts->push(cnt1); - mPts->push(cnt2); - mPts->push(end); - mCmds->push(PathCommand::CubicTo); -} - - BWTessellator::BWTessellator(GlGeometryBuffer* buffer): mBuffer(buffer) { } diff --git a/src/renderer/gl_engine/tvgGlTessellator.h b/src/renderer/gl_engine/tvgGlTessellator.h index 85472281..7e2d96f5 100644 --- a/src/renderer/gl_engine/tvgGlTessellator.h +++ b/src/renderer/gl_engine/tvgGlTessellator.h @@ -39,12 +39,11 @@ class Stroker }; public: Stroker(GlGeometryBuffer* buffer, const Matrix& matrix); - void stroke(const RenderShape *rshape, const RenderPath& path); + void stroke(const RenderShape *rshape); RenderRegion bounds() const; private: void doStroke(const RenderPath& path); - void doDashStroke(const RenderPath& path, const float* patterns, uint32_t patternCnt, float offset, float length); float strokeRadius() const { @@ -75,34 +74,6 @@ private: Point mRightBottom = {0.0f, 0.0f}; }; -class DashStroke -{ -public: - DashStroke(Array* cmds, Array* pts, const float* patterns, uint32_t patternCnt, float offset, float length); - void doStroke(const RenderPath& path, bool drawPoint); - -private: - void drawPoint(const Point& p); - void dashLineTo(const Point& pt, bool drawPoint); - void dashCubicTo(const Point& pt1, const Point& pt2, const Point& pt3, bool drawPoint); - void moveTo(const Point& pt); - void lineTo(const Point& pt); - void cubicTo(const Point& pt1, const Point& pt2, const Point& pt3); - - Array* mCmds; - Array* mPts; - const float* mDashPattern; - uint32_t mDashCount; - float mDashOffset; - float mDashLength; - float mCurrLen = 0.0f; - int32_t mCurrIdx = 0; - bool mCurOpGap = false; - bool mMove = true; - Point mPtStart = {}; - Point mPtCur = {}; -}; - class BWTessellator { public: diff --git a/src/renderer/tvgRender.cpp b/src/renderer/tvgRender.cpp index 221d0000..e8271795 100644 --- a/src/renderer/tvgRender.cpp +++ b/src/renderer/tvgRender.cpp @@ -590,4 +590,277 @@ bool RenderTrimPath::trim(const RenderPath& in, RenderPath& out) const } return out.pts.count >= 2; -} \ No newline at end of file +} + +/************************************************************************/ +/* RenderDashPath Class Implementation */ +/************************************************************************/ + +class RenderDashPath +{ +public: + RenderDashPath(Array* cmds, Array* pts, const float* patterns, uint32_t patternCnt, float offset, float length); + void doStroke(const RenderPath& path, bool drawPoint); + +private: + void drawPoint(const Point& p); + void dashLineTo(const Point& pt, bool drawPoint); + void dashCubicTo(const Point& pt1, const Point& pt2, const Point& pt3, bool drawPoint); + void moveTo(const Point& pt); + void lineTo(const Point& pt); + void cubicTo(const Point& pt1, const Point& pt2, const Point& pt3); + + Array* mCmds; + Array* mPts; + const float* mDashPattern; + uint32_t mDashCount; + float mDashOffset; + float mDashLength; + float mCurrLen = 0.0f; + int32_t mCurrIdx = 0; + bool mCurOpGap = false; + bool mMove = true; + Point mPtStart = {}; + Point mPtCur = {}; +}; + + +RenderDashPath::RenderDashPath(Array *cmds, Array *pts, const float *patterns, uint32_t patternCnt, float offset, float length) + : mCmds(cmds), + mPts(pts), + mDashPattern(patterns), + mDashCount(patternCnt), + mDashOffset(offset), + mDashLength(length) +{ +} + + +void RenderDashPath::doStroke(const RenderPath& path, bool validPoint) +{ + //validPoint: zero length segment with non-butt cap still should be rendered as a point - only the caps are visible + int32_t idx = 0; + auto offset = mDashOffset; + bool gap = false; + if (!tvg::zero(mDashOffset)) { + auto length = (mDashCount % 2) ? mDashLength * 2 : mDashLength; + offset = fmodf(offset, length); + if (offset < 0) offset += length; + + for (uint32_t i = 0; i < mDashCount * (mDashCount % 2 + 1); ++i, ++idx) { + auto curPattern = mDashPattern[i % mDashCount]; + if (offset < curPattern) break; + offset -= curPattern; + gap = !gap; + } + idx = idx % mDashCount; + } + + auto pts = path.pts.data; + ARRAY_FOREACH(cmd, path.cmds) { + switch (*cmd) { + case PathCommand::Close: { + this->dashLineTo(mPtStart, validPoint); + break; + } + case PathCommand::MoveTo: { + // reset the dash state + mCurrIdx = idx; + mCurrLen = mDashPattern[idx] - offset; + mCurOpGap = gap; + mMove = true; + mPtStart = mPtCur = *pts; + pts++; + break; + } + case PathCommand::LineTo: { + this->dashLineTo(*pts, validPoint); + pts++; + break; + } + case PathCommand::CubicTo: { + this->dashCubicTo(pts[0], pts[1], pts[2], validPoint); + pts += 3; + break; + } + default: break; + } + } +} + + +void RenderDashPath::drawPoint(const Point& p) +{ + if (mMove || mDashPattern[mCurrIdx] < FLOAT_EPSILON) { + this->moveTo(p); + mMove = false; + } + this->lineTo(p); +} + + +void RenderDashPath::dashLineTo(const Point& to, bool validPoint) +{ + auto len = length(mPtCur - to); + + if (tvg::zero(len)) { + this->moveTo(mPtCur); + } else if (len <= mCurrLen) { + mCurrLen -= len; + if (!mCurOpGap) { + if (mMove) { + this->moveTo(mPtCur); + mMove = false; + } + this->lineTo(to); + } + } else { + Line curr = {mPtCur, to}; + + while (len - mCurrLen > DASH_PATTERN_THRESHOLD) { + Line right; + if (mCurrLen > 0.0f) { + Line left; + curr.split(mCurrLen, left, right); + len -= mCurrLen; + if (!mCurOpGap) { + if (mMove || mDashPattern[mCurrIdx] - mCurrLen < FLOAT_EPSILON) { + this->moveTo(left.pt1); + mMove = false; + } + this->lineTo(left.pt2); + } + } else { + if (validPoint && !mCurOpGap) drawPoint(curr.pt1); + right = curr; + } + mCurrIdx = (mCurrIdx + 1) % mDashCount; + mCurrLen = mDashPattern[mCurrIdx]; + mCurOpGap = !mCurOpGap; + curr = right; + mPtCur = curr.pt1; + mMove = true; + } + mCurrLen -= len; + if (!mCurOpGap) { + if (mMove) { + this->moveTo(curr.pt1); + mMove = false; + } + this->lineTo(curr.pt2); + } + + if (mCurrLen < 0.1f) { + mCurrIdx = (mCurrIdx + 1) % mDashCount; + mCurrLen = mDashPattern[mCurrIdx]; + mCurOpGap = !mCurOpGap; + } + } + + mPtCur = to; +} + + +void RenderDashPath::dashCubicTo(const Point& cnt1, const Point& cnt2, const Point& end, bool validPoint) +{ + Bezier cur{ mPtCur, cnt1, cnt2, end }; + + auto len = cur.length(); + + if (tvg::zero(len)) { + this->moveTo(mPtCur); + } else if (len <= mCurrLen) { + mCurrLen -= len; + if (!mCurOpGap) { + if (mMove) { + this->moveTo(mPtCur); + mMove = false; + } + this->cubicTo(cnt1, cnt2, end); + } + } else { + while (len - mCurrLen > DASH_PATTERN_THRESHOLD) { + Bezier right; + if (mCurrLen > 0.0f) { + Bezier left; + cur.split(mCurrLen, left, right); + len -= mCurrLen; + if (!mCurOpGap) { + if (mMove || mDashPattern[mCurrIdx] - mCurrLen < FLOAT_EPSILON) { + this->moveTo(left.start); + mMove = false; + } + this->cubicTo(left.ctrl1, left.ctrl2, left.end); + } + } else { + if (validPoint && !mCurOpGap) drawPoint(cur.start); + right = cur; + } + mCurrIdx = (mCurrIdx + 1) % mDashCount; + mCurrLen = mDashPattern[mCurrIdx]; + mCurOpGap = !mCurOpGap; + cur = right; + mPtCur = cur.start; + mMove = true; + } + + mCurrLen -= len; + if (!mCurOpGap) { + if (mMove) { + this->moveTo(cur.start); + mMove = false; + } + this->cubicTo(cur.ctrl1, cur.ctrl2, cur.end); + } + + if (mCurrLen < 0.1f) { + mCurrIdx = (mCurrIdx + 1) % mDashCount; + mCurrLen = mDashPattern[mCurrIdx]; + mCurOpGap = !mCurOpGap; + } + } + mPtCur = end; +} + + +void RenderDashPath::moveTo(const Point& pt) +{ + mPts->push(pt); + mCmds->push(PathCommand::MoveTo); +} + + +void RenderDashPath::lineTo(const Point& pt) +{ + mPts->push(pt); + mCmds->push(PathCommand::LineTo); +} + + +void RenderDashPath::cubicTo(const Point& cnt1, const Point& cnt2, const Point& end) +{ + mPts->push(cnt1); + mPts->push(cnt2); + mPts->push(end); + mCmds->push(PathCommand::CubicTo); +} + + +bool RenderShape::strokeDash(RenderPath& out) const +{ + if (!stroke) return false; + if (!stroke->dash.pattern) return false; + if (stroke->dash.count == 0) return false; + if (stroke->dash.length < DASH_PATTERN_THRESHOLD) return false; + + out.cmds.reserve(20 * path.cmds.count); + out.pts.reserve(20 * path.pts.count); + + RenderDashPath dash(&out.cmds, &out.pts, stroke->dash.pattern, stroke->dash.count, stroke->dash.offset, stroke->dash.length); + if (trimpath()) { + RenderPath trimmedPath; + if (stroke->trim.trim(path, trimmedPath)) dash.doStroke(trimmedPath, stroke->cap != StrokeCap::Butt); + else return false; + } else dash.doStroke(path, stroke->cap != StrokeCap::Butt); + return true; +} diff --git a/src/renderer/tvgRender.h b/src/renderer/tvgRender.h index 5f28bd9a..b360c6f0 100644 --- a/src/renderer/tvgRender.h +++ b/src/renderer/tvgRender.h @@ -364,6 +364,8 @@ struct RenderShape return stroke->dash.count; } + bool strokeDash(RenderPath& out) const; + StrokeCap strokeCap() const { if (!stroke) return StrokeCap::Square;