mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-14 20:14:37 +00:00
wg_engine: update mesh generations and render mechanics
[issues 1479: lottie](#1479) - optimaze stroking algorithm by prevent vector normalizations - using render meshes heaps to reduce webgpu instances re-creations - using single instance of pilylines for standard operations such as trim and split - "on-the-fly" strokes generations with dash pattern - "on-the-fly" mesh objects generation in a process of path decoding - merge strokes into single mesh by each shape
This commit is contained in:
parent
398d181e3f
commit
c778f451b1
7 changed files with 383 additions and 619 deletions
|
@ -32,11 +32,11 @@ void WgMath::initialize()
|
|||
{
|
||||
if (initialized) return;
|
||||
initialized = true;
|
||||
const uint32_t nPoints = 360;
|
||||
sinus.reserve(360);
|
||||
cosin.reserve(360);
|
||||
constexpr uint32_t nPoints = 360;
|
||||
sinus.reserve(nPoints);
|
||||
cosin.reserve(nPoints);
|
||||
for (uint32_t i = 0; i < nPoints; i++) {
|
||||
float angle = i * (2 * M_PI) / nPoints;
|
||||
float angle = i * (2 * MATH_PI) / nPoints;
|
||||
sinus.push(sin(angle));
|
||||
cosin.push(cos(angle));
|
||||
}
|
||||
|
@ -54,8 +54,9 @@ void WgMath::release() {
|
|||
|
||||
WgPolyline::WgPolyline()
|
||||
{
|
||||
pts.reserve(1024);
|
||||
dist.reserve(1014);
|
||||
constexpr uint32_t nPoints = 360;
|
||||
pts.reserve(nPoints);
|
||||
dist.reserve(nPoints);
|
||||
}
|
||||
|
||||
|
||||
|
@ -179,123 +180,78 @@ void WgPolyline::getBBox(WgPoint& pmin, WgPoint& pmax) const
|
|||
// WgGeometryData
|
||||
//***********************************************************************
|
||||
|
||||
void WgGeometryData::computeTriFansIndexes()
|
||||
{
|
||||
if (positions.count <= 2) return;
|
||||
indexes.reserve((positions.count - 2) * 3);
|
||||
for (size_t i = 0; i < positions.count - 2; i++) {
|
||||
indexes.push(0);
|
||||
indexes.push(i + 1);
|
||||
indexes.push(i + 2);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void WgGeometryData::computeContour(WgGeometryData* data)
|
||||
{
|
||||
assert(data);
|
||||
assert(data->positions.count > 1);
|
||||
clear();
|
||||
uint32_t istart = data->getIndexMinX(); // index start
|
||||
uint32_t icnt = data->positions.count; // index count
|
||||
|
||||
uint32_t iprev = istart == 0 ? icnt - 1 : istart - 1;
|
||||
uint32_t inext = (istart + 1) % icnt; // index current
|
||||
WgPoint p0 = data->positions[istart]; // current segment start
|
||||
bool isIntersected = false;
|
||||
bool isClockWise = !isCW(data->positions[iprev], data->positions[istart], data->positions[inext]);
|
||||
|
||||
uint32_t ii{}; // intersected segment index
|
||||
WgPoint pi{}; // intersection point
|
||||
positions.push(p0);
|
||||
while(!((inext == istart) && (!isIntersected))) {
|
||||
if (data->getClosestIntersection(p0, data->positions[inext], pi, ii)) {
|
||||
isIntersected = true;
|
||||
p0 = pi;
|
||||
// operate intersection point
|
||||
if (isClockWise) // clock wise behavior
|
||||
inext = isCW(p0, pi, data->positions[ii]) ? ii : ((ii + 1) % icnt);
|
||||
else // contr-clock wise behavior
|
||||
inext = isCW(p0, pi, data->positions[ii]) ? ((ii + 1) % icnt) : ii;
|
||||
} else { // simple next point
|
||||
isIntersected = false;
|
||||
p0 = data->positions[inext];
|
||||
inext = (inext + 1) % icnt;
|
||||
}
|
||||
positions.push(p0);
|
||||
}
|
||||
WgGeometryData::WgGeometryData() {
|
||||
constexpr uint32_t nPoints = 10240;
|
||||
positions.pts.reserve(nPoints);
|
||||
texCoords.reserve(nPoints);
|
||||
indexes.reserve(nPoints);
|
||||
}
|
||||
|
||||
|
||||
void WgGeometryData::appendCubic(WgPoint p1, WgPoint p2, WgPoint p3)
|
||||
void WgGeometryData::clear()
|
||||
{
|
||||
WgPoint p0 = positions.count > 0 ? positions.last() : WgPoint(0.0f, 0.0f);
|
||||
const size_t segs = 16;
|
||||
for (size_t i = 1; i <= segs; i++) {
|
||||
float t = i / (float)segs;
|
||||
// get cubic spline interpolation coefficients
|
||||
float t0 = 1 * (1.0f - t) * (1.0f - t) * (1.0f - t);
|
||||
float t1 = 3 * (1.0f - t) * (1.0f - t) * t;
|
||||
float t2 = 3 * (1.0f - t) * t * t;
|
||||
float t3 = 1 * t * t * t;
|
||||
positions.push(p0 * t0 + p1 * t1 + p2 * t2 + p3 * t3);
|
||||
}
|
||||
};
|
||||
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});
|
||||
};
|
||||
{ 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.count;
|
||||
positions.push(p0); // +0
|
||||
positions.push(p1); // +1
|
||||
positions.push(p2); // +2
|
||||
positions.push(p3); // +3
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// TODO: optimize vertex and index count
|
||||
void WgGeometryData::appendCircle(WgPoint center, float radius)
|
||||
{
|
||||
uint32_t index = positions.count;
|
||||
uint32_t nSegments = std::trunc(radius);
|
||||
for (uint32_t i = 0; i < nSegments; i++) {
|
||||
float angle0 = (float)(i + 0) / nSegments * MATH_PI * 2.0f;
|
||||
float angle1 = (float)(i + 1) / nSegments * MATH_PI * 2.0f;
|
||||
WgPoint p0 = center + WgPoint(sin(angle0) * radius, cos(angle0) * radius);
|
||||
WgPoint p1 = center + WgPoint(sin(angle1) * radius, cos(angle1) * radius);
|
||||
positions.push(center); // +0
|
||||
positions.push(p0); // +1
|
||||
positions.push(p1); // +2
|
||||
indexes.push(index + 0);
|
||||
indexes.push(index + 1);
|
||||
indexes.push(index + 2);
|
||||
index += 3;
|
||||
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)(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.push({ 0.0f, 0.0f });
|
||||
positions.push({ w , 0.0f });
|
||||
positions.push({ w , h });
|
||||
positions.push({ 0.0f, 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 });
|
||||
|
@ -306,15 +262,15 @@ void WgGeometryData::appendImageBox(float w, float h)
|
|||
indexes.push(0);
|
||||
indexes.push(2);
|
||||
indexes.push(3);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
void WgGeometryData::appendBlitBox()
|
||||
{
|
||||
positions.push({ -1.0f, +1.0f });
|
||||
positions.push({ +1.0f, +1.0f });
|
||||
positions.push({ +1.0f, -1.0f });
|
||||
positions.push({ -1.0f, -1.0f });
|
||||
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 });
|
||||
|
@ -331,13 +287,13 @@ void WgGeometryData::appendBlitBox()
|
|||
void WgGeometryData::appendMesh(const RenderMesh* rmesh)
|
||||
{
|
||||
assert(rmesh);
|
||||
positions.reserve(rmesh->triangleCnt * 3);
|
||||
positions.pts.reserve(rmesh->triangleCnt * 3);
|
||||
texCoords.reserve(rmesh->triangleCnt * 3);
|
||||
indexes.reserve(rmesh->triangleCnt * 3);
|
||||
for (uint32_t i = 0; i < rmesh->triangleCnt; i++) {
|
||||
positions.push(rmesh->triangles[i].vertex[0].pt);
|
||||
positions.push(rmesh->triangles[i].vertex[1].pt);
|
||||
positions.push(rmesh->triangles[i].vertex[2].pt);
|
||||
positions.appendPoint(rmesh->triangles[i].vertex[0].pt);
|
||||
positions.appendPoint(rmesh->triangles[i].vertex[1].pt);
|
||||
positions.appendPoint(rmesh->triangles[i].vertex[2].pt);
|
||||
texCoords.push(rmesh->triangles[i].vertex[0].uv);
|
||||
texCoords.push(rmesh->triangles[i].vertex[1].uv);
|
||||
texCoords.push(rmesh->triangles[i].vertex[2].uv);
|
||||
|
@ -345,440 +301,185 @@ void WgGeometryData::appendMesh(const RenderMesh* rmesh)
|
|||
indexes.push(i*3 + 1);
|
||||
indexes.push(i*3 + 2);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
WgPoint WgGeometryData::interpolate(float t, uint32_t& index)
|
||||
{
|
||||
assert(t >= 0.0f);
|
||||
assert(t <= 1.0f);
|
||||
// get total length
|
||||
float totalLength = 0.0f;
|
||||
for (uint32_t i = 0; i < positions.count - 1; i++)
|
||||
totalLength += positions[i].dist(positions[i+1]);
|
||||
float targetLength = totalLength * t;
|
||||
// find current segment index and interpolation factor
|
||||
float currentFactor = 0.0f;
|
||||
float currentLength = 0.0f;
|
||||
for (index = 0; index < positions.count - 1; index++) {
|
||||
float segmentLength = positions[index].dist(positions[index+1]);
|
||||
currentLength += segmentLength;
|
||||
if(currentLength >= targetLength) {
|
||||
currentFactor = 1.0f -(currentLength - targetLength) / segmentLength;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// get interpolated position and segment index
|
||||
return positions[index] * (1.0f - currentFactor) + positions[index + 1] * currentFactor;
|
||||
}
|
||||
|
||||
|
||||
float WgGeometryData::getLength()
|
||||
{
|
||||
float length = 0.0f;
|
||||
for (uint32_t i = 0; i < positions.count - 1; i++)
|
||||
length += positions[i].dist(positions[i+1]);
|
||||
return length;
|
||||
}
|
||||
|
||||
|
||||
bool WgGeometryData::getClosestIntersection(WgPoint p1, WgPoint p2, WgPoint& pi, uint32_t& index)
|
||||
{
|
||||
bool finded = false;
|
||||
pi = p2;
|
||||
float dist = p1.dist(p2);
|
||||
for (uint32_t i = 0; i < positions.count - 1; i++) {
|
||||
WgPoint p3 = positions[i+0];
|
||||
WgPoint p4 = positions[i+1];
|
||||
float bot = (p1.x - p2.x)*(p3.y - p4.y) - (p1.y - p2.y)*(p3.x - p4.x);
|
||||
if (bot != 0) {
|
||||
float top0 = (p1.x - p3.x)*(p3.y - p4.y) - (p1.y - p3.y)*(p3.x - p4.x);
|
||||
float top1 = (p1.x - p2.x)*(p1.y - p3.y) - (p1.y - p2.y)*(p1.x - p3.x);
|
||||
float t0 = +top0 / bot;
|
||||
float t1 = -top1 / bot;
|
||||
if ((t0 > 0.0f) && (t0 < 1.0f) && (t1 > 0.0f) && (t1 < 1.0f)) {
|
||||
WgPoint p = { p1.x + (p2.x - p1.x) * top0 / bot, p1.y + (p2.y - p1.y) * top0 / bot };
|
||||
float d = p.dist(p1);
|
||||
if (d < dist) {
|
||||
pi = p;
|
||||
index = i;
|
||||
dist = d;
|
||||
finded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return finded;
|
||||
}
|
||||
|
||||
bool WgGeometryData::isCW(WgPoint p1, WgPoint p2, WgPoint p3)
|
||||
{
|
||||
WgPoint v1 = p2 - p1;
|
||||
WgPoint v2 = p3 - p1;
|
||||
return (v1.x*v2.y - v1.y*v2.x) < 0.0;
|
||||
}
|
||||
|
||||
|
||||
uint32_t WgGeometryData::getIndexMinX()
|
||||
{
|
||||
assert(positions.count > 0);
|
||||
uint32_t index = 0;
|
||||
for (uint32_t i = 1; i < positions.count; i++)
|
||||
if (positions[index].x > positions[i].x) index = i;
|
||||
return index;
|
||||
}
|
||||
|
||||
|
||||
uint32_t WgGeometryData::getIndexMaxX()
|
||||
{
|
||||
assert(positions.count > 0);
|
||||
uint32_t index = 0;
|
||||
for (uint32_t i = 1; i < positions.count; i++)
|
||||
if (positions[index].x < positions[i].x) index = i;
|
||||
return index;
|
||||
}
|
||||
|
||||
|
||||
uint32_t WgGeometryData::getIndexMinY()
|
||||
{
|
||||
assert(positions.count > 0);
|
||||
uint32_t index = 0;
|
||||
for (uint32_t i = 1; i < positions.count; i++)
|
||||
if (positions[index].y > positions[i].y) index = i;
|
||||
return index;
|
||||
}
|
||||
|
||||
|
||||
uint32_t WgGeometryData::getIndexMaxY()
|
||||
{
|
||||
assert(positions.count > 0);
|
||||
uint32_t index = 0;
|
||||
for (uint32_t i = 1; i < positions.count; i++)
|
||||
if (positions[index].y < positions[i].y) index = i;
|
||||
return index;
|
||||
}
|
||||
|
||||
|
||||
void WgGeometryData::close()
|
||||
{
|
||||
if (positions.count > 1) {
|
||||
positions.push(positions[0]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void WgGeometryData::clear()
|
||||
{
|
||||
indexes.clear();
|
||||
positions.clear();
|
||||
texCoords.clear();
|
||||
}
|
||||
|
||||
//***********************************************************************
|
||||
// WgGeometryDataGroup
|
||||
//***********************************************************************
|
||||
|
||||
void WgGeometryDataGroup::getBBox(WgPoint& pmin, WgPoint& pmax) {
|
||||
assert(geometries.count > 0);
|
||||
assert(geometries[0]->positions.count > 0);
|
||||
pmin = geometries[0]->positions[0];
|
||||
pmax = geometries[0]->positions[0];
|
||||
for (uint32_t i = 0; i < geometries.count; i++) {
|
||||
for (uint32_t j = 0; j < geometries[i]->positions.count; j++) {
|
||||
pmin.x = std::min(pmin.x, geometries[i]->positions[j].x);
|
||||
pmin.y = std::min(pmin.y, geometries[i]->positions[j].y);
|
||||
pmax.x = std::max(pmax.x, geometries[i]->positions[j].x);
|
||||
pmax.y = std::max(pmax.y, geometries[i]->positions[j].y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void WgGeometryDataGroup::tesselate(const RenderShape& rshape)
|
||||
{
|
||||
// windiwg fill rule
|
||||
if (rshape.rule == FillRule::Winding) {
|
||||
WgGeometryDataGroup polylines{};
|
||||
decodePath(rshape, &polylines);
|
||||
contourPolyline(&polylines, this);
|
||||
} else // even-odd fill rule
|
||||
if (rshape.rule == FillRule::EvenOdd) {
|
||||
decodePath(rshape, this);
|
||||
}
|
||||
// update triangle fans indexes
|
||||
for (uint32_t i = 0; i < geometries.count; i++)
|
||||
geometries[i]->computeTriFansIndexes();
|
||||
}
|
||||
|
||||
|
||||
void WgGeometryDataGroup::stroke(const RenderShape& rshape)
|
||||
{
|
||||
assert(rshape.stroke);
|
||||
auto strokeData = new WgGeometryData();
|
||||
// decode
|
||||
WgGeometryDataGroup polylines{};
|
||||
decodePath(rshape, &polylines);
|
||||
// trim -> split -> stroke
|
||||
if ((rshape.stroke->dashPattern) && ((rshape.stroke->trim.begin != 0.0f) || (rshape.stroke->trim.end != 1.0f))) {
|
||||
WgGeometryDataGroup trimmed{};
|
||||
WgGeometryDataGroup splitted{};
|
||||
trimPolyline(&polylines, &trimmed, rshape.stroke);
|
||||
splitPolyline(&trimmed, &splitted, rshape.stroke);
|
||||
strokePolyline(&splitted, strokeData, rshape.stroke);
|
||||
} else // trim -> stroke
|
||||
if ((rshape.stroke->trim.begin != 0.0f) || (rshape.stroke->trim.end != 1.0f)) {
|
||||
WgGeometryDataGroup trimmed{};
|
||||
trimPolyline(&polylines, &trimmed, rshape.stroke);
|
||||
strokePolyline(&trimmed, strokeData, rshape.stroke);
|
||||
} else // split -> stroke
|
||||
if (rshape.stroke->dashPattern) {
|
||||
WgGeometryDataGroup splitted{};
|
||||
splitPolyline(&polylines, &splitted, rshape.stroke);
|
||||
strokePolyline(&splitted, strokeData, rshape.stroke);
|
||||
} else { // stroke
|
||||
strokePolyline(&polylines, strokeData, rshape.stroke);
|
||||
}
|
||||
// append stroke geometry
|
||||
geometries.push(strokeData);
|
||||
}
|
||||
|
||||
|
||||
void WgGeometryDataGroup::release()
|
||||
{
|
||||
for (uint32_t i = 0; i < geometries.count; i++)
|
||||
delete geometries[i];
|
||||
geometries.clear();
|
||||
}
|
||||
|
||||
|
||||
void WgGeometryDataGroup::decodePath(const RenderShape& rshape, WgGeometryDataGroup* polyline)
|
||||
{
|
||||
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) {
|
||||
polyline->geometries.push(new WgGeometryData);
|
||||
auto outline = polyline->geometries.last();
|
||||
outline->positions.push(rshape.path.pts[pntIndex]);
|
||||
pntIndex++;
|
||||
} else if (cmd == PathCommand::LineTo) {
|
||||
auto outline = polyline->geometries.last();
|
||||
if (outline)
|
||||
outline->positions.push(rshape.path.pts[pntIndex]);
|
||||
pntIndex++;
|
||||
} else if (cmd == PathCommand::Close) {
|
||||
auto outline = polyline->geometries.last();
|
||||
if ((outline) && (outline->positions.count > 0))
|
||||
outline->positions.push(outline->positions[0]);
|
||||
} else if (cmd == PathCommand::CubicTo) {
|
||||
auto outline = polyline->geometries.last();
|
||||
if ((outline) && (outline->positions.count > 0))
|
||||
outline->appendCubic(
|
||||
rshape.path.pts[pntIndex + 0],
|
||||
rshape.path.pts[pntIndex + 1],
|
||||
rshape.path.pts[pntIndex + 2]
|
||||
);
|
||||
pntIndex += 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void WgGeometryDataGroup::contourPolyline(WgGeometryDataGroup* polyline, WgGeometryDataGroup* contours)
|
||||
void WgGeometryData::appendStrokeDashed(const WgPolyline* polyline, const RenderStroke *stroke)
|
||||
{
|
||||
assert(stroke);
|
||||
assert(polyline);
|
||||
assert(contours);
|
||||
for (uint32_t i = 0 ; i < polyline->geometries.count; i++) {
|
||||
WgGeometryData* geometry = new WgGeometryData();
|
||||
geometry->computeContour(polyline->geometries[i]);
|
||||
contours->geometries.push(geometry);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void WgGeometryDataGroup::trimPolyline(WgGeometryDataGroup* polyline, WgGeometryDataGroup* trimmed, RenderStroke *stroke)
|
||||
{
|
||||
assert(stroke);
|
||||
for (uint32_t i = 0; i < polyline->geometries.count; i++) {
|
||||
auto segment = new WgGeometryData;
|
||||
uint32_t is = 0;
|
||||
uint32_t ie = 0;
|
||||
WgPoint vs = polyline->geometries[i]->interpolate(stroke->trim.begin, is);
|
||||
WgPoint ve = polyline->geometries[i]->interpolate(stroke->trim.end, ie);
|
||||
segment->positions.push(vs);
|
||||
for (uint32_t j = is+1; j <= ie; j++)
|
||||
segment->positions.push(polyline->geometries[i]->positions[j]);
|
||||
segment->positions.push(ve);
|
||||
trimmed->geometries.push(segment);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void WgGeometryDataGroup::splitPolyline(WgGeometryDataGroup* polyline, WgGeometryDataGroup* splitted, RenderStroke *stroke)
|
||||
{
|
||||
assert(stroke);
|
||||
for (uint32_t i = 0; i < polyline->geometries.count; i++) {
|
||||
auto& vlist = polyline->geometries[i]->positions;
|
||||
|
||||
// append single point segment
|
||||
if (vlist.count == 1) {
|
||||
auto segment = new WgGeometryData;
|
||||
segment->positions.push(vlist.last());
|
||||
splitted->geometries.push(segment);
|
||||
}
|
||||
|
||||
if (vlist.count >= 2) {
|
||||
uint32_t icurr = 1;
|
||||
uint32_t ipatt = 0;
|
||||
WgPoint vcurr = vlist[0];
|
||||
while (icurr < vlist.count) {
|
||||
if (ipatt % 2 == 0) {
|
||||
splitted->geometries.push(new WgGeometryData);
|
||||
splitted->geometries.last()->positions.push(vcurr);
|
||||
static WgPolyline dashed;
|
||||
dashed.clear();
|
||||
// append single point polyline
|
||||
if (polyline->pts.count == 1)
|
||||
appendStroke(polyline, stroke);
|
||||
// append multy points dashed polyline
|
||||
else 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 sergemnt
|
||||
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);
|
||||
dashed.clear();
|
||||
}
|
||||
float lcurr = stroke->dashPattern[ipatt];
|
||||
while ((icurr < vlist.count) && (vlist[icurr].dist(vcurr) < lcurr)) {
|
||||
lcurr -= vlist[icurr].dist(vcurr);
|
||||
vcurr = vlist[icurr];
|
||||
icurr++;
|
||||
if (ipatt % 2 == 0) splitted->geometries.last()->positions.push(vcurr);
|
||||
}
|
||||
if (icurr < vlist.count) {
|
||||
vcurr = vcurr + (vlist[icurr] - vlist[icurr-1]).normal() * lcurr;
|
||||
if (ipatt % 2 == 0) splitted->geometries.last()->positions.push(vcurr);
|
||||
}
|
||||
ipatt = (ipatt + 1) % stroke->dashCnt;
|
||||
}
|
||||
// update current subline length
|
||||
currentLength -= dist[i+1];
|
||||
}
|
||||
// draw last subline
|
||||
if (dashIndex % 2 == 0) {
|
||||
dashed.appendPoint(pts.last());
|
||||
appendStroke(&dashed, stroke);
|
||||
dashed.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void WgGeometryDataGroup::strokePolyline(WgGeometryDataGroup* polyline, WgGeometryData* strokes, RenderStroke *stroke)
|
||||
void WgGeometryData::appendStroke(const WgPolyline* polyline, const RenderStroke *stroke)
|
||||
{
|
||||
assert(stroke);
|
||||
assert(polyline);
|
||||
float wdt = stroke->width / 2;
|
||||
for (uint32_t i = 0; i < polyline->geometries.count; i++) {
|
||||
auto outline = polyline->geometries[i];
|
||||
|
||||
// single point sub-path
|
||||
if (outline->positions.count == 1) {
|
||||
if (stroke->cap == StrokeCap::Round) {
|
||||
strokes->appendCircle(outline->positions[0], wdt);
|
||||
} else if (stroke->cap == StrokeCap::Butt) {
|
||||
// for zero length sub-paths no stroke is rendered
|
||||
} else if (stroke->cap == StrokeCap::Square) {
|
||||
strokes->appendRect(
|
||||
outline->positions[0] + WgPoint(+wdt, +wdt),
|
||||
outline->positions[0] + WgPoint(+wdt, -wdt),
|
||||
outline->positions[0] + WgPoint(-wdt, +wdt),
|
||||
outline->positions[0] + WgPoint(-wdt, -wdt)
|
||||
);
|
||||
}
|
||||
// single point sub-path
|
||||
if (polyline->pts.count == 1) {
|
||||
if (stroke->cap == StrokeCap::Round) {
|
||||
appendCircle(polyline->pts[0], wdt);
|
||||
} else if (stroke->cap == StrokeCap::Butt) {
|
||||
// for zero length sub-paths no stroke is rendered
|
||||
} else if (stroke->cap == StrokeCap::Square) {
|
||||
appendRect(
|
||||
polyline->pts[0] + WgPoint(+wdt, +wdt),
|
||||
polyline->pts[0] + WgPoint(+wdt, -wdt),
|
||||
polyline->pts[0] + WgPoint(-wdt, +wdt),
|
||||
polyline->pts[0] + WgPoint(-wdt, -wdt)
|
||||
);
|
||||
}
|
||||
|
||||
// single line sub-path
|
||||
if (outline->positions.count == 2) {
|
||||
WgPoint v0 = outline->positions[0];
|
||||
WgPoint v1 = outline->positions[1];
|
||||
WgPoint dir0 = (v1 - v0).normal();
|
||||
WgPoint nrm0 = WgPoint{ -dir0.y, +dir0.x };
|
||||
if (stroke->cap == StrokeCap::Round) {
|
||||
strokes->appendRect(
|
||||
v0 - nrm0 * wdt, v0 + nrm0 * wdt,
|
||||
v1 - nrm0 * wdt, v1 + nrm0 * wdt);
|
||||
strokes->appendCircle(outline->positions[0], wdt);
|
||||
strokes->appendCircle(outline->positions[1], wdt);
|
||||
} else if (stroke->cap == StrokeCap::Butt) {
|
||||
strokes->appendRect(
|
||||
v0 - nrm0 * wdt, v0 + nrm0 * wdt,
|
||||
v1 - nrm0 * wdt, v1 + nrm0 * wdt
|
||||
);
|
||||
} else if (stroke->cap == StrokeCap::Square) {
|
||||
strokes->appendRect(
|
||||
v0 - nrm0 * wdt - dir0 * wdt, v0 + nrm0 * wdt - dir0 * wdt,
|
||||
v1 - nrm0 * wdt + dir0 * wdt, v1 + nrm0 * wdt + dir0 * wdt
|
||||
);
|
||||
}
|
||||
} else
|
||||
|
||||
// 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);
|
||||
appendCircle(polyline->pts[1], wdt);
|
||||
} 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
|
||||
|
||||
// multi-lined sub-path
|
||||
if (outline->positions.count > 2) {
|
||||
// multi-lined sub-path
|
||||
if (polyline->pts.count > 2) {
|
||||
if (polyline->isClosed()) {
|
||||
if (stroke->join == StrokeJoin::Round) {
|
||||
appendCircle(polyline->pts[0], wdt);
|
||||
} else {
|
||||
float dist0 = polyline->dist.last();
|
||||
float dist1 = polyline->dist[1];
|
||||
WgPoint v0 = polyline->pts[polyline->pts.count - 2];
|
||||
WgPoint v1 = polyline->pts[0];
|
||||
WgPoint v2 = polyline->pts[1];
|
||||
WgPoint dir0 = (v1 - v0) / dist0;
|
||||
WgPoint dir1 = (v2 - v1) / dist1;
|
||||
WgPoint nrm0 { +dir0.y, -dir0.x };
|
||||
WgPoint nrm1 { +dir1.y, -dir1.x };
|
||||
WgPoint offset0 = nrm0 * wdt;
|
||||
WgPoint offset1 = nrm1 * wdt;
|
||||
if (stroke->join == StrokeJoin::Bevel) {
|
||||
appendRect(v1 - offset0, v1 + offset0, v1 - offset1, v1 + offset1);
|
||||
} else if (stroke->join == StrokeJoin::Miter) {
|
||||
WgPoint nrm = (nrm0 + nrm1).normal();
|
||||
float cosine = nrm.dot(nrm0);
|
||||
if ((cosine != 0.0f) && (abs(cosine) != 1.0f) && (abs(wdt / cosine) <= stroke->miterlimit * 2)) {
|
||||
appendRect(v1 + nrm * (wdt / cosine), v1 + offset0, v1 + offset1, v1 - nrm * (wdt / cosine));
|
||||
} else {
|
||||
appendRect(v1 - offset0, v1 + offset0, v1 - offset1, v1 + offset1);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// append first cap
|
||||
WgPoint v0 = outline->positions[0];
|
||||
WgPoint v1 = outline->positions[1];
|
||||
WgPoint dir0 = (v1 - v0).normal();
|
||||
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) {
|
||||
strokes->appendCircle(v0, wdt);
|
||||
appendCircle(v0, wdt);
|
||||
} else if (stroke->cap == StrokeCap::Butt) {
|
||||
// no cap needed
|
||||
} else if (stroke->cap == StrokeCap::Square) {
|
||||
strokes->appendRect(
|
||||
v0 - nrm0 * wdt - dir0 * wdt,
|
||||
v0 + nrm0 * wdt - dir0 * wdt,
|
||||
v0 - nrm0 * wdt,
|
||||
v0 + nrm0 * wdt
|
||||
);
|
||||
appendRect(v0 - nrm0 * wdt - dir0 * wdt, v0 + nrm0 * wdt - dir0 * wdt, v0 - nrm0 * wdt, v0 + nrm0 * wdt);
|
||||
}
|
||||
|
||||
// append last cap
|
||||
v0 = outline->positions[outline->positions.count - 2];
|
||||
v1 = outline->positions[outline->positions.count - 1];
|
||||
dir0 = (v1 - v0).normal();
|
||||
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) {
|
||||
strokes->appendCircle(v1, wdt);
|
||||
appendCircle(v1, wdt);
|
||||
} else if (stroke->cap == StrokeCap::Butt) {
|
||||
// no cap needed
|
||||
} else if (stroke->cap == StrokeCap::Square) {
|
||||
strokes->appendRect(
|
||||
v1 - nrm0 * wdt,
|
||||
v1 + nrm0 * wdt,
|
||||
v1 - nrm0 * wdt + dir0 * wdt,
|
||||
v1 + nrm0 * wdt + dir0 * wdt
|
||||
);
|
||||
appendRect(v1 - nrm0 * wdt, v1 + nrm0 * wdt, v1 - nrm0 * wdt + dir0 * wdt, v1 + nrm0 * wdt + dir0 * wdt);
|
||||
}
|
||||
}
|
||||
|
||||
// append sub-lines
|
||||
for (uint32_t j = 0; j < outline->positions.count - 1; j++) {
|
||||
WgPoint v0 = outline->positions[j + 0];
|
||||
WgPoint v1 = outline->positions[j + 1];
|
||||
WgPoint dir = (v1 - v0).normal();
|
||||
WgPoint nrm { -dir.y, +dir.x };
|
||||
strokes->appendRect(
|
||||
v0 - nrm * wdt,
|
||||
v0 + nrm * wdt,
|
||||
v1 - nrm * wdt,
|
||||
v1 + nrm * 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);
|
||||
|
||||
// append joints (TODO: separate by joint types)
|
||||
for (uint32_t j = 1; j < outline->positions.count - 1; j++) {
|
||||
WgPoint v0 = outline->positions[j - 1];
|
||||
WgPoint v1 = outline->positions[j + 0];
|
||||
WgPoint v2 = outline->positions[j + 1];
|
||||
WgPoint dir0 = (v1 - v0).normal();
|
||||
WgPoint dir1 = (v1 - v2).normal();
|
||||
WgPoint nrm0 { -dir0.y, +dir0.x };
|
||||
WgPoint nrm1 { +dir1.y, -dir1.x };
|
||||
if (i > 0) {
|
||||
if (stroke->join == StrokeJoin::Round) {
|
||||
strokes->appendCircle(v1, wdt);
|
||||
} else if (stroke->join == StrokeJoin::Bevel) {
|
||||
strokes->appendRect(
|
||||
v1 - nrm0 * wdt, v1 - nrm1 * wdt,
|
||||
v1 + nrm1 * wdt, v1 + nrm0 * wdt
|
||||
);
|
||||
} else if (stroke->join == StrokeJoin::Miter) {
|
||||
WgPoint nrm = (dir0 + dir1).normal();
|
||||
float cosine = nrm.dot(nrm0);
|
||||
if ((cosine != 0.0f) && (abs(wdt / cosine) <= stroke->miterlimit * 2)) {
|
||||
strokes->appendRect(v1 + nrm * (wdt / cosine), v1 + nrm0 * wdt, v1 + nrm1 * wdt, v1);
|
||||
strokes->appendRect(v1 - nrm * (wdt / cosine), v1 - nrm0 * wdt, v1 - nrm1 * wdt, v1);
|
||||
} else {
|
||||
strokes->appendRect(
|
||||
v1 - nrm0 * wdt, v1 - nrm1 * wdt,
|
||||
v1 + nrm1 * wdt, v1 + nrm0 * wdt);
|
||||
appendCircle(v1, wdt);
|
||||
} else {
|
||||
float dist0 = polyline->dist[i + 0];
|
||||
WgPoint v0 = polyline->pts[i - 1];
|
||||
WgPoint dir0 = (v1 - v0) / dist0;
|
||||
WgPoint nrm0 { +dir0.y, -dir0.x };
|
||||
WgPoint offset0 = nrm0 * wdt;
|
||||
if (stroke->join == StrokeJoin::Bevel) {
|
||||
appendRect(v1 - offset0, v1 + offset0, v1 - offset1, v1 + offset1);
|
||||
} else if (stroke->join == StrokeJoin::Miter) {
|
||||
WgPoint nrm = (nrm0 + nrm1).normal();
|
||||
float cosine = nrm.dot(nrm0);
|
||||
if ((cosine != 0.0f) && (abs(cosine) != 1.0f) && (abs(wdt / cosine) <= stroke->miterlimit * 2)) {
|
||||
appendRect(v1 + nrm * (wdt / cosine), v1 + offset0, v1 + offset1, v1);
|
||||
appendRect(v1 - nrm * (wdt / cosine), v1 - offset0, v1 - offset1, v1);
|
||||
} else {
|
||||
appendRect(v1 - offset0, v1 + offset0, v1 - offset1, v1 + offset1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,7 +59,6 @@ public:
|
|||
inline WgPoint lerp(const WgPoint& p, float t) const { return { x + (p.x - x) * t, y + (p.y - y) * t }; };
|
||||
};
|
||||
|
||||
|
||||
struct WgMath
|
||||
{
|
||||
Array<float> sinus;
|
||||
|
@ -102,14 +101,12 @@ struct WgGeometryData
|
|||
{
|
||||
static WgMath* gMath;
|
||||
|
||||
Array<WgPoint> positions{};
|
||||
WgPolyline positions{};
|
||||
Array<WgPoint> texCoords{};
|
||||
Array<uint32_t> indexes{};
|
||||
|
||||
// webgpu did not support triangle fans primitives type
|
||||
// so we can emulate triangle fans using indexing
|
||||
void computeTriFansIndexes();
|
||||
void computeContour(WgGeometryData* data);
|
||||
WgGeometryData();
|
||||
void clear();
|
||||
|
||||
void appendCubic(WgPoint p1, WgPoint p2, WgPoint p3);
|
||||
void appendBox(WgPoint pmin, WgPoint pmax);
|
||||
|
@ -118,37 +115,8 @@ struct WgGeometryData
|
|||
void appendImageBox(float w, float h);
|
||||
void appendBlitBox();
|
||||
void appendMesh(const RenderMesh* rmesh);
|
||||
|
||||
WgPoint interpolate(float t, uint32_t& ind); // t = [0;1]
|
||||
|
||||
float getLength();
|
||||
bool getClosestIntersection(WgPoint p1, WgPoint p2, WgPoint& pi, uint32_t& index);
|
||||
bool isCW(WgPoint p1, WgPoint p2, WgPoint p3);
|
||||
|
||||
uint32_t getIndexMinX();
|
||||
uint32_t getIndexMaxX();
|
||||
uint32_t getIndexMinY();
|
||||
uint32_t getIndexMaxY();
|
||||
|
||||
void close();
|
||||
void clear();
|
||||
void appendStrokeDashed(const WgPolyline* polyline, const RenderStroke *stroke);
|
||||
void appendStroke(const WgPolyline* polyline, const RenderStroke *stroke);
|
||||
};
|
||||
|
||||
struct WgGeometryDataGroup
|
||||
{
|
||||
Array<WgGeometryData*> geometries{};
|
||||
virtual ~WgGeometryDataGroup() { release(); }
|
||||
|
||||
void getBBox(WgPoint& pmin, WgPoint& pmax);
|
||||
void tesselate(const RenderShape& rshape);
|
||||
void stroke(const RenderShape& rshape);
|
||||
void release();
|
||||
private:
|
||||
static void decodePath(const RenderShape& rshape, WgGeometryDataGroup* polyline);
|
||||
static void contourPolyline(WgGeometryDataGroup* polyline, WgGeometryDataGroup* contours);
|
||||
static void trimPolyline(WgGeometryDataGroup* polyline, WgGeometryDataGroup* trimmed, RenderStroke *stroke);
|
||||
static void splitPolyline(WgGeometryDataGroup* polyline, WgGeometryDataGroup* splitted, RenderStroke *stroke);
|
||||
static void strokePolyline(WgGeometryDataGroup* polyline, WgGeometryData* strokes, RenderStroke *stroke);
|
||||
};
|
||||
|
||||
#endif // _TVG_WG_GEOMETRY_H_
|
||||
#endif // _TVG_WG_GEOMETRY_H_
|
|
@ -30,7 +30,7 @@
|
|||
// WgMeshData
|
||||
//***********************************************************************
|
||||
|
||||
void WgMeshData::draw(WGPURenderPassEncoder renderPassEncoder)
|
||||
void WgMeshData::draw(WgContext& context, WGPURenderPassEncoder renderPassEncoder)
|
||||
{
|
||||
wgpuRenderPassEncoderSetVertexBuffer(renderPassEncoder, 0, bufferPosition, 0, vertexCount * sizeof(float) * 2);
|
||||
wgpuRenderPassEncoderSetIndexBuffer(renderPassEncoder, bufferIndex, WGPUIndexFormat_Uint32, 0, indexCount * sizeof(uint32_t));
|
||||
|
@ -38,7 +38,15 @@ void WgMeshData::draw(WGPURenderPassEncoder renderPassEncoder)
|
|||
}
|
||||
|
||||
|
||||
void WgMeshData::drawImage(WGPURenderPassEncoder renderPassEncoder)
|
||||
void WgMeshData::drawFan(WgContext& context, WGPURenderPassEncoder renderPassEncoder)
|
||||
{
|
||||
wgpuRenderPassEncoderSetVertexBuffer(renderPassEncoder, 0, bufferPosition, 0, vertexCount * sizeof(float) * 2);
|
||||
wgpuRenderPassEncoderSetIndexBuffer(renderPassEncoder, context.indexBufferFan, WGPUIndexFormat_Uint32, 0, indexCount * sizeof(uint32_t));
|
||||
wgpuRenderPassEncoderDrawIndexed(renderPassEncoder, indexCount, 1, 0, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
void WgMeshData::drawImage(WgContext& context, WGPURenderPassEncoder renderPassEncoder)
|
||||
{
|
||||
wgpuRenderPassEncoderSetVertexBuffer(renderPassEncoder, 0, bufferPosition, 0, vertexCount * sizeof(float) * 2);
|
||||
wgpuRenderPassEncoderSetVertexBuffer(renderPassEncoder, 1, bufferTexCoord, 0, vertexCount * sizeof(float) * 2);
|
||||
|
@ -47,13 +55,25 @@ void WgMeshData::drawImage(WGPURenderPassEncoder renderPassEncoder)
|
|||
};
|
||||
|
||||
|
||||
void WgMeshData::update(WgContext& context, WgGeometryData* geometryData){
|
||||
void WgMeshData::update(WgContext& context, const WgPolyline* polyline)
|
||||
{
|
||||
assert(polyline);
|
||||
assert(polyline->pts.count > 2);
|
||||
vertexCount = polyline->pts.count;
|
||||
indexCount = (polyline->pts.count - 2) * 3;
|
||||
context.allocateVertexBuffer(bufferPosition, &polyline->pts[0], vertexCount * sizeof(float) * 2);
|
||||
context.allocateIndexBufferFan(vertexCount);
|
||||
}
|
||||
|
||||
|
||||
void WgMeshData::update(WgContext& context, const WgGeometryData* geometryData)
|
||||
{
|
||||
assert(geometryData);
|
||||
vertexCount = geometryData->positions.count;
|
||||
vertexCount = geometryData->positions.pts.count;
|
||||
indexCount = geometryData->indexes.count;
|
||||
// buffer position data create and write
|
||||
if (geometryData->positions.count > 0)
|
||||
context.allocateVertexBuffer(bufferPosition, &geometryData->positions[0], vertexCount * sizeof(float) * 2);
|
||||
if (geometryData->positions.pts.count > 0)
|
||||
context.allocateVertexBuffer(bufferPosition, &geometryData->positions.pts[0], vertexCount * sizeof(float) * 2);
|
||||
// buffer tex coords data create and write
|
||||
if (geometryData->texCoords.count > 0)
|
||||
context.allocateVertexBuffer(bufferTexCoord, &geometryData->texCoords[0], vertexCount * sizeof(float) * 2);
|
||||
|
@ -63,6 +83,19 @@ void WgMeshData::update(WgContext& context, WgGeometryData* geometryData){
|
|||
};
|
||||
|
||||
|
||||
void WgMeshData::update(WgContext& context, const WgPoint pmin, const WgPoint pmax)
|
||||
{
|
||||
vertexCount = 4;
|
||||
indexCount = 6;
|
||||
const float data[] = {
|
||||
pmin.x, pmin.y, pmax.x, pmin.y,
|
||||
pmax.x, pmax.y, pmin.x, pmax.y
|
||||
};
|
||||
context.allocateVertexBuffer(bufferPosition, data, sizeof(data));
|
||||
context.allocateIndexBufferFan(vertexCount);
|
||||
}
|
||||
|
||||
|
||||
void WgMeshData::release(WgContext& context)
|
||||
{
|
||||
context.releaseBuffer(bufferIndex);
|
||||
|
@ -105,29 +138,41 @@ void WgMeshDataPool::release(WgContext& context)
|
|||
mList.clear();
|
||||
}
|
||||
|
||||
WgMeshDataPool* WgMeshDataGroup::MeshDataPool = nullptr;
|
||||
WgMeshDataPool* WgMeshDataGroup::gMeshDataPool = nullptr;
|
||||
|
||||
//***********************************************************************
|
||||
// WgMeshDataGroup
|
||||
//***********************************************************************
|
||||
|
||||
void WgMeshDataGroup::update(WgContext& context, WgGeometryDataGroup* geometryDataGroup)
|
||||
void WgMeshDataGroup::append(WgContext& context, const WgPolyline* polyline)
|
||||
{
|
||||
release(context);
|
||||
assert(geometryDataGroup);
|
||||
for (uint32_t i = 0; i < geometryDataGroup->geometries.count; i++) {
|
||||
if (geometryDataGroup->geometries[i]->positions.count > 2) {
|
||||
meshes.push(MeshDataPool->allocate(context));
|
||||
meshes.last()->update(context, geometryDataGroup->geometries[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
assert(polyline);
|
||||
assert(polyline->pts.count >= 3);
|
||||
meshes.push(gMeshDataPool->allocate(context));
|
||||
meshes.last()->update(context, polyline);
|
||||
}
|
||||
|
||||
|
||||
void WgMeshDataGroup::append(WgContext& context, const WgGeometryData* geometryData)
|
||||
{
|
||||
assert(geometryData);
|
||||
assert(geometryData->positions.pts.count >= 3);
|
||||
meshes.push(gMeshDataPool->allocate(context));
|
||||
meshes.last()->update(context, geometryData);
|
||||
}
|
||||
|
||||
|
||||
void WgMeshDataGroup::append(WgContext& context, const WgPoint pmin, const WgPoint pmax)
|
||||
{
|
||||
meshes.push(gMeshDataPool->allocate(context));
|
||||
meshes.last()->update(context, pmin, pmax);
|
||||
}
|
||||
|
||||
|
||||
void WgMeshDataGroup::release(WgContext& context)
|
||||
{
|
||||
for (uint32_t i = 0; i < meshes.count; i++)
|
||||
MeshDataPool->free(context, meshes[i]);
|
||||
gMeshDataPool->free(context, meshes[i]);
|
||||
meshes.clear();
|
||||
};
|
||||
|
||||
|
@ -223,44 +268,85 @@ void WgRenderDataShape::updateMeshes(WgContext &context, const RenderShape &rsha
|
|||
{
|
||||
releaseMeshes(context);
|
||||
strokeFirst = false;
|
||||
// update shapes geometry
|
||||
WgGeometryDataGroup shapes;
|
||||
shapes.tesselate(rshape);
|
||||
meshGroupShapes.update(context, &shapes);
|
||||
// update shapes bbox
|
||||
WgPoint pmin{}, pmax{};
|
||||
shapes.getBBox(pmin, pmax);
|
||||
WgGeometryData box;
|
||||
box.appendBox(pmin, pmax);
|
||||
meshBBoxShapes.update(context, &box);
|
||||
// update strokes geometry
|
||||
if (rshape.stroke) {
|
||||
strokeFirst = rshape.stroke->strokeFirst;
|
||||
WgGeometryDataGroup strokes;
|
||||
strokes.stroke(rshape);
|
||||
strokes.getBBox(pmin, pmax);
|
||||
meshGroupStrokes.update(context, &strokes);
|
||||
// update strokes bbox
|
||||
|
||||
static WgPolyline polyline;
|
||||
polyline.clear();
|
||||
// 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
|
||||
updateMeshes(context, &polyline, rshape.stroke);
|
||||
polyline.clear();
|
||||
polyline.appendPoint(rshape.path.pts[pntIndex]);
|
||||
pntIndex++;
|
||||
} else if (cmd == PathCommand::LineTo) {
|
||||
polyline.appendPoint(rshape.path.pts[pntIndex]);
|
||||
pntIndex++;
|
||||
} else if (cmd == PathCommand::Close) {
|
||||
polyline.close();
|
||||
} else if (cmd == PathCommand::CubicTo) {
|
||||
polyline.appendCubic(
|
||||
rshape.path.pts[pntIndex + 0],
|
||||
rshape.path.pts[pntIndex + 1],
|
||||
rshape.path.pts[pntIndex + 2]);
|
||||
pntIndex += 3;
|
||||
}
|
||||
}
|
||||
// proceed last polyline
|
||||
updateMeshes(context, &polyline, rshape.stroke);
|
||||
}
|
||||
|
||||
|
||||
void WgRenderDataShape::updateMeshes(WgContext& context, const WgPolyline* polyline, const RenderStroke* rstroke)
|
||||
{
|
||||
assert(polyline);
|
||||
// generate fill geometry
|
||||
if (polyline->pts.count >= 3) {
|
||||
WgPoint pmin{}, pmax{};
|
||||
strokes.getBBox(pmin, pmax);
|
||||
WgGeometryData box;
|
||||
box.appendBox(pmin, pmax);
|
||||
meshBBoxStrokes.update(context, &box);
|
||||
polyline->getBBox(pmin, pmax);
|
||||
meshGroupShapes.append(context, polyline);
|
||||
meshGroupShapesBBox.append(context, pmin, pmax);
|
||||
}
|
||||
// generate strokes geometry
|
||||
if ((polyline->pts.count >= 1) && rstroke) {
|
||||
static WgGeometryData geometryData; geometryData.clear();
|
||||
static WgPolyline trimmed;
|
||||
// trim -> split -> stroke
|
||||
if ((rstroke->dashPattern) && ((rstroke->trim.begin != 0.0f) || (rstroke->trim.end != 1.0f))) {
|
||||
polyline->trim(&trimmed, rstroke->trim.begin, rstroke->trim.end);
|
||||
geometryData.appendStrokeDashed(&trimmed, rstroke);
|
||||
} else // trim -> stroke
|
||||
if ((rstroke->trim.begin != 0.0f) || (rstroke->trim.end != 1.0f)) {
|
||||
polyline->trim(&trimmed, rstroke->trim.begin, rstroke->trim.end);
|
||||
geometryData.appendStroke(&trimmed, rstroke);
|
||||
} else // split -> stroke
|
||||
if (rstroke->dashPattern) {
|
||||
geometryData.appendStrokeDashed(polyline, rstroke);
|
||||
} else { // stroke
|
||||
geometryData.appendStroke(polyline, rstroke);
|
||||
}
|
||||
// append render meshes and bboxes
|
||||
WgPoint pmin{}, pmax{};
|
||||
geometryData.positions.getBBox(pmin, pmax);
|
||||
meshGroupStrokes.append(context, &geometryData);
|
||||
meshGroupStrokesBBox.append(context, pmin, pmax);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void WgRenderDataShape::releaseMeshes(WgContext &context)
|
||||
{
|
||||
meshGroupStrokesBBox.release(context);
|
||||
meshGroupStrokes.release(context);
|
||||
meshGroupShapesBBox.release(context);
|
||||
meshGroupShapes.release(context);
|
||||
}
|
||||
|
||||
|
||||
void WgRenderDataShape::release(WgContext& context)
|
||||
{
|
||||
meshBBoxStrokes.release(context);
|
||||
meshBBoxShapes.release(context);
|
||||
releaseMeshes(context);
|
||||
renderSettingsStroke.release(context);
|
||||
renderSettingsShape.release(context);
|
||||
|
@ -287,6 +373,8 @@ WgRenderDataShape* WgRenderDataShapePool::allocate(WgContext& context)
|
|||
|
||||
void WgRenderDataShapePool::free(WgContext& context, WgRenderDataShape* dataShape)
|
||||
{
|
||||
dataShape->meshGroupShapes.release(context);
|
||||
dataShape->meshGroupStrokes.release(context);
|
||||
mPool.push(dataShape);
|
||||
}
|
||||
|
||||
|
|
|
@ -30,10 +30,13 @@ struct WgMeshData {
|
|||
size_t vertexCount{};
|
||||
size_t indexCount{};
|
||||
|
||||
void draw(WGPURenderPassEncoder renderPassEncoder);
|
||||
void drawImage(WGPURenderPassEncoder renderPassEncoder);
|
||||
void draw(WgContext& context, WGPURenderPassEncoder renderPassEncoder);
|
||||
void drawFan(WgContext& context, WGPURenderPassEncoder renderPassEncoder);
|
||||
void drawImage(WgContext& context, WGPURenderPassEncoder renderPassEncoder);
|
||||
|
||||
void update(WgContext& context, WgGeometryData* geometryData);
|
||||
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 release(WgContext& context);
|
||||
};
|
||||
|
||||
|
@ -48,11 +51,13 @@ public:
|
|||
};
|
||||
|
||||
struct WgMeshDataGroup {
|
||||
static WgMeshDataPool* MeshDataPool;
|
||||
static WgMeshDataPool* gMeshDataPool;
|
||||
|
||||
Array<WgMeshData*> meshes{};
|
||||
|
||||
void update(WgContext& context, WgGeometryDataGroup* geometryDataGroup);
|
||||
|
||||
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 release(WgContext& context);
|
||||
};
|
||||
|
||||
|
@ -91,12 +96,13 @@ struct WgRenderDataShape: public WgRenderDataPaint
|
|||
WgRenderSettings renderSettingsShape{};
|
||||
WgRenderSettings renderSettingsStroke{};
|
||||
WgMeshDataGroup meshGroupShapes{};
|
||||
WgMeshDataGroup meshGroupShapesBBox{};
|
||||
WgMeshDataGroup meshGroupStrokes{};
|
||||
WgMeshData meshBBoxShapes{};
|
||||
WgMeshData meshBBoxStrokes{};
|
||||
WgMeshDataGroup meshGroupStrokesBBox{};
|
||||
bool strokeFirst{};
|
||||
|
||||
void updateMeshes(WgContext& context, const RenderShape& rshape);
|
||||
void updateMeshes(WgContext& context, const WgPolyline* polyline, const RenderStroke* rstroke);
|
||||
void releaseMeshes(WgContext& context);
|
||||
void release(WgContext& context) override;
|
||||
uint32_t identifier() override { return TVG_CLASS_ID_SHAPE; };
|
||||
|
|
|
@ -78,40 +78,41 @@ void WgRenderStorage::release(WgContext& context)
|
|||
}
|
||||
|
||||
|
||||
void WgRenderStorage::renderShape(WgRenderDataShape* renderData, WgPipelineBlendType blendType)
|
||||
void WgRenderStorage::renderShape(WgContext& context, WgRenderDataShape* renderData, WgPipelineBlendType blendType)
|
||||
{
|
||||
assert(renderData);
|
||||
assert(mRenderPassEncoder);
|
||||
if (renderData->strokeFirst)
|
||||
drawStroke(renderData, blendType);
|
||||
drawShape(renderData, blendType);
|
||||
drawStroke(context, renderData, blendType);
|
||||
drawShape(context, renderData, blendType);
|
||||
if (!renderData->strokeFirst)
|
||||
drawStroke(renderData, blendType);
|
||||
drawStroke(context, renderData, blendType);
|
||||
}
|
||||
|
||||
|
||||
void WgRenderStorage::renderPicture(WgRenderDataPicture* renderData, WgPipelineBlendType blendType)
|
||||
void WgRenderStorage::renderPicture(WgContext& context, WgRenderDataPicture* renderData, WgPipelineBlendType blendType)
|
||||
{
|
||||
assert(renderData);
|
||||
assert(mRenderPassEncoder);
|
||||
uint8_t blend = (uint8_t)blendType;
|
||||
wgpuRenderPassEncoderSetStencilReference(mRenderPassEncoder, 0);
|
||||
mPipelines->image[blend].use(mRenderPassEncoder, mBindGroupCanvas, renderData->bindGroupPaint, renderData->bindGroupPicture);
|
||||
renderData->meshData.drawImage(mRenderPassEncoder);
|
||||
renderData->meshData.drawImage(context, mRenderPassEncoder);
|
||||
}
|
||||
|
||||
|
||||
void WgRenderStorage::drawShape(WgRenderDataShape* renderData, WgPipelineBlendType blendType)
|
||||
void WgRenderStorage::drawShape(WgContext& context, WgRenderDataShape* renderData, WgPipelineBlendType blendType)
|
||||
{
|
||||
assert(renderData);
|
||||
assert(mRenderPassEncoder);
|
||||
assert(renderData->meshGroupShapes.meshes.count == renderData->meshGroupShapesBBox.meshes.count);
|
||||
// draw shape geometry
|
||||
uint8_t blend = (uint8_t)blendType;
|
||||
wgpuRenderPassEncoderSetStencilReference(mRenderPassEncoder, 0);
|
||||
for (uint32_t i = 0; i < renderData->meshGroupShapes.meshes.count; i++) {
|
||||
// draw to stencil (first pass)
|
||||
mPipelines->fillShape.use(mRenderPassEncoder, mBindGroupCanvas, renderData->bindGroupPaint);
|
||||
renderData->meshGroupShapes.meshes[i]->draw(mRenderPassEncoder);
|
||||
renderData->meshGroupShapes.meshes[i]->drawFan(context, mRenderPassEncoder);
|
||||
// fill shape (second pass)
|
||||
WgRenderSettings& settings = renderData->renderSettingsShape;
|
||||
if (settings.fillType == WgRenderSettingsType::Solid)
|
||||
|
@ -120,24 +121,24 @@ void WgRenderStorage::drawShape(WgRenderDataShape* renderData, WgPipelineBlendTy
|
|||
mPipelines->linear[blend].use(mRenderPassEncoder, mBindGroupCanvas, renderData->bindGroupPaint, settings.bindGroupLinear);
|
||||
else if (settings.fillType == WgRenderSettingsType::Radial)
|
||||
mPipelines->radial[blend].use(mRenderPassEncoder, mBindGroupCanvas, renderData->bindGroupPaint, settings.bindGroupRadial);
|
||||
renderData->meshBBoxShapes.draw(mRenderPassEncoder);
|
||||
renderData->meshGroupShapesBBox.meshes[i]->drawFan(context, mRenderPassEncoder);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void WgRenderStorage::drawStroke(WgRenderDataShape* renderData, WgPipelineBlendType blendType)
|
||||
void WgRenderStorage::drawStroke(WgContext& context, WgRenderDataShape* renderData, WgPipelineBlendType blendType)
|
||||
{
|
||||
assert(renderData);
|
||||
assert(mRenderPassEncoder);
|
||||
assert(renderData->meshGroupStrokes.meshes.count == renderData->meshGroupStrokesBBox.meshes.count);
|
||||
// draw stroke geometry
|
||||
uint8_t blend = (uint8_t)blendType;
|
||||
if (renderData->meshGroupStrokes.meshes.count > 0) {
|
||||
// draw strokes to stencil (first pass)
|
||||
// draw strokes to stencil (first pass)
|
||||
for (uint32_t i = 0; i < renderData->meshGroupStrokes.meshes.count; i++) {
|
||||
// draw to stencil (first pass)
|
||||
wgpuRenderPassEncoderSetStencilReference(mRenderPassEncoder, 255);
|
||||
for (uint32_t i = 0; i < renderData->meshGroupStrokes.meshes.count; i++) {
|
||||
mPipelines->fillStroke.use(mRenderPassEncoder, mBindGroupCanvas, renderData->bindGroupPaint);
|
||||
renderData->meshGroupStrokes.meshes[i]->draw(mRenderPassEncoder);
|
||||
}
|
||||
mPipelines->fillStroke.use(mRenderPassEncoder, mBindGroupCanvas, renderData->bindGroupPaint);
|
||||
renderData->meshGroupStrokes.meshes[i]->draw(context, mRenderPassEncoder);
|
||||
// fill shape (second pass)
|
||||
wgpuRenderPassEncoderSetStencilReference(mRenderPassEncoder, 0);
|
||||
WgRenderSettings& settings = renderData->renderSettingsStroke;
|
||||
|
@ -147,7 +148,7 @@ void WgRenderStorage::drawStroke(WgRenderDataShape* renderData, WgPipelineBlendT
|
|||
mPipelines->linear[blend].use(mRenderPassEncoder, mBindGroupCanvas, renderData->bindGroupPaint, settings.bindGroupLinear);
|
||||
else if (settings.fillType == WgRenderSettingsType::Radial)
|
||||
mPipelines->radial[blend].use(mRenderPassEncoder, mBindGroupCanvas, renderData->bindGroupPaint, settings.bindGroupRadial);
|
||||
renderData->meshBBoxStrokes.draw(mRenderPassEncoder);
|
||||
renderData->meshGroupStrokesBBox.meshes[i]->drawFan(context, mRenderPassEncoder);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -48,16 +48,16 @@ public:
|
|||
void beginRenderPass(WGPUCommandEncoder commandEncoder, bool clear);
|
||||
void endRenderPass();
|
||||
|
||||
void renderShape(WgRenderDataShape* renderData, WgPipelineBlendType blendType);
|
||||
void renderPicture(WgRenderDataPicture* renderData, WgPipelineBlendType blendType);
|
||||
void renderShape(WgContext& context, WgRenderDataShape* renderData, WgPipelineBlendType blendType);
|
||||
void renderPicture(WgContext& context, WgRenderDataPicture* renderData, WgPipelineBlendType blendType);
|
||||
|
||||
void clear(WGPUCommandEncoder commandEncoder);
|
||||
void blend(WGPUCommandEncoder commandEncoder, WgRenderStorage* targetSrc, WgBindGroupBlendMethod* blendMethod);
|
||||
void compose(WGPUCommandEncoder commandEncoder, WgRenderStorage* targetMsk, WgBindGroupCompositeMethod* composeMethod, WgBindGroupOpacity* opacity);
|
||||
void antialias(WGPUCommandEncoder commandEncoder, WgRenderStorage* targetSrc);
|
||||
private:
|
||||
void drawShape(WgRenderDataShape* renderData, WgPipelineBlendType blendType);
|
||||
void drawStroke(WgRenderDataShape* renderData, WgPipelineBlendType blendType);
|
||||
void drawShape(WgContext& context, WgRenderDataShape* renderData, WgPipelineBlendType blendType);
|
||||
void drawStroke(WgContext& context, WgRenderDataShape* renderData, WgPipelineBlendType blendType);
|
||||
|
||||
void dispatchWorkgroups(WGPUComputePassEncoder computePassEncoder);
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ void WgRenderer::initialize()
|
|||
mOpacityPool.initialize(mContext);
|
||||
mBlendMethodPool.initialize(mContext);
|
||||
mCompositeMethodPool.initialize(mContext);
|
||||
WgMeshDataGroup::MeshDataPool = new WgMeshDataPool();
|
||||
WgMeshDataGroup::gMeshDataPool = new WgMeshDataPool();
|
||||
WgGeometryData::gMath = new WgMath();
|
||||
WgGeometryData::gMath->initialize();
|
||||
}
|
||||
|
@ -59,8 +59,8 @@ void WgRenderer::release()
|
|||
WgGeometryData::gMath->release();
|
||||
delete WgGeometryData::gMath;
|
||||
mRenderDataShapePool.release(mContext);
|
||||
WgMeshDataGroup::MeshDataPool->release(mContext);
|
||||
delete WgMeshDataGroup::MeshDataPool;
|
||||
WgMeshDataGroup::gMeshDataPool->release(mContext);
|
||||
delete WgMeshDataGroup::gMeshDataPool;
|
||||
mCompositorStack.clear();
|
||||
mRenderStorageStack.clear();
|
||||
mRenderStoragePool.release(mContext);
|
||||
|
@ -162,13 +162,13 @@ bool WgRenderer::renderShape(RenderData data)
|
|||
assert(renderStorage);
|
||||
// use hardware blend
|
||||
if (WgPipelines::isBlendMethodSupportsHW(mBlendMethod))
|
||||
renderStorage->renderShape((WgRenderDataShape *)data, blendType);
|
||||
renderStorage->renderShape(mContext, (WgRenderDataShape *)data, blendType);
|
||||
else { // use custom blend
|
||||
// terminate current render pass
|
||||
renderStorage->endRenderPass();
|
||||
// render image to render target
|
||||
mRenderTarget.beginRenderPass(mCommandEncoder, true);
|
||||
mRenderTarget.renderShape((WgRenderDataShape *)data, blendType);
|
||||
mRenderTarget.renderShape(mContext, (WgRenderDataShape *)data, blendType);
|
||||
mRenderTarget.endRenderPass();
|
||||
// blend shape with current render storage
|
||||
WgBindGroupBlendMethod* blendMethod = mBlendMethodPool.allocate(mContext, mBlendMethod);
|
||||
|
@ -188,13 +188,13 @@ bool WgRenderer::renderImage(RenderData data)
|
|||
assert(renderStorage);
|
||||
// use hardware blend
|
||||
if (WgPipelines::isBlendMethodSupportsHW(mBlendMethod))
|
||||
renderStorage->renderPicture((WgRenderDataPicture *)data, blendType);
|
||||
renderStorage->renderPicture(mContext, (WgRenderDataPicture *)data, blendType);
|
||||
else { // use custom blend
|
||||
// terminate current render pass
|
||||
renderStorage->endRenderPass();
|
||||
// render image to render target
|
||||
mRenderTarget.beginRenderPass(mCommandEncoder, true);
|
||||
mRenderTarget.renderPicture((WgRenderDataPicture *)data, blendType);
|
||||
mRenderTarget.renderPicture(mContext, (WgRenderDataPicture *)data, blendType);
|
||||
mRenderTarget.endRenderPass();
|
||||
// blend shape with current render storage
|
||||
WgBindGroupBlendMethod* blendMethod = mBlendMethodPool.allocate(mContext, mBlendMethod);
|
||||
|
|
Loading…
Add table
Reference in a new issue