wg_engine: geometry generating optimization

Streaming model for massive vertex and index creations: minimize memory allocations, range checks and other conditions
Reduce number of segments length calculations (sqrt) and bbox (min and max).
Update distances and bboxes on a whole buffer and only if necessary. For shapes without strokes compute distances not necessary at all. bbox can be updated only on the final stage of geometry workflow, but not on the each stage.
Using stack memory instead of heap. its more cache friendly and did not fragment memory, faster memory allocations (weak place of realization)
Using cache for points distances and whole path length. Updates only if necessary
Validation of geometry consistency executes only on the final stage of path life cicle. It more friendly for data streaming: no any conditions and branches.
Using binary search for strokes trimming
Pre-cached circles geometry for caps and joints
Refactored strokes elements generation functions. Code is more readable and modifiable in general. Can be easily fixed if some geometry issues will be finded
This commit is contained in:
Sergii Liebodkin 2024-09-23 18:22:30 +00:00 committed by Jinny You
parent 06441437c0
commit ee6a7214d4
7 changed files with 545 additions and 701 deletions

View file

@ -12,7 +12,6 @@ source_file = [
'tvgWgBindGroups.cpp',
'tvgWgCommon.cpp',
'tvgWgCompositor.cpp',
'tvgWgGeometry.cpp',
'tvgWgPipelines.cpp',
'tvgWgRenderData.cpp',
'tvgWgRenderer.cpp',

View file

@ -48,9 +48,9 @@ void WgCompositor::initialize(WgContext& context, uint32_t width, uint32_t heigh
storageInterm.initialize(context, width, height);
storageDstCopy.initialize(context, width, height);
// composition and blend geometries
WgGeometryData geometryData;
geometryData.appendBlitBox();
meshData.update(context, &geometryData);
WgVertexBufferInd vertexBuffer;
vertexBuffer.appendBlitBox();
meshData.update(context, vertexBuffer);
}

View file

@ -1,436 +0,0 @@
/*
* Copyright (c) 2023 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "tvgWgGeometry.h"
//***********************************************************************
// WgPolyline
//***********************************************************************
WgMath* WgGeometryData::gMath = nullptr;
void WgMath::initialize()
{
if (initialized) return;
initialized = true;
constexpr uint32_t nPoints = 360;
sinus.reserve(nPoints);
cosin.reserve(nPoints);
for (uint32_t i = 0; i < nPoints; i++) {
float angle = i * (2 * MATH_PI) / nPoints;
sinus.push(sin(angle));
cosin.push(cos(angle));
}
};
void WgMath::release() {
sinus.clear();
cosin.clear();
};
//***********************************************************************
// WgPolyline
//***********************************************************************
WgPolyline::WgPolyline()
{
constexpr uint32_t nPoints = 360;
pts.reserve(nPoints);
dist.reserve(nPoints);
closed = false;
}
void WgPolyline::appendPoint(WgPoint pt)
{
if (pts.count > 0) {
float distance = pts.last().dist(pt);
// adjust precision because of real user data points
// can be further than the accepted accuracy,
// but still be considered tha same
if (!tvg::zero(distance*1e-1)) {
// update min and max indexes
iminx = pts[iminx].x >= pt.x ? pts.count : iminx;
imaxx = pts[imaxx].x <= pt.x ? pts.count : imaxx;
iminy = pts[iminy].y >= pt.y ? pts.count : iminy;
imaxy = pts[imaxy].y <= pt.y ? pts.count : imaxy;
// update total length
len += distance;
// update points and distances
pts.push(pt);
dist.push(distance);
};
} else {
// reset min and max indexes and total length
iminx = imaxx = iminy = imaxy = 0;
len = 0.0f;
// update points and distances
pts.push(pt);
dist.push(0.0f);
}
}
void WgPolyline::appendCubic(WgPoint p1, WgPoint p2, WgPoint p3, size_t nsegs)
{
WgPoint p0 = pts.count > 0 ? pts.last() : WgPoint(0.0f, 0.0f);
nsegs = nsegs == 0 ? 1 : nsegs;
float dt = 1.0f / (float)nsegs;
for (auto t = 0.0f; t <= 1.0f; t += dt) {
// get cubic spline interpolation coefficients
float t0 = 1.0f * (1.0f - t) * (1.0f - t) * (1.0f - t);
float t1 = 3.0f * (1.0f - t) * (1.0f - t) * t;
float t2 = 3.0f * (1.0f - t) * t * t;
float t3 = 1.0f * t * t * t;
appendPoint(p0 * t0 + p1 * t1 + p2 * t2 + p3 * t3);
}
}
void WgPolyline::trim(WgPolyline* polyline, float trimBegin, float trimEnd) const
{
assert(polyline);
float begLen = len * trimBegin;
float endLen = len * trimEnd;
float currentLength = 0.0f;
// find start point
uint32_t indexStart = 0;
WgPoint pointStart{};
currentLength = 0.0f;
for (indexStart = 1; indexStart < pts.count; indexStart++) {
currentLength += dist[indexStart];
if(currentLength >= begLen) {
float t = 1.0f - (currentLength - begLen) / dist[indexStart];
pointStart = pts[indexStart-1] * (1.0f - t) + pts[indexStart] * t;
break;
}
}
if (indexStart >= pts.count) return;
// find end point
uint32_t indexEnd = 0;
WgPoint pointEnd{};
currentLength = 0.0f;
for (indexEnd = 1; indexEnd < pts.count; indexEnd++) {
currentLength += dist[indexEnd];
if(currentLength >= endLen) {
float t = 1.0f - (currentLength - endLen) / dist[indexEnd];
pointEnd = pts[indexEnd-1] * (1.0f - t) + pts[indexEnd] * t;
break;
}
}
if (indexEnd >= pts.count) return;
// fill polyline
polyline->appendPoint(pointStart);
for (uint32_t i = indexStart; i <= indexEnd - 1; i++)
polyline->appendPoint(pts[i]);
polyline->appendPoint(pointEnd);
}
void WgPolyline::close()
{
if (pts.count > 0) appendPoint(pts[0]);
closed = true;
}
void WgPolyline::clear()
{
// reset min and max indexes and total length
iminx = imaxx = iminy = imaxy = 0;
len = 0.0f;
// clear points and distances
pts.clear();
dist.clear();
closed = false;
}
void WgPolyline::getBBox(WgPoint& pmin, WgPoint& pmax) const
{
pmin.x = pts[iminx].x;
pmin.y = pts[iminy].y;
pmax.x = pts[imaxx].x;
pmax.y = pts[imaxy].y;
}
//***********************************************************************
// WgGeometryData
//***********************************************************************
WgGeometryData::WgGeometryData() {
constexpr uint32_t nPoints = 10240;
positions.pts.reserve(nPoints);
texCoords.reserve(nPoints);
indexes.reserve(nPoints);
}
void WgGeometryData::clear()
{
indexes.clear();
positions.clear();
texCoords.clear();
}
void WgGeometryData::appendBox(WgPoint pmin, WgPoint pmax)
{
appendRect(
{ pmin.x, pmin.y },
{ pmax.x, pmin.y },
{ pmin.x, pmax.y },
{ pmax.x, pmax.y });
}
void WgGeometryData::appendRect(WgPoint p0, WgPoint p1, WgPoint p2, WgPoint p3)
{
uint32_t index = positions.pts.count;
positions.appendPoint(p0); // +0
positions.appendPoint(p1); // +1
positions.appendPoint(p2); // +2
positions.appendPoint(p3); // +3
indexes.push(index + 0);
indexes.push(index + 1);
indexes.push(index + 2);
indexes.push(index + 1);
indexes.push(index + 3);
indexes.push(index + 2);
}
void WgGeometryData::appendCircle(WgPoint center, float radius, float scale)
{
uint32_t indexCenter = positions.pts.count;
positions.appendPoint(center);
uint32_t index = positions.pts.count;
positions.appendPoint({ center.x + gMath->sinus[0] * radius, center.y + gMath->cosin[0] * radius });
uint32_t nPoints = (uint32_t)(scale * radius * 2.0f);
nPoints = nPoints < 8 ? 8 : nPoints;
const uint32_t step = gMath->sinus.count / nPoints;
for (uint32_t i = step; i < gMath->sinus.count; i += step) {
positions.appendPoint({ center.x + gMath->sinus[i] * radius, center.y + gMath->cosin[i] * radius });
indexes.push(index); // prev point
indexes.push(indexCenter); // center point
indexes.push(index + 1); // curr point
index++;
}
positions.appendPoint({ center.x + gMath->sinus[0] * radius, center.y + gMath->cosin[0] * radius });
indexes.push(index); // prev point
indexes.push(indexCenter); // center point
indexes.push(index + 1); // curr point
positions.appendPoint(center);
}
void WgGeometryData::appendImageBox(float w, float h)
{
positions.appendPoint({ 0.0f, 0.0f });
positions.appendPoint({ w , 0.0f });
positions.appendPoint({ w , h });
positions.appendPoint({ 0.0f, h });
texCoords.push({ 0.0f, 0.0f });
texCoords.push({ 1.0f, 0.0f });
texCoords.push({ 1.0f, 1.0f });
texCoords.push({ 0.0f, 1.0f });
indexes.push(0);
indexes.push(1);
indexes.push(2);
indexes.push(0);
indexes.push(2);
indexes.push(3);
}
void WgGeometryData::appendBlitBox()
{
positions.appendPoint({ -1.0f, +1.0f });
positions.appendPoint({ +1.0f, +1.0f });
positions.appendPoint({ +1.0f, -1.0f });
positions.appendPoint({ -1.0f, -1.0f });
texCoords.push({ 0.0f, 0.0f });
texCoords.push({ 1.0f, 0.0f });
texCoords.push({ 1.0f, 1.0f });
texCoords.push({ 0.0f, 1.0f });
indexes.push(0);
indexes.push(1);
indexes.push(2);
indexes.push(0);
indexes.push(2);
indexes.push(3);
}
void WgGeometryData::appendStrokeDashed(const WgPolyline* polyline, const RenderStroke *stroke, float scale)
{
assert(stroke);
assert(polyline);
static WgPolyline dashed;
dashed.clear();
// ignore single points polyline
// append multiple points dashed polyline
if (polyline->pts.count >= 2) {
auto& pts = polyline->pts;
auto& dist = polyline->dist;
// starting state
uint32_t dashIndex = 0;
float currentLength = stroke->dashPattern[dashIndex];
// iterate by polyline points
for (uint32_t i = 0; i < pts.count - 1; i++) {
// append current polyline point
if (dashIndex % 2 == 0)
dashed.appendPoint(pts[i]);
// move inside polyline segment
while(currentLength < dist[i+1]) {
// get current point
float t = currentLength / dist[i+1];
dashed.appendPoint(pts[i] + (pts[i+1] - pts[i]) * t);
// update current state
dashIndex = (dashIndex + 1) % stroke->dashCnt;
currentLength += stroke->dashPattern[dashIndex];
// append stroke if dash
if (dashIndex % 2 != 0) {
appendStroke(&dashed, stroke, scale);
dashed.clear();
}
}
// update current subline length
currentLength -= dist[i+1];
}
// draw last subline
if (dashIndex % 2 == 0) {
dashed.appendPoint(pts.last());
appendStroke(&dashed, stroke, scale);
dashed.clear();
}
}
}
void WgGeometryData::appendStrokeJoin(const WgPoint& v0, const WgPoint& v1, const WgPoint& v2, StrokeJoin join, float halfWidth, float miterLimit, float scale)
{
WgPoint dir0 = (v1 - v0).normal();
WgPoint dir1 = (v2 - v1).normal();
WgPoint nrm0 { +dir0.y, -dir0.x };
WgPoint nrm1 { +dir1.y, -dir1.x };
WgPoint offset0 = nrm0 * halfWidth;
WgPoint offset1 = nrm1 * halfWidth;
if (join == StrokeJoin::Round) {
appendCircle(v1, halfWidth, scale);
} else if (join == StrokeJoin::Bevel) {
appendRect(v1 - offset0, v1 + offset1, v1 - offset1, v1 + offset0);
} else if (join == StrokeJoin::Miter) {
WgPoint nrm = (nrm0 + nrm1);
// adjust precision because dot product could return above 1 that results acos returns Nan
if (!tvg::zero((dir0.x * dir1.y - dir0.y * dir1.x)*1e-1)) {
nrm.normalize();
float cosine = nrm.dot(nrm0);
float angle = std::acos(dir0.dot(dir1.negative()));
float miterRatio = 1.0f / (std::sin(angle) * 0.5);
if (miterRatio <= miterLimit) {
appendRect(v1 + nrm * (halfWidth / cosine), v1 + offset0, v1 + offset1, v1);
appendRect(v1 - nrm * (halfWidth / cosine), v1 - offset0, v1 - offset1, v1);
} else {
appendRect(v1 - offset0, v1 + offset1, v1 - offset1, v1 + offset0);
}
}
}
}
void WgGeometryData::appendStroke(const WgPolyline* polyline, const RenderStroke *stroke, float scale)
{
assert(stroke);
assert(polyline);
float wdt = stroke->width * 0.5f;
// single line sub-path
if (polyline->pts.count == 2) {
WgPoint v0 = polyline->pts[0];
WgPoint v1 = polyline->pts[1];
WgPoint dir0 = (v1 - v0) / polyline->dist[1];
WgPoint nrm0 = WgPoint{ -dir0.y, +dir0.x };
if (stroke->cap == StrokeCap::Round) {
appendRect(v0 - nrm0 * wdt, v0 + nrm0 * wdt, v1 - nrm0 * wdt, v1 + nrm0 * wdt);
appendCircle(polyline->pts[0], wdt, scale);
appendCircle(polyline->pts[1], wdt, scale);
} else if (stroke->cap == StrokeCap::Butt) {
appendRect(v0 - nrm0 * wdt, v0 + nrm0 * wdt, v1 - nrm0 * wdt, v1 + nrm0 * wdt);
} else if (stroke->cap == StrokeCap::Square) {
appendRect(
v0 - nrm0 * wdt - dir0 * wdt, v0 + nrm0 * wdt - dir0 * wdt,
v1 - nrm0 * wdt + dir0 * wdt, v1 + nrm0 * wdt + dir0 * wdt
);
}
} else if (polyline->pts.count > 2) { // multi-lined sub-path
if (polyline->closed) {
WgPoint v0 = polyline->pts[polyline->pts.count - 2];
WgPoint v1 = polyline->pts[0];
WgPoint v2 = polyline->pts[1];
appendStrokeJoin(v0, v1, v2, stroke->join, wdt, stroke->miterlimit, scale);
} else {
// append first cap
WgPoint v0 = polyline->pts[0];
WgPoint v1 = polyline->pts[1];
WgPoint dir0 = (v1 - v0) / polyline->dist[1];
WgPoint nrm0 = WgPoint{ -dir0.y, +dir0.x };
if (stroke->cap == StrokeCap::Round) {
appendCircle(v0, wdt, scale);
} else if (stroke->cap == StrokeCap::Butt) {
// no cap needed
} else if (stroke->cap == StrokeCap::Square) {
appendRect(v0 - nrm0 * wdt - dir0 * wdt, v0 + nrm0 * wdt - dir0 * wdt, v0 - nrm0 * wdt, v0 + nrm0 * wdt);
}
// append last cap
v0 = polyline->pts[polyline->pts.count - 2];
v1 = polyline->pts[polyline->pts.count - 1];
dir0 = (v1 - v0) / polyline->dist[polyline->pts.count - 1];
nrm0 = WgPoint{ -dir0.y, +dir0.x };
if (stroke->cap == StrokeCap::Round) {
appendCircle(v1, wdt, scale);
} else if (stroke->cap == StrokeCap::Butt) {
// no cap needed
} else if (stroke->cap == StrokeCap::Square) {
appendRect(v1 - nrm0 * wdt, v1 + nrm0 * wdt, v1 - nrm0 * wdt + dir0 * wdt, v1 + nrm0 * wdt + dir0 * wdt);
}
}
// append sub-lines
for (uint32_t i = 0; i < polyline->pts.count - 1; i++) {
float dist1 = polyline->dist[i + 1];
WgPoint v1 = polyline->pts[i + 0];
WgPoint v2 = polyline->pts[i + 1];
WgPoint dir1 = (v2 - v1) / dist1;
WgPoint nrm1 { +dir1.y, -dir1.x };
WgPoint offset1 = nrm1 * wdt;
appendRect(v1 - offset1, v1 + offset1, v2 - offset1, v2 + offset1);
if (i > 0) {
WgPoint v0 = polyline->pts[i - 1];
appendStrokeJoin(v0, v1, v2, stroke->join, wdt, stroke->miterlimit, scale);
}
}
}
}

View file

@ -24,102 +24,434 @@
#define _TVG_WG_GEOMETRY_H_
#include <cassert>
#include <functional>
#include "tvgMath.h"
#include "tvgRender.h"
#include "tvgArray.h"
class WgPoint
{
public:
float x;
float y;
// base vector operations
static Point operator-(const Point& a) { return {-a.x, -a.y}; }
static inline float length2(const Point& a) { return a.x*a.x+a.y*a.y; };
static inline float distance2(const Point& a, const Point& b) { return length2(a - b); };
static inline float distance(const Point& a, const Point& b) { return length(a - b); };
static inline float dot(const Point& a, const Point& b) { return a.x*b.x + a.y*b.y; };
static inline Point min(const Point& a, const Point& b) { return { std::min(a.x, b.x), std::min(a.y, b.y) }; };
static inline Point max(const Point& a, const Point& b) { return { std::max(a.x, b.x), std::max(a.y, b.y) }; };
static inline Point lerp(const Point& a, const Point& b, float t) { return a * (1.0f - t) + b * t; };
static inline Point normalize(const Point& a) { float rlen = 1.0f / length(a); return { a.x * rlen, a.y * rlen }; }
WgPoint() {}
WgPoint(float x, float y): x(x), y(y) {}
WgPoint(const Point& p): x(p.x), y(p.y) {}
// default size of vertex and index buffers
#define WG_POINTS_COUNT 16384
WgPoint operator + (const WgPoint& p) const { return { x + p.x, y + p.y }; }
WgPoint operator - (const WgPoint& p) const { return { x - p.x, y - p.y }; }
WgPoint operator * (const WgPoint& p) const { return { x * p.x, y * p.y }; }
WgPoint operator / (const WgPoint& p) const { return { x / p.x, y / p.y }; }
WgPoint operator + (const float c) const { return { x + c, y + c }; }
WgPoint operator - (const float c) const { return { x - c, y - c }; }
WgPoint operator * (const float c) const { return { x * c, y * c }; }
WgPoint operator / (const float c) const { return { x / c, y / c }; }
WgPoint negative() const { return {-x, -y}; }
inline void negate() { x = -x; y = -y; }
inline float length() const { return sqrt(x*x + y*y); }
inline float length2() const { return x*x + y*y; }
inline float dot(const WgPoint& p) const { return x * p.x + y * p.y; }
inline float dist(const WgPoint& p) const { return sqrt(dist2(p)); }
inline float dist2(const WgPoint& p) const { return ((p.x - x)*(p.x - x) + (p.y - y)*(p.y - y)); }
inline bool equal(const WgPoint& p) const { return tvg::equal(x, p.x) && tvg::equal(y, p.y); }
inline void normalize() { float rlen = 1.0f / length(); x *= rlen; y *= rlen; }
inline WgPoint normal() const { float rlen = 1.0f / length(); return { x * rlen, y * rlen }; }
inline WgPoint lerp(const WgPoint& p, float t) const { return { x + (p.x - x) * t, y + (p.y - y) * t }; };
inline WgPoint trans(const Matrix& m) const { return { x * m.e11 + y * m.e12 + m.e13, x * m.e21 + y * m.e22 + m.e23 }; };
};
struct WgMath
{
Array<float> sinus;
Array<float> cosin;
bool initialized{};
void initialize();
void release();
};
struct WgPolyline
{
Array<WgPoint> pts;
Array<float> dist;
// polyline bbox points indexes
uint32_t iminx{};
uint32_t iminy{};
uint32_t imaxx{};
uint32_t imaxy{};
// total polyline length
float len{};
// simple vertex buffer
struct WgVertexBuffer {
Point vbuff[WG_POINTS_COUNT]; // vertex buffer
float vdist[WG_POINTS_COUNT]; // distance to previous point
float vleng[WG_POINTS_COUNT]; // distance to the first point through all previous points
size_t vcount{};
bool closed{};
WgPolyline();
// callback for external process of polyline
using onPolylineFn = std::function<void(const WgVertexBuffer& buff)>;
void appendPoint(WgPoint pt);
void appendCubic(WgPoint p1, WgPoint p2, WgPoint p3, size_t nsegs = 16);
// reset buffer
void reset() {
vcount = 0;
closed = false;
}
void trim(WgPolyline* polyline, float trimBegin, float trimEnd) const;
// get the last point with optional index offset from the end
Point last(size_t offset = 0) const {
return vbuff[vcount - offset - 1];
}
void close();
void clear();
// get the last distance with optional index offset from the end
float lastDist(size_t offset = 0) const {
return vdist[vcount - offset - 1];
}
void getBBox(WgPoint& pmin, WgPoint& pmax) const;
// get total length
float total() const {
return (vcount == 0) ? 0.0f : vleng[vcount-1];
}
// get next vertex index by length using binary search
size_t getIndexByLength(float len) const {
if (vcount <= 1) return 0;
size_t left = 0;
size_t right = vcount - 1;
while (left <= right) {
size_t mid = left + (right - left) / 2;
if (vleng[mid] == len) return mid;
else if (vleng[mid] < len) left = mid + 1;
else right = mid - 1;
}
return right + 1;
}
// get min and max values of the buffer
void getMinMax(Point& pmin, Point& pmax) const {
if (vcount == 0) return;
pmax = pmin = vbuff[0];
for (size_t i = 1; i < vcount; i++) {
pmin = min(pmin, vbuff[i]);
pmax = max(pmax, vbuff[i]);
}
}
// update points distancess to the prev point and total length
void updateDistances() {
if (vcount == 0) return;
vdist[0] = 0.0f;
vleng[0] = 0.0f;
for (size_t i = 1; i < vcount; i++) {
vdist[i] = distance(vbuff[i-1], vbuff[i]);
vleng[i] = vleng[i-1] + vdist[i];
}
}
// close vertex buffer
void close() {
// check if last point is not to close to the first point
if (!tvg::zero(distance2(vbuff[0], last())))
append(vbuff[0]);
closed = true;
}
// append point
void append(const Point& p) {
vbuff[vcount] = p;
vcount++;
}
// append source vertex buffer in index range from start to end (end not included)
void appendRange(const WgVertexBuffer& buff, size_t start_index, size_t end_index) {
if (start_index <= end_index)
for (size_t i = start_index; i < end_index; i++)
append(buff.vbuff[i]);
if (start_index > end_index) {
for (size_t i = start_index; i < buff.vcount; i++)
append(buff.vbuff[i]);
for (size_t i = 0; i < end_index; i++)
append(buff.vbuff[i]);
}
}
// append circle (list of triangles)
void appendCircle(float radius) {
// get approx circle length
float clen = 2.0f * radius * MATH_PI;
size_t nsegs = std::max((uint32_t)(clen / 8), 16U);
// append circle
Point prev { std::sin(0.0f) * radius, std::cos(0.0f) * radius };
for (size_t i = 1; i <= nsegs; i++) {
float t = (2.0f * MATH_PI * i) / nsegs;
Point curr { std::sin(t) * radius, std::cos(t) * radius };
append(Point{0.0f, 0.0f});
append(prev);
append(curr);
prev = curr;
}
}
// append cubic spline
void appendCubic(const Point& v0, const Point& v1, const Point& v2, const Point& v3) {
// get approx cubic length
float clen = distance(v0, v1) + distance(v1, v2) + distance(v2, v3);
size_t nsegs = std::max((uint32_t)(clen / 8), 16U);
// append cubic
Bezier bezier{v0, v1, v2, v3};
for (size_t i = 1; i <= nsegs; i++)
append(bezier.at((float)i / nsegs));
}
// trim source buffer
void trim(const WgVertexBuffer& buff, float beg, float end) {
// empty buffer guard
if (buff.vcount == 0) return;
// initialize
float len_beg = buff.total() * beg;
float len_end = buff.total() * end;
// find points
size_t index_beg = buff.getIndexByLength(len_beg);
size_t index_end = buff.getIndexByLength(len_end);
float len_total_beg = buff.vleng[index_beg];
float len_total_end = buff.vleng[index_end];
float len_seg_beg = buff.vdist[index_beg];
float len_seg_end = buff.vdist[index_end];
// append points
float t_beg = len_seg_beg > 0.0f ? 1.0f - (len_total_beg - len_beg) / len_seg_beg : 0.0f;
float t_end = len_seg_end > 0.0f ? 1.0f - (len_total_end - len_end) / len_seg_end : 0.0f;
if (index_beg > 0) append(lerp(buff.vbuff[index_beg-1], buff.vbuff[index_beg], t_beg));
appendRange(buff, index_beg, index_end);
if (index_end > 0) append(lerp(buff.vbuff[index_end-1], buff.vbuff[index_end], t_end));
}
// decode path with callback for external prcesses
void decodePath(const RenderShape& rshape, bool update_dist, onPolylineFn onPolyline) {
// decode path
reset();
size_t pntIndex = 0;
for (uint32_t cmdIndex = 0; cmdIndex < rshape.path.cmds.count; cmdIndex++) {
PathCommand cmd = rshape.path.cmds[cmdIndex];
if (cmd == PathCommand::MoveTo) {
// after path decoding we need to update distances and total length
if (update_dist) updateDistances();
if ((onPolyline) && (vcount != 0))
onPolyline(*this);
reset();
append(rshape.path.pts[pntIndex]);
pntIndex++;
} else if (cmd == PathCommand::LineTo) {
append(rshape.path.pts[pntIndex]);
pntIndex++;
} else if (cmd == PathCommand::Close) {
close();
} else if (cmd == PathCommand::CubicTo) {
appendCubic(vbuff[vcount - 1], rshape.path.pts[pntIndex + 0], rshape.path.pts[pntIndex + 1], rshape.path.pts[pntIndex + 2]);
pntIndex += 3;
}
}
// after path decoding we need to update distances and total length
if (update_dist) updateDistances();
if ((onPolyline) && (vcount != 0))
onPolyline(*this);
}
};
// simple indexed vertex buffer
struct WgVertexBufferInd {
Point vbuff[WG_POINTS_COUNT*2];
Point tbuff[WG_POINTS_COUNT*2];
uint32_t ibuff[WG_POINTS_COUNT*4];
size_t vcount = 0;
size_t icount = 0;
struct WgGeometryData
{
static WgMath* gMath;
// reset buffer
void reset() {
icount = vcount = 0;
}
WgPolyline positions{};
Array<WgPoint> texCoords{};
Array<uint32_t> indexes{};
// get min and max values of the buffer
void getMinMax(Point& pmin, Point& pmax) const {
if (vcount == 0) return;
pmax = pmin = vbuff[0];
for (size_t i = 1; i < vcount; i++) {
pmin = min(pmin, vbuff[i]);
pmax = max(pmax, vbuff[i]);
}
}
WgGeometryData();
void clear();
// append image box with tex coords
void appendImageBox(float w, float h) {
Point points[4] { { 0.0f, 0.0f }, { w, 0.0f }, { w, h }, { 0.0f, h } };
appendImageBox(points);
}
void appendBox(WgPoint pmin, WgPoint pmax);
void appendRect(WgPoint p0, WgPoint p1, WgPoint p2, WgPoint p3);
void appendCircle(WgPoint center, float radius, float scale = 1.0f);
void appendImageBox(float w, float h);
void appendBlitBox();
void appendStrokeDashed(const WgPolyline* polyline, const RenderStroke *stroke, float scale);
void appendStrokeJoin(const WgPoint& v0, const WgPoint& v1, const WgPoint& v2,
StrokeJoin join, float halfWidth, float miterLimit, float scale);
void appendStroke(const WgPolyline* polyline, const RenderStroke *stroke, float scale);
// append blit box with tex coords
void appendBlitBox() {
Point points[4] { { -1.0f, +1.0f }, { +1.0f, +1.0f }, { +1.0f, -1.0f }, { -1.0f, -1.0f } };
appendImageBox(points);
}
// append image box with tex coords
void appendImageBox(Point points[4]) {
// append vertexes
vbuff[vcount+0] = points[0];
vbuff[vcount+1] = points[1];
vbuff[vcount+2] = points[2];
vbuff[vcount+3] = points[3];
// append tex coords
tbuff[vcount+0] = { 0.0f, 0.0f };
tbuff[vcount+1] = { 1.0f, 0.0f };
tbuff[vcount+2] = { 1.0f, 1.0f };
tbuff[vcount+3] = { 0.0f, 1.0f };
// append indexes
ibuff[icount+0] = vcount + 0;
ibuff[icount+1] = vcount + 1;
ibuff[icount+2] = vcount + 2;
ibuff[icount+3] = vcount + 0;
ibuff[icount+4] = vcount + 2;
ibuff[icount+5] = vcount + 3;
// update buffer
vcount += 4;
icount += 6;
}
// append quad - two triangles formed from four points
void appendQuad(const Point& p0, const Point& p1, const Point& p2, const Point& p3) {
// append vertexes
vbuff[vcount+0] = p0;
vbuff[vcount+1] = p1;
vbuff[vcount+2] = p2;
vbuff[vcount+3] = p3;
// append indexes
ibuff[icount+0] = vcount + 0;
ibuff[icount+1] = vcount + 1;
ibuff[icount+2] = vcount + 2;
ibuff[icount+3] = vcount + 1;
ibuff[icount+4] = vcount + 3;
ibuff[icount+5] = vcount + 2;
// update buffer
vcount += 4;
icount += 6;
}
// dash buffer by pattern
void appendStrokesDashed(const WgVertexBuffer& buff, const RenderStroke* rstroke) {
// dashed buffer
WgVertexBuffer dashed;
dashed.reset();
// ignore single points polyline
if (buff.vcount < 2) return;
const float* dashPattern = rstroke->dashPattern;
size_t dashCnt = rstroke->dashCnt;
// starting state
uint32_t index_dash = 0;
float len_total = dashPattern[index_dash];
// iterate by polyline points
for (uint32_t i = 0; i < buff.vcount - 1; i++) {
// append current polyline point
if (index_dash % 2 == 0)
dashed.append(buff.vbuff[i]);
// move inside polyline segment
while(len_total < buff.vdist[i+1]) {
// get current point
float t = len_total / buff.vdist[i+1];
dashed.append(buff.vbuff[i] + (buff.vbuff[i+1] - buff.vbuff[i]) * t);
// update current state
index_dash = (index_dash + 1) % dashCnt;
len_total += dashPattern[index_dash];
// preceed stroke if dash
if (index_dash % 2 != 0) {
dashed.updateDistances();
appendStrokes(dashed, rstroke);
dashed.reset();
}
}
// update current subline length
len_total -= buff.vdist[i+1];
}
// draw last subline
if (index_dash % 2 == 0) {
dashed.append(buff.last());
dashed.updateDistances();
appendStrokes(dashed, rstroke);
}
}
// append buffer with optional offset
void appendBuffer(const WgVertexBuffer& buff, Point offset = Point{0.0f, 0.0f}) {
for (uint32_t i = 0; i < buff.vcount; i++ ) {
vbuff[vcount + i] = buff.vbuff[i] + offset;
ibuff[icount + i] = vcount + i;
}
vcount += buff.vcount;
icount += buff.vcount;
};
// append line
void appendLine(const Point& v0, const Point& v1, float dist, float halfWidth) {
Point sub = v1 - v0;
Point nrm = { +sub.y / dist * halfWidth, -sub.x / dist * halfWidth };
appendQuad(v0 - nrm, v0 + nrm, v1 - nrm, v1 + nrm);
}
// append bevel joint
void appendBevel(const Point& v0, const Point& v1, const Point& v2, float dist1, float dist2, float halfWidth) {
Point sub1 = v1 - v0;
Point sub2 = v2 - v1;
Point nrm1 { +sub1.y / dist1 * halfWidth, -sub1.x / dist1 * halfWidth };
Point nrm2 { +sub2.y / dist2 * halfWidth, -sub2.x / dist2 * halfWidth };
appendQuad(v1 - nrm1, v1 + nrm1, v1 - nrm2, v1 + nrm2);
}
// append miter joint
void appendMitter(const Point& v0, const Point& v1, const Point& v2, float dist1, float dist2, float halfWidth, float miterLimit) {
Point sub1 = v1 - v0;
Point sub2 = v2 - v1;
Point nrm1 { +sub1.y / dist1, -sub1.x / dist1 };
Point nrm2 { +sub2.y / dist2, -sub2.x / dist2 };
Point offset1 = nrm1 * halfWidth;
Point offset2 = nrm2 * halfWidth;
Point nrm = normalize(nrm1 + nrm2);
float cosine = dot(nrm, nrm1);
float angle = std::acos(dot(nrm1, -nrm2));
float miterRatio = 1.0f / (std::sin(angle) * 0.5f);
if (miterRatio <= miterLimit) {
appendQuad(v1 + nrm * (halfWidth / cosine), v1 + offset2, v1 + offset1, v1);
appendQuad(v1 - nrm * (halfWidth / cosine), v1 - offset2, v1 - offset1, v1);
} else {
appendQuad(v1 - offset1, v1 + offset2, v1 - offset2, v1 + offset1);
}
}
// append square cap
void appendSquare(Point v0, Point v1, float dist, float halfWidth) {
Point sub = v1 - v0;
Point offset = sub / dist * halfWidth;
Point nrm = { +offset.y, -offset.x };
appendQuad(v1 - nrm, v1 + nrm, v1 + offset - nrm, v1 + offset + nrm);
}
// append strokes
void appendStrokes(const WgVertexBuffer& buff, const RenderStroke* rstroke) {
assert(rstroke);
// empty buffer gueard
if (buff.vcount < 2) return;
float halfWidth = rstroke->width * 0.5f;
// append core lines
for (size_t i = 1; i < buff.vcount; i++)
appendLine(buff.vbuff[i-1], buff.vbuff[i], buff.vdist[i], halfWidth);
// append caps (square)
if ((rstroke->cap == StrokeCap::Square) && !buff.closed) {
appendSquare(buff.vbuff[1], buff.vbuff[0], buff.vdist[1], halfWidth);
appendSquare(buff.last(1), buff.last(0), buff.lastDist(0), halfWidth);
}
// append round joints and caps
if ((rstroke->join == StrokeJoin::Round) || (rstroke->cap == StrokeCap::Round)) {
// create mesh for circle
WgVertexBuffer circle;
circle.appendCircle(halfWidth);
// append caps (round)
if (rstroke->cap == StrokeCap::Round) {
appendBuffer(circle, buff.vbuff[0]);
// append ending cap if polyline is not closed
if (!buff.closed)
appendBuffer(circle, buff.last());
}
// append joints (round)
if (rstroke->join == StrokeJoin::Round) {
for (size_t i = 1; i < buff.vcount - 1; i++)
appendBuffer(circle, buff.vbuff[i]);
if (buff.closed) appendBuffer(circle, buff.last());
}
}
// append closed endings
if (buff.closed) {
// close by bevel
if (rstroke->join == StrokeJoin::Bevel)
appendBevel(buff.last(1), buff.vbuff[0], buff.vbuff[1], buff.lastDist(0), buff.vdist[1], halfWidth);
// close by mitter
else if (rstroke->join == StrokeJoin::Miter) {
appendMitter(buff.last(1), buff.vbuff[0], buff.vbuff[1], buff.lastDist(0), buff.vdist[1], halfWidth, rstroke->miterlimit);
}
}
// append joints (bevel)
if (rstroke->join == StrokeJoin::Bevel) {
for (size_t i = 1; i < buff.vcount - 1; i++)
appendBevel(buff.vbuff[i-1], buff.vbuff[i], buff.vbuff[i+1], buff.vdist[i], buff.vdist[i+1], halfWidth);
// append joints (mitter)
} else if (rstroke->join == StrokeJoin::Miter) {
for (size_t i = 1; i < buff.vcount - 1; i++)
appendMitter(buff.vbuff[i-1], buff.vbuff[i], buff.vbuff[i+1], buff.vdist[i], buff.vdist[i+1], halfWidth, rstroke->miterlimit);
}
}
};
#endif // _TVG_WG_GEOMETRY_H_

View file

@ -55,35 +55,36 @@ void WgMeshData::drawImage(WgContext& context, WGPURenderPassEncoder renderPassE
};
void WgMeshData::update(WgContext& context, const WgPolyline* polyline)
void WgMeshData::update(WgContext& context, const WgVertexBuffer& vertexBuffer)
{
assert(polyline);
assert(polyline->pts.count > 2);
vertexCount = polyline->pts.count;
indexCount = (polyline->pts.count - 2) * 3;
context.allocateBufferVertex(bufferPosition, (float *)&polyline->pts[0], vertexCount * sizeof(float) * 2);
assert(vertexBuffer.vcount > 2);
vertexCount = vertexBuffer.vcount;
indexCount = (vertexBuffer.vcount - 2) * 3;
// buffer position data create and write
context.allocateBufferVertex(bufferPosition, (float *)&vertexBuffer.vbuff, vertexCount * sizeof(float) * 2);
// buffer index data create and write
context.allocateBufferIndexFan(vertexCount);
}
void WgMeshData::update(WgContext& context, const WgGeometryData* geometryData)
void WgMeshData::update(WgContext& context, const WgVertexBufferInd& vertexBufferInd)
{
assert(geometryData);
vertexCount = geometryData->positions.pts.count;
indexCount = geometryData->indexes.count;
assert(vertexBufferInd.vcount > 2);
vertexCount = vertexBufferInd.vcount;
indexCount = vertexBufferInd.icount;
// buffer position data create and write
if (geometryData->positions.pts.count > 0)
context.allocateBufferVertex(bufferPosition, (float *)&geometryData->positions.pts[0], vertexCount * sizeof(float) * 2);
if (vertexCount > 0)
context.allocateBufferVertex(bufferPosition, (float *)&vertexBufferInd.vbuff, vertexCount * sizeof(float) * 2);
// buffer tex coords data create and write
if (geometryData->texCoords.count > 0)
context.allocateBufferVertex(bufferTexCoord, (float *)&geometryData->texCoords[0], vertexCount * sizeof(float) * 2);
if (vertexCount > 0)
context.allocateBufferVertex(bufferTexCoord, (float *)&vertexBufferInd.tbuff, vertexCount * sizeof(float) * 2);
// buffer index data create and write
if (geometryData->indexes.count > 0)
context.allocateBufferIndex(bufferIndex, &geometryData->indexes[0], indexCount * sizeof(uint32_t));
if (indexCount > 0)
context.allocateBufferIndex(bufferIndex, vertexBufferInd.ibuff, indexCount * sizeof(uint32_t));
};
void WgMeshData::update(WgContext& context, const WgPoint pmin, const WgPoint pmax)
void WgMeshData::update(WgContext& context, const Point pmin, const Point pmax)
{
vertexCount = 4;
indexCount = 6;
@ -144,25 +145,23 @@ WgMeshDataPool* WgMeshDataGroup::gMeshDataPool = nullptr;
// WgMeshDataGroup
//***********************************************************************
void WgMeshDataGroup::append(WgContext& context, const WgPolyline* polyline)
void WgMeshDataGroup::append(WgContext& context, const WgVertexBuffer& vertexBuffer)
{
assert(polyline);
assert(polyline->pts.count >= 3);
assert(vertexBuffer.vcount >= 3);
meshes.push(gMeshDataPool->allocate(context));
meshes.last()->update(context, polyline);
meshes.last()->update(context, vertexBuffer);
}
void WgMeshDataGroup::append(WgContext& context, const WgGeometryData* geometryData)
void WgMeshDataGroup::append(WgContext& context, const WgVertexBufferInd& vertexBufferInd)
{
assert(geometryData);
assert(geometryData->positions.pts.count >= 3);
assert(vertexBufferInd.vcount >= 3);
meshes.push(gMeshDataPool->allocate(context));
meshes.last()->update(context, geometryData);
meshes.last()->update(context, vertexBufferInd);
}
void WgMeshDataGroup::append(WgContext& context, const WgPoint pmin, const WgPoint pmax)
void WgMeshDataGroup::append(WgContext& context, const Point pmin, const Point pmax)
{
meshes.push(gMeshDataPool->allocate(context));
meshes.last()->update(context, pmin, pmax);
@ -307,7 +306,29 @@ void WgRenderDataPaint::updateClips(tvg::Array<tvg::RenderData> &clips) {
// WgRenderDataShape
//***********************************************************************
void WgRenderDataShape::updateBBox(WgPoint pmin, WgPoint pmax)
void WgRenderDataShape::appendShape(WgContext context, const WgVertexBuffer& vertexBuffer)
{
if (vertexBuffer.vcount < 3) return;
Point pmin{}, pmax{};
vertexBuffer.getMinMax(pmin, pmax);
meshGroupShapes.append(context, vertexBuffer);
meshGroupShapesBBox.append(context, pmin, pmax);
updateBBox(pmin, pmax);
}
void WgRenderDataShape::appendStroke(WgContext context, const WgVertexBufferInd& vertexBufferInd)
{
if (vertexBufferInd.vcount < 3) return;
Point pmin{}, pmax{};
vertexBufferInd.getMinMax(pmin, pmax);
meshGroupStrokes.append(context, vertexBufferInd);
meshGroupStrokesBBox.append(context, pmin, pmax);
updateBBox(pmin, pmax);
}
void WgRenderDataShape::updateBBox(Point pmin, Point pmax)
{
pMin.x = std::min(pMin.x, pmin.x);
pMin.y = std::min(pMin.y, pmin.y);
@ -316,11 +337,11 @@ void WgRenderDataShape::updateBBox(WgPoint pmin, WgPoint pmax)
}
void WgRenderDataShape::updateAABB(const Matrix& rt) {
WgPoint p0 = WgPoint(pMin.x, pMin.y).trans(rt);
WgPoint p1 = WgPoint(pMax.x, pMin.y).trans(rt);
WgPoint p2 = WgPoint(pMin.x, pMax.y).trans(rt);
WgPoint p3 = WgPoint(pMax.x, pMax.y).trans(rt);
void WgRenderDataShape::updateAABB(const Matrix& tr) {
Point p0 = Point{pMin.x, pMin.y} * tr;
Point p1 = Point{pMax.x, pMin.y} * tr;
Point p2 = Point{pMin.x, pMax.y} * tr;
Point p3 = Point{pMax.x, pMax.y} * tr;
aabb.x = std::min({p0.x, p1.x, p2.x, p3.x});
aabb.y = std::min({p0.y, p1.y, p2.y, p3.y});
aabb.w = std::max({p0.x, p1.x, p2.x, p3.x}) - aabb.x;
@ -333,143 +354,75 @@ void WgRenderDataShape::updateMeshes(WgContext &context, const RenderShape &rsha
releaseMeshes(context);
strokeFirst = rshape.stroke ? rshape.stroke->strokeFirst : false;
float scale = std::max(sqrt(tr.e11*tr.e11 + tr.e21*tr.e21), 1.0f);
Array<WgPolyline*> polylines{};
// decode path
size_t pntIndex = 0;
for (uint32_t cmdIndex = 0; cmdIndex < rshape.path.cmds.count; cmdIndex++) {
PathCommand cmd = rshape.path.cmds[cmdIndex];
if (cmd == PathCommand::MoveTo) {
// proceed current polyline
polylines.push(new WgPolyline);
polylines.last()->appendPoint(rshape.path.pts[pntIndex]);
pntIndex++;
} else if (cmd == PathCommand::LineTo) {
polylines.last()->appendPoint(rshape.path.pts[pntIndex]);
pntIndex++;
} else if (cmd == PathCommand::Close) {
polylines.last()->close();
} else if (cmd == PathCommand::CubicTo) {
assert(polylines.last()->pts.count > 0);
WgPoint pt0 = polylines.last()->pts.last().trans(tr);
WgPoint pt1 = WgPoint(rshape.path.pts[pntIndex + 0]).trans(tr);
WgPoint pt2 = WgPoint(rshape.path.pts[pntIndex + 1]).trans(tr);
WgPoint pt3 = WgPoint(rshape.path.pts[pntIndex + 2]).trans(tr);
uint32_t nsegs = std::max((uint32_t)(pt0.dist(pt1) + pt1.dist(pt2) + pt2.dist(pt3)), 32U);
polylines.last()->appendCubic(
rshape.path.pts[pntIndex + 0],
rshape.path.pts[pntIndex + 1],
rshape.path.pts[pntIndex + 2],
nsegs / 4);
pntIndex += 3;
}
}
// proceed shapes
float totalLen{};
for (uint32_t i = 0; i < polylines.count; i++) {
totalLen += polylines[i]->len;
updateShapes(context, polylines[i]);
}
// proceed strokes
if (rshape.stroke) {
float trimBegin{};
float trimEnd{};
if (!rshape.stroke->strokeTrim(trimBegin, trimEnd)) { trimBegin = 0.0f; trimEnd = 1.0f; }
if (rshape.stroke->trim.simultaneous) {
for (uint32_t i = 0; i < polylines.count; i++)
updateStrokes(context, polylines[i], rshape.stroke, scale, trimBegin, trimEnd);
} else {
if (trimBegin <= trimEnd) {
updateStrokesList(context, polylines, rshape.stroke, totalLen, scale, trimBegin, trimEnd);
} else {
updateStrokesList(context, polylines, rshape.stroke, totalLen, scale, 0.0f, trimEnd);
updateStrokesList(context, polylines, rshape.stroke, totalLen, scale, trimBegin, 1.0f);
}
}
}
// delete polylines
for (uint32_t i = 0; i < polylines.count; i++)
delete polylines[i];
// path decoded vertex buffer
WgVertexBuffer pbuff;
// append shape without strokes
if (!rshape.stroke) {
pbuff.decodePath(rshape, false, [&](const WgVertexBuffer& path_buff) {
appendShape(context, path_buff);
});
// append shape with strokes
} else if (rshape.stroke->trim.simultaneous) {
float tbeg{}, tend{};
if (!rshape.stroke->strokeTrim(tbeg, tend)) { tbeg = 0.0f; tend = 1.0f; }
if (tbeg == tend) return;
pbuff.decodePath(rshape, true, [&](const WgVertexBuffer& path_buff) {
appendShape(context, path_buff);
proceedStrokes(context, rshape.stroke, tbeg, tend, path_buff);
});
// append shape with strokes with simultaneous flag
} else {
float totalLen = 0.0f;
// append shapes
pbuff.decodePath(rshape, true, [&](const WgVertexBuffer& path_buff) {
appendShape(context, path_buff);
totalLen += path_buff.total();
});
// append strokes
float tbeg{}, tend{};
if (!rshape.stroke->strokeTrim(tbeg, tend)) { tbeg = 0.0f; tend = 1.0f; }
if (tbeg == tend) return;
float len_beg = totalLen * tbeg; // trim length begin
float len_end = totalLen * tend; // trim length end
float len_acc = 0.0; // accumulated length
// append strokes
pbuff.decodePath(rshape, true, [&](const WgVertexBuffer& path_buff) {
float len_path = path_buff.total(); // current path length
float tbeg = ((len_acc <= len_beg) && (len_acc + len_path > len_beg)) ? (len_beg - len_acc) / len_path : 0.0f;
float tend = ((len_acc <= len_end) && (len_acc + len_path > len_end)) ? (len_end - len_acc) / len_path : 1.0f;
if ((len_acc + len_path >= len_beg) && (len_acc <= len_end))
proceedStrokes(context, rshape.stroke, tbeg, tend, path_buff);
len_acc += len_path;
});
}
// update shapes bbox
updateAABB(tr);
meshDataBBox.update(context, pMin, pMax);
}
void WgRenderDataShape::updateShapes(WgContext& context, const WgPolyline* polyline)
void WgRenderDataShape::proceedStrokes(WgContext context, const RenderStroke* rstroke, float tbeg, float tend, const WgVertexBuffer& buff)
{
assert(polyline);
// generate fill geometry
if (polyline->pts.count >= 3) {
WgPoint pmin{}, pmax{};
polyline->getBBox(pmin, pmax);
meshGroupShapes.append(context, polyline);
meshGroupShapesBBox.append(context, pmin, pmax);
updateBBox(pmin, pmax);
}
}
void WgRenderDataShape::updateStrokesList(WgContext& context, Array<WgPolyline*> polylines, const RenderStroke* rstroke, float scale, float totalLen, float trimBegin, float trimEnd)
{
float tp1 = totalLen * trimBegin; // trim point begin
float tp2 = totalLen * trimEnd; // trim point end
float pc = 0; // point current
for (uint32_t i = 0; i < polylines.count; i++) {
float pl = polylines[i]->len; // current polyline length
float trimBegin = ((pc <= tp1) && (pc + pl > tp1)) ? (tp1 - pc) / pl : 0.0f;
float trimEnd = ((pc <= tp2) && (pc + pl > tp2)) ? (tp2 - pc) / pl : 1.0f;
if ((pc + pl >= tp1) && (pc <= tp2))
updateStrokes(context, polylines[i], rstroke, scale, trimBegin, trimEnd);
pc += pl;
// break if reached the tail
if (pc > tp2) break;
}
}
void WgRenderDataShape::updateStrokes(WgContext& context, const WgPolyline* polyline, const RenderStroke* rstroke, float scale, float trimBegin, float trimEnd)
{
assert(polyline);
// generate strokes geometry
if ((polyline->pts.count >= 1) && rstroke && (rstroke->width > 0.0f)) {
static WgGeometryData geometryData; geometryData.clear();
static WgPolyline trimmed;
// trim -> split -> stroke
if (trimBegin == trimEnd) return;
if ((rstroke->dashPattern) && ((trimBegin != 0.0f) || (trimEnd != 1.0f))) {
trimmed.clear();
if (trimBegin < trimEnd)
polyline->trim(&trimmed, trimBegin, trimEnd);
else {
polyline->trim(&trimmed, trimBegin, 1.0f);
polyline->trim(&trimmed, 0.0f, trimEnd);
}
geometryData.appendStrokeDashed(&trimmed, rstroke, scale);
} else // trim -> stroke
if ((trimBegin != 0.0f) || (trimEnd != 1.0f)) {
trimmed.clear();
if (trimBegin < trimEnd)
polyline->trim(&trimmed, trimBegin, trimEnd);
else {
polyline->trim(&trimmed, trimBegin, 1.0f);
polyline->trim(&trimmed, 0.0f, trimEnd);
}
geometryData.appendStroke(&trimmed, rstroke, scale);
} else // split -> stroke
if (rstroke->dashPattern) {
geometryData.appendStrokeDashed(polyline, rstroke, scale);
} else { // stroke
geometryData.appendStroke(polyline, rstroke, scale);
}
// append render meshes and bboxes
if(geometryData.positions.pts.count >= 3) {
WgPoint pmin{}, pmax{};
geometryData.positions.getBBox(pmin, pmax);
meshGroupStrokes.append(context, &geometryData);
meshGroupStrokesBBox.append(context, pmin, pmax);
updateBBox(pmin, pmax);
}
}
assert(rstroke);
WgVertexBufferInd stroke_buff;
// trim -> dash -> stroke
if ((tbeg != 0.0f) || (tend != 1.0f)) {
if (tbeg == tend) return;
WgVertexBuffer trimed_buff;
trimed_buff.trim(buff, tbeg, tend);
trimed_buff.updateDistances();
// trim ->dash -> stroke
if (rstroke->dashPattern) stroke_buff.appendStrokesDashed(trimed_buff, rstroke);
// trim -> stroke
else stroke_buff.appendStrokes(trimed_buff, rstroke);
} else
// dash -> stroke
if (rstroke->dashPattern) {
stroke_buff.appendStrokesDashed(buff, rstroke);
// stroke
} else
stroke_buff.appendStrokes(buff, rstroke);
appendStroke(context, stroke_buff);
}

View file

@ -37,9 +37,9 @@ struct WgMeshData {
void drawFan(WgContext& context, WGPURenderPassEncoder renderPassEncoder);
void drawImage(WgContext& context, WGPURenderPassEncoder renderPassEncoder);
void update(WgContext& context, const WgPolyline* polyline);
void update(WgContext& context, const WgGeometryData* geometryData);
void update(WgContext& context, const WgPoint pmin, const WgPoint pmax);
void update(WgContext& context, const WgVertexBuffer& vertexBuffer);
void update(WgContext& context, const WgVertexBufferInd& vertexBufferInd);
void update(WgContext& context, const Point pmin, const Point pmax);
void release(WgContext& context);
};
@ -58,9 +58,9 @@ struct WgMeshDataGroup {
Array<WgMeshData*> meshes{};
void append(WgContext& context, const WgPolyline* polyline);
void append(WgContext& context, const WgGeometryData* geometryData);
void append(WgContext& context, const WgPoint pmin, const WgPoint pmax);
void append(WgContext& context, const WgVertexBuffer& vertexBuffer);
void append(WgContext& context, const WgVertexBufferInd& vertexBufferInd);
void append(WgContext& context, const Point pmin, const Point pmax);
void release(WgContext& context);
};
@ -119,17 +119,17 @@ struct WgRenderDataShape: public WgRenderDataPaint
WgMeshData meshDataBBox{};
WgMeshDataGroup meshGroupStrokes{};
WgMeshDataGroup meshGroupStrokesBBox{};
WgPoint pMin{};
WgPoint pMax{};
Point pMin{};
Point pMax{};
bool strokeFirst{};
FillRule fillRule{};
void updateBBox(WgPoint pmin, WgPoint pmax);
void updateAABB(const Matrix& rt);
void appendShape(WgContext context, const WgVertexBuffer& vertexBuffer);
void appendStroke(WgContext context, const WgVertexBufferInd& vertexBufferInd);
void updateBBox(Point pmin, Point pmax);
void updateAABB(const Matrix& tr);
void updateMeshes(WgContext& context, const RenderShape& rshape, const Matrix& tr);
void updateShapes(WgContext& context, const WgPolyline* polyline);
void updateStrokesList(WgContext& context, Array<WgPolyline*> polylines, const RenderStroke* rstroke, float scale, float totalLen, float trimBegin, float trimEnd);
void updateStrokes(WgContext& context, const WgPolyline* polyline, const RenderStroke* rstroke, float scale, float trimBegin, float trimEnd);
void proceedStrokes(WgContext context, const RenderStroke* rstroke, float tbeg, float tend, const WgVertexBuffer& buff);
void releaseMeshes(WgContext& context);
void release(WgContext& context) override;
Type type() override { return Type::Shape; };

View file

@ -24,8 +24,6 @@
WgRenderer::WgRenderer()
{
WgGeometryData::gMath = new WgMath();
WgGeometryData::gMath->initialize();
}
@ -33,8 +31,6 @@ WgRenderer::~WgRenderer()
{
release();
mContext.release();
WgGeometryData::gMath->release();
delete WgGeometryData::gMath;
}
@ -130,11 +126,11 @@ RenderData WgRenderer::prepare(RenderSurface* surface, RenderData data, const Ma
// update image data
if (flags & (RenderUpdateFlag::Path | RenderUpdateFlag::Image)) {
WgGeometryData geometryData;
geometryData.appendImageBox(surface->w, surface->h);
WgVertexBufferInd vertexBufferInd;
vertexBufferInd.appendImageBox(surface->w, surface->h);
mContext.pipelines->layouts.releaseBindGroup(renderDataPicture->bindGroupPicture);
renderDataPicture->meshData.release(mContext);
renderDataPicture->meshData.update(mContext, &geometryData);
renderDataPicture->meshData.update(mContext, vertexBufferInd);
renderDataPicture->imageData.update(mContext, surface);
renderDataPicture->bindGroupPicture = mContext.pipelines->layouts.createBindGroupTexSampled(
mContext.samplerLinearRepeat, renderDataPicture->imageData.textureView