浏览代码

Merge pull request #262 from UnityTech/antialias

Antialias
/main
GitHub 5 年前
当前提交
2c3f1026
共有 13 个文件被更改,包括 1173 次插入456 次删除
  1. 9
      Runtime/Resources/UIWidgets_canvas.cginc
  2. 669
      Runtime/ui/painting/path.cs
  3. 15
      Runtime/ui/painting/picture.cs
  4. 3
      Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_clip.cs
  5. 134
      Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs
  6. 35
      Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_shader.cs
  7. 432
      Runtime/ui/renderer/common/geometry/path/path_cache.cs
  8. 91
      Runtime/ui/renderer/common/geometry/path/path_utils.cs
  9. 10
      Runtime/ui/renderer/common/picture.cs
  10. 65
      Runtime/Resources/UIWidgets_canvas_strokeAlpha.shader
  11. 9
      Runtime/Resources/UIWidgets_canvas_strokeAlpha.shader.meta
  12. 146
      Samples/UIWidgetSample/AntialiasSVGSample.cs
  13. 11
      Samples/UIWidgetSample/AntialiasSVGSample.cs.meta

9
Runtime/Resources/UIWidgets_canvas.cginc


half4 _color;
fixed _alpha;
fixed _strokeMult;
half4x4 _shaderMat;
sampler2D _shaderTex;
half4 _leftColor;

return color;
}
float strokeMask(float u, float v) {
return min(1.0, (1.0 - abs(u * 2.0 - 1.0)) * 1.0) * min(1.0, v);
}
fixed4 frag_stroke_alpha(v2f i) : SV_Target {
return shader_color(i) * strokeMask(i.ftcoord.x, i.ftcoord.y);
}

669
Runtime/ui/painting/path.cs


using System.Text;
using Unity.UIWidgets.foundation;
using UnityEngine;
using Vector2 = UnityEngine.Vector2;
using Vector3 = UnityEngine.Vector3;
struct VertexUV {
public List<Vector3> fillVertices;
public List<Vector2> fillUV;
public List<Vector3> strokeVertices;
public List<Vector2> strokeUV;
}
public class Path {
const float _KAPPA90 = 0.5522847493f;

float _minX, _minY;
float _maxX, _maxY;
PathCache _cache;
static uint pathGlobalKey = 0;

public uint pathKey {
get {
return this._pathKey;
}
get { return this._pathKey; }
}
public Path(int capacity = 128) {

public List<float> commands => this._commands;
public List<float> commands {
get { return this._commands; }
}
public override string ToString() {
var sb = new StringBuilder("Path: count = " + this._commands.Count);

internal PathCache flatten(float scale) {
scale = Mathf.Round(scale * 2.0f) / 2.0f; // round to 0.5f
this._cache = new PathCache(scale);
var i = 0;

if (x < this._minX) {
this._minX = x;
}
if (y < this._minY) {
this._minY = y;
}

}
if (y > this._maxY) {
this._maxY = y;
}

this._commandx = x;
this._commandy = y;
this._pathKey = pathGlobalKey++;
this._cache = null;
}

this._commandx = x;
this._commandy = y;
this._pathKey = pathGlobalKey++;
this._cache = null;
}

this._expandBounds(x1, y1);
this._expandBounds(x2, y2);
this._expandBounds(x3, y3);
this._commands.Add((float) PathCommand.bezierTo);
this._commands.Add(x1);
this._commands.Add(y1);

this._commands.Add(y3);
this._pathKey = pathGlobalKey++;
this._cache = null;
}

this._commands.Add(winding);
this._pathKey = pathGlobalKey++;
this._cache = null;
}

var y0 = this._commandy;
this._appendMoveTo(x + x0, y + y0);
}

public void relativeLineTo(float x, float y) {
var x0 = this._commandx;
var y0 = this._commandy;
public void lineTo(float x, float y) {
this._appendLineTo(x, y);
}

}
this.cubicTo(x0 + c1x, y0 + c1y, x0 + c2x, y0 + c2y, x0 + x, y0 + y);
}

if (!(w > 0)) {
this.lineTo(x2, y2);
return;
}
}
}
}
if (w == 1) {
this.quadraticBezierTo(x1, y1, x2, y2);
return;

var quadX = new float[5];
var quadY = new float[5];
conic.chopIntoQuadsPOW2(quadX, quadY, 1);
this.quadraticBezierTo(quadX[1], quadY[1], quadX[2], quadY[2]);
this.quadraticBezierTo(quadX[3], quadY[3], quadX[4], quadY[4]);
}

var y0 = this._commandy;
this.conicTo(x0 + x1, y0 + y1, x0 + x2, y0 + y2, w);
}

var squareRy = ry * ry;
var squareX = transformedMidPoint.dx * transformedMidPoint.dx;
var squareY = transformedMidPoint.dy * transformedMidPoint.dy;
// Check if the radii are big enough to draw the arc, scale radii if not.
// http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
var radiiScale = squareX / squareRx + squareY / squareRy;

ry *= radiiScale;
}
var unitPts = new [] {
var unitPts = new[] {
if (!clockwise != largeArc) { // flipped from the original implementation
if (!clockwise != largeArc) {
// flipped from the original implementation
if (thetaArc < 0 && clockwise) { // arcSweep flipped from the original implementation
if (thetaArc < 0 && clockwise) {
// arcSweep flipped from the original implementation
} else if (thetaArc > 0 && !clockwise) { // arcSweep flipped from the original implementation
}
else if (thetaArc > 0 && !clockwise) {
// arcSweep flipped from the original implementation
// the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd
int segments = Mathf.CeilToInt(Mathf.Abs(thetaArc / (2 * Mathf.PI / 3)));
var thetaWidth = thetaArc / segments;

}
bool expectIntegers = ScalarUtils.ScalarNearlyZero(Mathf.PI/2 - Mathf.Abs(thetaWidth)) &&
bool expectIntegers = ScalarUtils.ScalarNearlyZero(Mathf.PI / 2 - Mathf.Abs(thetaWidth)) &&
ScalarUtils.ScalarIsInteger(rx) && ScalarUtils.ScalarIsInteger(ry) &&
ScalarUtils.ScalarIsInteger(x1) && ScalarUtils.ScalarIsInteger(y1);

unitPts[1] += centerPoint;
unitPts[0] = unitPts[1];
unitPts[0] = unitPts[0].translate(t * sinEndTheta, -t * cosEndTheta);
var mapped = new [] {
var mapped = new[] {
/*
Computing the arc width introduces rounding errors that cause arcs to start
outside their marks. A round rect may lose convexity as a result. If the input

a0 = Mathf.Atan2(dx0, -dy0);
a1 = Mathf.Atan2(-dx1, dy1);
dir = PathWinding.clockwise;
} else {
}
else {
cx = x1 + dx0 * d + -dy0 * radius;
cy = y1 + dy0 * d + dx0 * radius;
a0 = Mathf.Atan2(-dx0, dy0);

if (dir == PathWinding.clockwise) {
if (Mathf.Abs(da) >= Mathf.PI * 2) {
da = Mathf.PI * 2;
} else {
}
else {
} else {
}
else {
} else {
}
else {
// Split arc into max 90 degree segments.
int ndivs = Mathf.Max(1, Mathf.Min((int) (Mathf.Abs(da) / (Mathf.PI * 0.5f) + 0.5f), 5));
float hda = (da / ndivs) / 2.0f;

if (move == PathCommand.moveTo) {
this._appendMoveTo(x1, y1);
} else {
}
else {
} else {
}
else {
float c1x = px + ptanx;
float c1y = py + ptany;
float c2x = x - tanx;

this._appendBezierTo(c1x, c1y, c2x, c2y, x1, y1);
}
px = x;
py = y;
ptanx = tanx;

if (points.Count == 0) {
return;
}
this._appendMoveTo(points[0].dx, points[0].dy);
for (int i = 1; i < points.Count; i++) {

this.addPath(path);
return;
}
var transform = Matrix3.makeTrans(offset.dx, offset.dy);
this.addPath(path, transform);
}

if (transform != null) {
transform.mapXY(x, y, out x, out y);
}
this._appendMoveTo(x, y);
}
i += 3;

if (transform != null) {
transform.mapXY(x, y, out x, out y);
}
this._appendLineTo(x, y);
}
i += 3;

transform.mapXY(c2x, c2y, out c2x, out c2y);
transform.mapXY(x1, y1, out x1, out y1);
}
this._appendBezierTo(c1x, c1y, c2x, c2y, x1, y1);
}
i += 7;

return totalW != 0;
}
static int windingLine(float x0, float y0, float x1, float y1, float x, float y) {
if (y0 == y1) {
return 0;

var closerY = Mathf.Abs(midY - startY) < Mathf.Abs(midY - endY) ? startY : endY;
c1.y2 = c2.y0 = closerY;
}
// Verify that all five points are in order.
D.assert(_between(startY, c1.y1, c1.y2));
D.assert(_between(c1.y1, c1.y2, c2.y1));

readonly ArrayRef<PathPath> _paths = new ArrayRef<PathPath>();
readonly ArrayRef<PathPoint> _points = new ArrayRef<PathPoint>();
List<Vector3> _vertices = null;
List<Vector2> _uv = null;
List<Vector3> _strokeVertices = null;
List<Vector2> _strokeUV = null;
bool _fillConvex;
bool _fillConvex;
public MeshMesh fillMesh {
get { return this._fillMesh; }
}
public MeshMesh strokeMesh {
get { return this._strokeMesh; }
}
float _fringe;
public PathCache(float scale) {
this._scale = scale;

this.addPath();
this.addPoint(0, 0, PointFlags.corner);
}
ref var path = ref this._paths.array[this._paths.length - 1];
if (path.count > 0) {
ref var pt = ref this._points.array[this._points.length - 1];

y = point.y + y1,
flags = flags,
});
} else {
}
else {
this._addPoint(new PathPoint {
x = point.x + x1,
y = point.y + y1,

public void normalize() {
var points = this._points;
var paths = this._paths;
var paths = this._paths;
for (var j = 0; j < paths.length; j++) {
ref var path = ref paths.array[j];
if (path.count <= 1) {

}
}
void _expandFill() {
var points = this._points;
var paths = this._paths;
for (var j = 0; j < paths.length; j++) {
ref var path = ref paths.array[j];
if (path.count <= 2) {
continue;
}
var ip0 = path.first + path.count - 1;
var ip1 = path.first;
for (var i = 0; i < path.count; i++) {
ref var p0 = ref points.array[ip0];
ref var p1 = ref points.array[ip1];
p0.dx = p1.x - p0.x; // no need to normalize
p0.dy = p1.y - p0.y;
ip0 = ip1++;
}
path.convex = true;
ip0 = path.first + path.count - 1;
ip1 = path.first;
for (var i = 0; i < path.count; i++) {
ref var p0 = ref points.array[ip0];
ref var p1 = ref points.array[ip1];
float cross = p1.dx * p0.dy - p0.dx * p1.dy;
if (cross < 0.0f) {
path.convex = false;
}
ip0 = ip1++;
}
}
var cvertices = 0;
for (var i = 0; i < paths.length; i++) {
ref var path = ref paths.array[i];
if (path.count <= 2) {
continue;
}
cvertices += path.count;
}
this._vertices = new List<Vector3>(cvertices);
for (var i = 0; i < paths.length; i++) {
ref var path = ref paths.array[i];
if (path.count <= 2) {
continue;
}
path.ifill = this._vertices.Count;
for (var j = 0; j < path.count; j++) {
ref var p = ref points.array[path.first + j];
this._vertices.Add(new Vector2(p.x, p.y));
}
path.nfill = this._vertices.Count - path.ifill;
}
}
public MeshMesh getFillMesh(out bool convex) {
if (this._fillMesh != null) {
convex = this._fillConvex;
return this._fillMesh;
}
this._expandFill();
var paths = this._paths;
var cindices = 0;
for (var i = 0; i < paths.length; i++) {
ref var path = ref paths.array[i];
if (path.count <= 2) {
continue;
}
if (path.nfill > 0) {
D.assert(path.nfill >= 2);
cindices += (path.nfill - 2) * 3;
}
}
var indices = new List<int>(cindices);
for (var i = 0; i < paths.length; i++) {
ref var path = ref paths.array[i];
if (path.count <= 2) {
continue;
}
if (path.nfill > 0) {
for (var j = 2; j < path.nfill; j++) {
indices.Add(path.ifill);
indices.Add(path.ifill + j);
indices.Add(path.ifill + j - 1);
}
}
}
D.assert(indices.Count == cindices);
var mesh = new MeshMesh(null, this._vertices, indices);
this._fillMesh = mesh;
this._fillConvex = false;
for (var i = 0; i < paths.length; i++) {
ref var path = ref paths.array[i];
if (path.count <= 2) {
continue;
}
if (this._fillConvex) {
// if more than two paths, convex is false.
this._fillConvex = false;
break;
}
if (!path.convex) {
// if not convex, convex is false.
break;
}
this._fillConvex = true;
}
convex = this._fillConvex;
return this._fillMesh;
}
void _calculateJoins(float w, StrokeJoin lineJoin, float miterLimit) {
float iw = w > 0.0f ? 1.0f / w : 0.0f;

}
}
void _expandStroke(float w, StrokeCap lineCap, StrokeJoin lineJoin, float miterLimit) {
this._calculateJoins(w, lineJoin, miterLimit);
VertexUV _expandStroke(float w, float fringe, StrokeCap lineCap, StrokeJoin lineJoin, float miterLimit) {
float aa = fringe;
float u0 = 0.0f, u1 = 1.0f;
ncap = PathUtils.curveDivs(w, Mathf.PI, this._tessTol);
ncap = uiPathUtils.curveDivs(w, Mathf.PI, this._tessTol);
}
w += aa * 0.5f;
if (aa == 0.0f) {
u0 = 0.5f;
u1 = 0.5f;
this._calculateJoins(w, lineJoin, miterLimit);
ref var path = ref paths.array[i];
var path = paths.array[i];
cvertices += 4;
cvertices += 8;
this._uv = new List<Vector2>(cvertices);
ref var path = ref paths.array[i];
var path = paths.array[i];
if (path.count <= 1) {
continue;
}

e = path.count - 1;
}
ref var p0 = ref points.array[ip0];
ref var p1 = ref points.array[ip1];
var p0 = points.array[ip0];
var p1 = points.array[ip1];
this._vertices.buttCapStart(p0, p0.dx, p0.dy, w, 0.0f);
this._vertices.buttCapStart(this._uv, p0, p0.dx, p0.dy, w, 0.0f, aa, u0, u1);
this._vertices.buttCapStart(p0, p0.dx, p0.dy, w, w);
this._vertices.buttCapStart(this._uv, p0, p0.dx, p0.dy, w, w, aa, u0, u1);
this._vertices.roundCapStart(p0, p0.dx, p0.dy, w, ncap);
this._vertices.roundCapStart(this._uv, p0, p0.dx, p0.dy, w, ncap, u0, u1);
p0 = ref points.array[ip0];
p1 = ref points.array[ip1];
p0 = points.array[ip0];
p1 = points.array[ip1];
this._vertices.roundJoin(p0, p1, w, w, ncap);
this._vertices.roundJoin(this._uv, p0, p1, w, w, ncap, u0, u1, aa);
this._vertices.bevelJoin(p0, p1, w, w);
this._vertices.bevelJoin(this._uv, p0, p1, w, w, u0, u1, aa);
this._uv.Add(new Vector2(u0, 1));
this._uv.Add(new Vector2(u1, 1));
}
ip0 = ip1++;

p0 = ref points.array[ip0];
p1 = ref points.array[ip1];
p0 = points.array[ip0];
p1 = points.array[ip1];
this._vertices.buttCapEnd(p1, p0.dx, p0.dy, w, 0.0f);
this._vertices.buttCapEnd(this._uv, p1, p0.dx, p0.dy, w, 0.0f, aa, u0, u1);
this._vertices.buttCapEnd(p1, p0.dx, p0.dy, w, w);
this._vertices.buttCapEnd(this._uv, p1, p0.dx, p0.dy, w, w, aa, u0, u1);
this._vertices.roundCapEnd(p1, p0.dx, p0.dy, w, ncap);
this._vertices.roundCapEnd(this._uv, p1, p0.dx, p0.dy, w, ncap, u0, u1);
this._uv.Add(new Vector2(u0, 1));
this._uv.Add(new Vector2(u1, 1));
paths.array[i] = path;
D.assert(this._uv.Count == this._vertices.Count);
return new VertexUV {
strokeVertices = this._vertices,
strokeUV = this._uv,
};
public MeshMesh getStrokeMesh(float strokeWidth, StrokeCap lineCap, StrokeJoin lineJoin, float miterLimit) {
VertexUV _expandFill(float fringe) {
float aa = fringe;
float woff = aa * 0.5f;
var points = this._points;
var paths = this._paths;
this._calculateJoins(fringe, StrokeJoin.miter, 4.0f);
var cvertices = 0;
for (var i = 0; i < paths.length; i++) {
var path = paths.array[i];
if (path.count <= 2) {
continue;
}
cvertices += path.count;
}
this._fillConvex = false;
for (var i = 0; i < paths.length; i++) {
var path = paths.array[i];
if (path.count <= 2) {
continue;
}
if (this._fillConvex) {
// if more than two paths, convex is false.
this._fillConvex = false;
break;
}
if (!path.convex) {
// if not convex, convex is false.
break;
}
this._fillConvex = true;
}
this._vertices = new List<Vector3>(cvertices);
this._uv = new List<Vector2>(cvertices);
for (var i = 0; i < paths.length; i++) {
var path = paths.array[i];
if (path.count <= 2) {
continue;
}
path.ifill = this._vertices.Count;
for (var j = 0; j < path.count; j++) {
var p = points.array[path.first + j];
if (aa > 0.0f) {
this._vertices.Add(new Vector2(p.x + p.dmx * woff, p.y + p.dmy * woff));
}
else {
this._vertices.Add(new Vector2(p.x, p.y));
}
this._uv.Add(new Vector2(0.5f, 1.0f));
}
path.nfill = this._vertices.Count - path.ifill;
paths.array[i] = path;
}
if (aa > 0.0f) {
this._strokeVertices = new List<Vector3>();
this._strokeUV = new List<Vector2>();
cvertices = 0;
for (var i = 0; i < paths.length; i++) {
var path = paths.array[i];
if (path.count <= 2) {
continue;
}
cvertices += path.count * 2;
}
this._strokeVertices.Capacity = cvertices;
this._strokeUV.Capacity = cvertices;
float lw = this._fillConvex ? woff : aa + woff;
float rw = aa - woff;
float lu = this._fillConvex ? 0.5f : 0.0f;
float ru = 1.0f;
for (var i = 0; i < paths.length; i++) {
var path = paths.array[i];
if (path.count <= 2) {
continue;
}
path.istroke = this._strokeVertices.Count;
for (var j = 0; j < path.count; j++) {
var p = points.array[path.first + j];
this._strokeVertices.Add(new Vector2(p.x + p.dmx * lw, p.y + p.dmy * lw));
this._strokeUV.Add(new Vector2(lu, 1.0f));
this._strokeVertices.Add(new Vector2(p.x - p.dmx * rw, p.y - p.dmy * rw));
this._strokeUV.Add(new Vector2(ru, 1.0f));
}
path.nstroke = this._strokeVertices.Count - path.istroke;
paths.array[i] = path;
}
}
return new VertexUV {
fillVertices = this._vertices,
fillUV = this._uv,
strokeVertices = this._strokeVertices,
strokeUV = this._strokeUV,
};
}
public void computeStrokeMesh(float strokeWidth, float fringe, StrokeCap lineCap, StrokeJoin lineJoin,
float miterLimit) {
this._fillMesh == null && // Ensure that the cached stroke mesh was not calculated in computeFillMesh
this._fringe == fringe &&
return this._strokeMesh;
return;
this._expandStroke(strokeWidth, lineCap, lineJoin, miterLimit);
var verticesUV = this._expandStroke(strokeWidth, fringe, lineCap, lineJoin, miterLimit);
ref var path = ref paths.array[i];
var path = paths.array[i];
if (path.count <= 1) {
continue;
}

var indices = new List<int>(cindices);
for (var i = 0; i < paths.length; i++) {
ref var path = ref paths.array[i];
var path = paths.array[i];
if (path.count <= 1) {
continue;
}

D.assert(indices.Count == cindices);
this._strokeMesh = new MeshMesh(null, this._vertices, indices);
this._strokeMesh = new MeshMesh(null, verticesUV.strokeVertices, indices, verticesUV.strokeUV);
this._fillMesh = null;
this._fringe = fringe;
return this._strokeMesh;
}
public void computeFillMesh(float fringe, out bool convex) {
if (this._fillMesh != null && (fringe != 0.0f || this._strokeMesh != null) && this._fringe == fringe) {
convex = this._fillConvex;
return;
}
var verticesUV = this._expandFill(fringe);
convex = this._fillConvex;
var paths = this._paths;
var cindices = 0;
for (var i = 0; i < paths.length; i++) {
var path = paths.array[i];
if (path.count <= 2) {
continue;
}
if (path.nfill > 0) {
D.assert(path.nfill >= 2);
cindices += (path.nfill - 2) * 3;
}
}
var indices = new List<int>(cindices);
for (var i = 0; i < paths.length; i++) {
var path = paths.array[i];
if (path.count <= 2) {
continue;
}
if (path.nfill > 0) {
for (var j = 2; j < path.nfill; j++) {
indices.Add(path.ifill);
indices.Add(path.ifill + j);
indices.Add(path.ifill + j - 1);
}
}
}
D.assert(indices.Count == cindices);
if (verticesUV.strokeVertices != null) {
cindices = 0;
for (var i = 0; i < paths.length; i++) {
var path = paths.array[i];
if (path.count <= 2) {
continue;
}
if (path.nstroke > 0) {
D.assert(path.nstroke >= 6);
cindices += path.nstroke * 3;
}
}
var strokeIndices = new List<int>(cindices);
for (var i = 0; i < paths.length; i++) {
var path = paths.array[i];
if (path.count <= 2) {
continue;
}
if (path.nstroke > 0) {
strokeIndices.Add(path.istroke + path.nstroke - 1);
strokeIndices.Add(path.istroke + path.nstroke - 2);
strokeIndices.Add(path.istroke);
strokeIndices.Add(path.istroke + path.nstroke - 1);
strokeIndices.Add(path.istroke);
strokeIndices.Add(path.istroke + 1);
for (var j = 2; j < path.nstroke; j++) {
if ((j & 1) == 0) {
strokeIndices.Add(path.istroke + j - 1);
strokeIndices.Add(path.istroke + j - 2);
strokeIndices.Add(path.istroke + j);
}
else {
strokeIndices.Add(path.istroke + j - 2);
strokeIndices.Add(path.istroke + j - 1);
strokeIndices.Add(path.istroke + j);
}
}
}
}
D.assert(strokeIndices.Count == cindices);
this._strokeMesh = new MeshMesh(null, verticesUV.strokeVertices, strokeIndices, verticesUV.strokeUV);
}
var mesh = new MeshMesh(null, verticesUV.fillVertices, indices, verticesUV.fillUV);
this._fillMesh = mesh;
this._fringe = fringe;
}
}

return d;
}
public static void buttCapStart(this List<Vector3> dst, PathPoint p,
float dx, float dy, float w, float d) {
public static void buttCapStart(this List<Vector3> dst, List<Vector2> uv, PathPoint p,
float dx, float dy, float w, float d, float aa, float u0, float u1) {
dst.Add(new Vector2(px + dlx * w - dx * aa, py + dly * w - dy * aa));
dst.Add(new Vector2(px - dlx * w - dx * aa, py - dly * w - dy * aa));
uv.Add(new Vector2(u0, 0));
uv.Add(new Vector2(u1, 0));
uv.Add(new Vector2(u0, 1));
uv.Add(new Vector2(u1, 1));
public static void buttCapEnd(this List<Vector3> dst, PathPoint p,
float dx, float dy, float w, float d) {
public static void buttCapEnd(this List<Vector3> dst, List<Vector2> uv, PathPoint p,
float dx, float dy, float w, float d, float aa, float u0, float u1) {
float px = p.x + dx * d;
float py = p.y + dy * d;
float dlx = dy;

dst.Add(new Vector2(px - dlx * w, py - dly * w));
dst.Add(new Vector2(px + dlx * w + dx * aa, py + dly * w + dy * aa));
dst.Add(new Vector2(px - dlx * w + dx * aa, py - dly * w + dy * aa));
uv.Add(new Vector2(u0, 1));
uv.Add(new Vector2(u1, 1));
uv.Add(new Vector2(u0, 0));
uv.Add(new Vector2(u1, 0));
public static void roundCapStart(this List<Vector3> dst, PathPoint p,
float dx, float dy, float w, int ncap) {
public static void roundCapStart(this List<Vector3> dst, List<Vector2> uv, PathPoint p,
float dx, float dy, float w, int ncap, float u0, float u1) {
float px = p.x;
float py = p.y;
float dlx = dy;

float ax = Mathf.Cos(a) * w, ay = Mathf.Sin(a) * w;
dst.Add(new Vector2(px - dlx * ax - dx * ay, py - dly * ax - dy * ay));
dst.Add(new Vector2(px, py));
uv.Add(new Vector2(u0, 1));
uv.Add(new Vector2(0.5f, 1));
uv.Add(new Vector2(u0, 1));
uv.Add(new Vector2(u1, 1));
public static void roundCapEnd(this List<Vector3> dst, PathPoint p,
float dx, float dy, float w, int ncap) {
public static void roundCapEnd(this List<Vector3> dst, List<Vector2> uv, PathPoint p,
float dx, float dy, float w, int ncap, float u0, float u1) {
float px = p.x;
float py = p.y;
float dlx = dy;

dst.Add(new Vector2(px - dlx * w, py - dly * w));
uv.Add(new Vector2(u0, 1));
uv.Add(new Vector2(u1, 1));
for (var i = 0; i < ncap; i++) {
float a = (float) i / (ncap - 1) * Mathf.PI;

uv.Add(new Vector2(0.5f, 1));
uv.Add(new Vector2(u0, 1));
}
}

return Mathf.Max(2, Mathf.CeilToInt(arc / da));
}
public static void roundJoin(this List<Vector3> dst, PathPoint p0, PathPoint p1,
float lw, float rw, int ncap) {
public static void roundJoin(this List<Vector3> dst, List<Vector2> uv, PathPoint p0, PathPoint p1,
float lw, float rw, int ncap, float lu, float ru, float fringe) {
float dlx0 = p0.dy;
float dly0 = -p0.dx;
float dlx1 = p1.dy;

dst.Add(new Vector2(lx0, ly0));
dst.Add(new Vector2(p1.x - dlx0 * rw, p1.y - dly0 * rw));
uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(ru, 1));
var n = Mathf.CeilToInt((a0 - a1) / Mathf.PI * ncap).clamp(2, ncap);
for (var i = 0; i < n; i++) {

dst.Add(new Vector2(p1.x, p1.y));
dst.Add(new Vector2(rx, ry));
uv.Add(new Vector2(0.5f, 1));
uv.Add(new Vector2(ru, 1));
uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(ru, 1));
}
else {
float rx0, ry0, rx1, ry1;

dst.Add(new Vector2(p1.x + dlx0 * lw, p1.y + dly0 * lw));
dst.Add(new Vector2(rx0, ry0));
uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(ru, 1));
var n = Mathf.CeilToInt((a1 - a0) / Mathf.PI * ncap).clamp(2, ncap);
for (var i = 0; i < n; i++) {

dst.Add(new Vector2(lx, ly));
dst.Add(new Vector2(p1.x, p1.y));
uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(0.5f, 1));
uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(ru, 1));
public static void bevelJoin(this List<Vector3> dst, PathPoint p0, PathPoint p1,
float lw, float rw) {
public static void bevelJoin(this List<Vector3> dst, List<Vector2> uv, PathPoint p0, PathPoint p1,
float lw, float rw, float lu, float ru, float fringe) {
float rx0, ry0, rx1, ry1;
float lx0, ly0, lx1, ly1;

dst.Add(new Vector2 {x = lx0, y = ly0});
dst.Add(new Vector2 {x = p1.x - dlx0 * rw, y = p1.y - dly0 * rw});
uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(ru, 1));
if ((p1.flags & PointFlags.bevel) != 0) {
dst.Add(new Vector2(lx0, ly0));

uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(ru, 1));
uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(ru, 1));
}
else {
rx0 = p1.x - p1.dmx * rw;

dst.Add(new Vector2(rx0, ry0));
dst.Add(new Vector2(p1.x, p1.y));
dst.Add(new Vector2(p1.x - dlx1 * rw, p1.y - dly1 * rw));
uv.Add(new Vector2(0.5f, 1));
uv.Add(new Vector2(ru, 1));
uv.Add(new Vector2(ru, 1));
uv.Add(new Vector2(ru, 1));
uv.Add(new Vector2(0.5f, 1));
uv.Add(new Vector2(ru, 1));
uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(ru, 1));
}
else {
chooseBevel((p1.flags & PointFlags.innerBevel) != 0, p0, p1, -rw,

dst.Add(new Vector2(rx0, ry0));
uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(ru, 1));
if ((p1.flags & PointFlags.bevel) != 0) {
dst.Add(new Vector2(p1.x + dlx0 * lw, p1.y + dly0 * lw));

uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(ru, 1));
uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(ru, 1));
}
else {
lx0 = p1.x + p1.dmx * lw;

dst.Add(new Vector2(lx0, ly0));
dst.Add(new Vector2(p1.x + dlx1 * lw, p1.y + dly1 * lw));
dst.Add(new Vector2(p1.x, p1.y));
uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(0.5f, 1));
uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(0.5f, 1));
uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(ru, 1));
}
}
}

15
Runtime/ui/painting/picture.cs


var state = this._getState();
var scale = XformUtils.getScale(state.xform);
var rect = cmd.path.flatten(
var cache = cmd.path.flatten(
).getFillMesh(out _).transform(state.xform).bounds;
);
cache.computeFillMesh(0.0f, out _);
var rect = cache.fillMesh.transform(state.xform).bounds;
state.scissor = state.scissor == null ? rect : state.scissor.intersect(rect);
break;
}

MeshMesh mesh;
if (paint.style == PaintingStyle.fill) {
var cache = path.flatten(scale * devicePixelRatio);
mesh = cache.getFillMesh(out _).transform(state.xform);
cache.computeFillMesh(0.0f, out _);
mesh = cache.fillMesh.transform(state.xform);
}
else {
float strokeWidth = (paint.strokeWidth * scale).clamp(0, 200.0f);

}
var cache = path.flatten(scale * devicePixelRatio);
mesh = cache.getStrokeMesh(
cache.computeStrokeMesh(
0.0f,
paint.strokeMiterLimit).transform(state.xform);
paint.strokeMiterLimit);
mesh = cache.strokeMesh.transform(state.xform);
}
if (paint.maskFilter != null && paint.maskFilter.sigma != 0) {

3
Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_clip.cs


newElement.saveCount = saveCount;
var pathCache = uiPath.flatten(scale);
var fillMesh = pathCache.getFillMesh(out newElement.convex);
pathCache.computeFillMesh(0.0f, out newElement.convex);
var fillMesh = pathCache.fillMesh;
newElement.mesh = fillMesh.transform(matrix);
var vertices = newElement.mesh.vertices;

134
Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs


RenderLayer _createMaskLayer(RenderLayer parentLayer, uiRect maskBounds,
_drawPathDrawMeshCallbackDelegate drawCallback,
uiPaint paint, bool convex, float alpha, Texture tex, uiRect texBound, TextBlobMesh textMesh,
uiMeshMesh mesh, bool notEmoji) {
uiPaint paint, bool convex, float alpha, float strokeMult, Texture tex, uiRect texBound,
TextBlobMesh textMesh,
uiMeshMesh fillMesh, uiMeshMesh strokeMesh, bool notEmoji) {
var textureWidth = Mathf.CeilToInt(maskBounds.width * this._devicePixelRatio);
if (textureWidth < 1) {
textureWidth = 1;

var maskState = maskLayer.states[maskLayer.states.Count - 1];
maskState.matrix = parentState.matrix;
drawCallback.Invoke(uiPaint.shapeOnly(paint), mesh, convex, alpha, tex, texBound, textMesh, notEmoji);
drawCallback.Invoke(uiPaint.shapeOnly(paint), fillMesh, strokeMesh, convex, alpha, strokeMult, tex,
texBound, textMesh, notEmoji);
var removed = this._layers.removeLast();
D.assert(removed == maskLayer);

}
void _drawWithMaskFilter(uiRect meshBounds, uiPaint paint, uiMaskFilter maskFilter,
uiMeshMesh mesh, bool convex, float alpha, Texture tex, uiRect texBound, TextBlobMesh textMesh, bool notEmoji,
uiMeshMesh fillMesh, uiMeshMesh strokeMesh, bool convex, float alpha, float strokeMult, Texture tex,
uiRect texBound, TextBlobMesh textMesh, bool notEmoji,
_drawPathDrawMeshCallbackDelegate drawCallback) {
var layer = this._currentLayer;
var clipBounds = layer.layerBounds;

}
if (clipBounds.isEmpty) {
this._drawPathDrawMeshQuit(mesh, textMesh);
this._drawPathDrawMeshQuit(fillMesh, strokeMesh, textMesh);
return;
}

this._drawPathDrawMeshQuit(mesh, textMesh);
this._drawPathDrawMeshQuit(fillMesh, strokeMesh, textMesh);
return;
}

if (maskBounds.isEmpty) {
this._drawPathDrawMeshQuit(mesh, textMesh);
this._drawPathDrawMeshQuit(fillMesh, strokeMesh, textMesh);
var maskLayer = this._createMaskLayer(layer, maskBounds, drawCallback, paint, convex, alpha, tex, texBound,
textMesh, mesh, notEmoji);
var maskLayer = this._createMaskLayer(layer, maskBounds, drawCallback, paint, convex, alpha, strokeMult,
tex, texBound,
textMesh, fillMesh, strokeMesh, notEmoji);
var blurLayer = this._createBlurLayer(maskLayer, sigma, sigma, layer);

layer.draws.Add(CanvasShader.texRT(layer, paint, blurMesh, blurLayer));
}
delegate void _drawPathDrawMeshCallbackDelegate(uiPaint p, uiMeshMesh mesh, bool convex, float alpha,
delegate void _drawPathDrawMeshCallbackDelegate(uiPaint p, uiMeshMesh fillMesh, uiMeshMesh strokeMesh,
bool convex, float alpha, float strokeMult,
void _drawPathDrawMeshCallback(uiPaint p, uiMeshMesh mesh, bool convex, float alpha, Texture tex,
void _drawPathDrawMeshCallback(uiPaint p, uiMeshMesh fillMesh, uiMeshMesh strokeMesh, bool convex, float alpha,
float strokeMult, Texture tex,
if (!this._applyClip(mesh.bounds)) {
ObjectPool<uiMeshMesh>.release(mesh);
if (!this._applyClip(fillMesh.bounds)) {
ObjectPool<uiMeshMesh>.release(fillMesh);
ObjectPool<uiMeshMesh>.release(strokeMesh);
layer.draws.Add(CanvasShader.convexFill(layer, p, mesh));
layer.draws.Add(CanvasShader.convexFill(layer, p, fillMesh));
layer.draws.Add(CanvasShader.fill0(layer, mesh));
layer.draws.Add(CanvasShader.fill1(layer, p, mesh.boundsMesh));
layer.draws.Add(CanvasShader.fill0(layer, fillMesh));
layer.draws.Add(CanvasShader.fill1(layer, p, fillMesh.boundsMesh));
}
if (strokeMesh != null) {
layer.draws.Add(CanvasShader.strokeAlpha(layer, p, alpha, strokeMult, strokeMesh));
layer.draws.Add(CanvasShader.stroke1(layer, strokeMesh.duplicate()));
void _drawPathDrawMeshCallback2(uiPaint p, uiMeshMesh mesh, bool convex, float alpha, Texture tex,
void _drawPathDrawMeshCallback2(uiPaint p, uiMeshMesh fillMesh, uiMeshMesh strokeMesh, bool convex, float alpha,
float strokeMult, Texture tex,
if (!this._applyClip(mesh.bounds)) {
ObjectPool<uiMeshMesh>.release(mesh);
if (!this._applyClip(strokeMesh.bounds)) {
ObjectPool<uiMeshMesh>.release(strokeMesh);
layer.draws.Add(CanvasShader.stroke0(layer, p, alpha, mesh));
layer.draws.Add(CanvasShader.stroke1(layer, mesh.duplicate()));
layer.draws.Add(CanvasShader.strokeAlpha(layer, p, alpha, strokeMult, strokeMesh));
layer.draws.Add(CanvasShader.stroke1(layer, strokeMesh.duplicate()));
void _drawTextDrawMeshCallback(uiPaint p, uiMeshMesh mesh, bool convex, float alpha, Texture tex,
void _drawTextDrawMeshCallback(uiPaint p, uiMeshMesh fillMesh, uiMeshMesh strokeMesh, bool convex, float alpha,
float strokeMult, Texture tex,
uiRect textBlobBounds, TextBlobMesh textMesh, bool notEmoji) {
if (!this._applyClip(textBlobBounds)) {
ObjectPool<TextBlobMesh>.release(textMesh);

}
}
void _drawPathDrawMeshQuit(uiMeshMesh mesh, TextBlobMesh textMesh) {
ObjectPool<uiMeshMesh>.release(mesh);
void _drawPathDrawMeshQuit(uiMeshMesh fillMesh, uiMeshMesh strokeMesh, TextBlobMesh textMesh) {
ObjectPool<uiMeshMesh>.release(fillMesh);
ObjectPool<uiMeshMesh>.release(strokeMesh);
ObjectPool<TextBlobMesh>.release(textMesh);
}

var cache = path.flatten(state.scale * this._devicePixelRatio);
bool convex;
var fillMesh = cache.getFillMesh(out convex);
var mesh = fillMesh.transform(state.matrix);
cache.computeFillMesh(this._fringeWidth, out convex);
var fillMesh = cache.fillMesh;
var strokeMesh = cache.strokeMesh;
var fmesh = fillMesh.transform(state.matrix);
var smesh = strokeMesh?.transform(state.matrix);
float strokeMult = 1.0f;
this._drawWithMaskFilter(mesh.bounds, paint, paint.maskFilter.Value, mesh, convex, 0, null,
this._drawWithMaskFilter(fmesh.bounds, paint, paint.maskFilter.Value, fmesh, smesh, convex, 0,
strokeMult, null,
this._drawPathDrawMeshCallback(paint, mesh, convex, 0, null, uiRectHelper.zero, null, false);
this._drawPathDrawMeshCallback(paint, fmesh, smesh, convex, 1.0f, strokeMult, null, uiRectHelper.zero,
null, false);
}
else {
var state = this._currentLayer.currentState;

strokeWidth = this._fringeWidth;
}
strokeWidth = strokeWidth / state.scale * 0.5f;
float strokeMult = (this._fringeWidth * 0.5f + strokeWidth * 0.5f) / this._fringeWidth;
var strokenMesh = cache.getStrokeMesh(
strokeWidth / state.scale * 0.5f,
cache.computeStrokeMesh(
strokeWidth,
this._fringeWidth,
var strokeMesh = cache.strokeMesh;
var mesh = strokenMesh.transform(state.matrix);
var mesh = strokeMesh.transform(state.matrix);
this._drawWithMaskFilter(mesh.bounds, paint, paint.maskFilter.Value, mesh, false, alpha, null,
this._drawWithMaskFilter(mesh.bounds, paint, paint.maskFilter.Value, null, mesh, false, alpha,
strokeMult, null,
this._drawPathDrawMeshCallback2(paint, mesh, false, alpha, null, uiRectHelper.zero, null, false);
this._drawPathDrawMeshCallback2(paint, null, mesh, false, alpha, strokeMult, null, uiRectHelper.zero,
null, false);
}
}

this._saveLayer(uiRectHelper.fromRect(cmd.rect), uiPaint.fromPaint(cmd.paint));
break;
}
case DrawRestore _: {
saveCount--;
if (saveCount < 0) {

this._restore();
break;
}
case DrawResetMatrix _:
this._resetMatrix();
break;

}
case DrawClipPath cmd: {
var uipath = uiPath.fromPath(cmd.path);
this._clipPath(uipath);

case DrawPath cmd: {
var uipath = uiPath.fromPath(cmd.path);
this._drawPath(uipath, uiPaint.fromPaint(cmd.paint));

case DrawImageNine cmd: {
this._drawImageNine(cmd.image, uiRectHelper.fromRect(cmd.src),
uiRectHelper.fromRect(cmd.center), uiRectHelper.fromRect(cmd.dst),

default:
throw new Exception("unknown drawCmd: " + drawCmd);
}

this._saveLayer(cmd.rect.Value, cmd.paint);
break;
}
case uiDrawRestore _: {
saveCount--;
if (saveCount < 0) {

this._restore();
break;
}
case uiDrawResetMatrix _:
this._resetMatrix();
break;

}
default:
throw new Exception("unknown drawCmd: " + drawCmd);
}

}
if (paint.maskFilter != null && paint.maskFilter.Value.sigma != 0) {
this._drawWithMaskFilter(textBlobBounds, paint, paint.maskFilter.Value, null, false, 0, tex,
this._drawWithMaskFilter(textBlobBounds, paint, paint.maskFilter.Value, null, null, false, 0, 0, tex,
this._drawTextDrawMeshCallback(paint, null, false, 0, tex, textBlobBounds, mesh, notEmoji);
this._drawTextDrawMeshCallback(paint, null, null, false, 0, 0, tex, textBlobBounds, mesh, notEmoji);
}
public void flush(uiPicture picture) {

this._lastScissor = null;
this._layers.Clear();
}
AllocDebugger.onFrameEnd();
}
int _lastRtID;

35
Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_shader.cs


static readonly MaterialByBlendModeStencilComp _texMat;
static readonly Material _stencilMat;
static readonly Material _filterMat;
static readonly MaterialByBlendModeStencilComp _strokeAlphaMat;
static CanvasShader() {
var convexFillShader = Shader.Find("UIWidgets/canvas_convexFill");

throw new Exception("UIWidgets/canvas_stroke1 not found");
}
var strokeAlphaShader = Shader.Find("UIWidgets/canvas_strokeAlpha");
if (strokeAlphaShader == null) {
throw new Exception("UIWidgets/canvas_strokeAlpha not found");
}
var texShader = Shader.Find("UIWidgets/canvas_tex");
if (texShader == null) {
throw new Exception("UIWidgets/canvas_tex not found");

_fill1Mat = new MaterialByBlendMode(fill1Shader);
_stroke0Mat = new MaterialByBlendModeStencilComp(stroke0Shader);
_stroke1Mat = new Material(stroke1Shader) {hideFlags = HideFlags.HideAndDontSave};
_strokeAlphaMat = new MaterialByBlendModeStencilComp(strokeAlphaShader);
_texMat = new MaterialByBlendModeStencilComp(texShader);
_stencilMat = new Material(stencilShader) {hideFlags = HideFlags.HideAndDontSave};
_filterMat = new Material(filterShader) {hideFlags = HideFlags.HideAndDontSave};

static readonly int _alphaId = Shader.PropertyToID("_alpha");
static readonly int _strokeMultId = Shader.PropertyToID("_strokeMult");
static readonly int _colorId = Shader.PropertyToID("_color");
static readonly int _shaderMatId = Shader.PropertyToID("_shaderMat");
static readonly int _shaderTexId = Shader.PropertyToID("_shaderTex");

}
static void _getShaderPassAndProps(
PictureFlusher.RenderLayer layer, uiPaint paint, uiMatrix3? meshMatrix, float alpha,
PictureFlusher.RenderLayer layer, uiPaint paint, uiMatrix3? meshMatrix, float alpha, float strokeMult,
out int pass, out MaterialPropertyBlockWrapper props) {
Vector4 viewport = layer.viewport;

props.SetFloat(_strokeMultId, strokeMult);
switch (paint.shader) {
case null:

public static PictureFlusher.CmdDraw convexFill(PictureFlusher.RenderLayer layer, uiPaint paint,
uiMeshMesh mesh) {
var mat = _convexFillMat.getMaterial(paint.blendMode, layer.ignoreClip);
_getShaderPassAndProps(layer, paint, mesh.matrix, 1.0f, out var pass, out var props);
_getShaderPassAndProps(layer, paint, mesh.matrix, 1.0f, 0.0f, out var pass, out var props);
return PictureFlusher.CmdDraw.create(
mesh: mesh,

public static PictureFlusher.CmdDraw fill1(PictureFlusher.RenderLayer layer, uiPaint paint,
uiMeshMesh mesh) {
var mat = _fill1Mat.getMaterial(paint.blendMode);
_getShaderPassAndProps(layer, paint, mesh.matrix, 1.0f, out var pass, out var props);
_getShaderPassAndProps(layer, paint, mesh.matrix, 1.0f, 0.0f, out var pass, out var props);
var ret = PictureFlusher.CmdDraw.create(
mesh: mesh.boundsMesh,

public static PictureFlusher.CmdDraw stroke0(PictureFlusher.RenderLayer layer, uiPaint paint,
float alpha, uiMeshMesh mesh) {
var mat = _stroke0Mat.getMaterial(paint.blendMode, layer.ignoreClip);
_getShaderPassAndProps(layer, paint, mesh.matrix, alpha, out var pass, out var props);
_getShaderPassAndProps(layer, paint, mesh.matrix, alpha, 0.0f, out var pass, out var props);
return PictureFlusher.CmdDraw.create(
mesh: mesh,

);
}
public static PictureFlusher.CmdDraw strokeAlpha(PictureFlusher.RenderLayer layer, uiPaint paint, float alpha, float strokeMult, uiMeshMesh mesh) {
var mat = _strokeAlphaMat.getMaterial(paint.blendMode, layer.ignoreClip);
_getShaderPassAndProps(layer, paint, mesh.matrix, alpha, strokeMult, out var pass, out var props);
return PictureFlusher.CmdDraw.create(
mesh: mesh,
pass: pass,
material: mat,
properties: props
);
}
public static PictureFlusher.CmdDraw stencilClear(
PictureFlusher.RenderLayer layer, uiMeshMesh mesh) {
Vector4 viewport = layer.viewport;

public static PictureFlusher.CmdDraw tex(PictureFlusher.RenderLayer layer, uiPaint paint,
uiMeshMesh mesh, Image image) {
var mat = _texMat.getMaterial(paint.blendMode, layer.ignoreClip);
_getShaderPassAndProps(layer, paint, mesh.matrix, 1.0f, out var pass, out var props);
_getShaderPassAndProps(layer, paint, mesh.matrix, 1.0f, 0.0f, out var pass, out var props);
image.texture.filterMode = paint.filterMode;
props.SetTexture(_texId, image.texture);

public static PictureFlusher.CmdDraw texRT(PictureFlusher.RenderLayer layer, uiPaint paint,
uiMeshMesh mesh, PictureFlusher.RenderLayer renderLayer) {
var mat = _texMat.getMaterial(paint.blendMode, layer.ignoreClip);
_getShaderPassAndProps(layer, paint, mesh.matrix, 1.0f, out var pass, out var props);
_getShaderPassAndProps(layer, paint, mesh.matrix, 1.0f, 0.0f, out var pass, out var props);
props.SetInt(_texModeId, 1); // pre alpha
return PictureFlusher.CmdDraw.create(

uiMeshMesh mesh, TextBlobMesh textMesh, Texture tex) {
var mat = _texMat.getMaterial(paint.blendMode, layer.ignoreClip);
var meshMatrix = mesh != null ? mesh.matrix : textMesh.matrix;
_getShaderPassAndProps(layer, paint, meshMatrix, 1.0f, out var pass, out var props);
_getShaderPassAndProps(layer, paint, meshMatrix, 1.0f, 0.0f, out var pass, out var props);
tex.filterMode = paint.filterMode;
props.SetTexture(_texId, tex);
props.SetInt(_texModeId, 2); // alpha only

432
Runtime/ui/renderer/common/geometry/path/path_cache.cs


float _scale;
bool _fillConvex;
bool _fillConvex;
public uiMeshMesh fillMesh {
get { return this._fillMesh; }
}
public uiMeshMesh strokeMesh {
get { return this._strokeMesh; }
}
float _fringe;
public static uiPathCache create(float scale) {
uiPathCache newPathCache = ObjectPool<uiPathCache>.alloc();

}
}
uiList<Vector3> _expandFill() {
var points = this._points;
var paths = this._paths;
for (var j = 0; j < paths.Count; j++) {
var path = paths[j];
if (path.count <= 2) {
continue;
}
var ip0 = path.first + path.count - 1;
var ip1 = path.first;
for (var i = 0; i < path.count; i++) {
var p0 = points[ip0];
var p1 = points[ip1];
p0.dx = p1.x - p0.x; // no need to normalize
p0.dy = p1.y - p0.y;
points[ip0] = p0;
ip0 = ip1++;
}
path.convex = true;
ip0 = path.first + path.count - 1;
ip1 = path.first;
for (var i = 0; i < path.count; i++) {
var p0 = points[ip0];
var p1 = points[ip1];
float cross = p1.dx * p0.dy - p0.dx * p1.dy;
if (cross < 0.0f) {
path.convex = false;
}
ip0 = ip1++;
}
paths[j] = path;
}
var cvertices = 0;
for (var i = 0; i < paths.Count; i++) {
var path = paths[i];
if (path.count <= 2) {
continue;
}
cvertices += path.count;
}
var _vertices = ObjectPool<uiList<Vector3>>.alloc();
_vertices.SetCapacity(cvertices);
for (var i = 0; i < paths.Count; i++) {
var path = paths[i];
if (path.count <= 2) {
continue;
}
path.ifill = _vertices.Count;
for (var j = 0; j < path.count; j++) {
var p = points[path.first + j];
_vertices.Add(new Vector2(p.x, p.y));
}
path.nfill = _vertices.Count - path.ifill;
paths[i] = path;
}
return _vertices;
}
public uiMeshMesh getFillMesh(out bool convex) {
if (this._fillMesh != null) {
convex = this._fillConvex;
return this._fillMesh;
}
var vertices = this._expandFill();
var paths = this._paths;
var cindices = 0;
for (var i = 0; i < paths.Count; i++) {
var path = paths[i];
if (path.count <= 2) {
continue;
}
if (path.nfill > 0) {
D.assert(path.nfill >= 2);
cindices += (path.nfill - 2) * 3;
}
}
var indices = ObjectPool<uiList<int>>.alloc();
indices.SetCapacity(cindices);
for (var i = 0; i < paths.Count; i++) {
var path = paths[i];
if (path.count <= 2) {
continue;
}
if (path.nfill > 0) {
for (var j = 2; j < path.nfill; j++) {
indices.Add(path.ifill);
indices.Add(path.ifill + j);
indices.Add(path.ifill + j - 1);
}
}
}
D.assert(indices.Count == cindices);
var mesh = uiMeshMesh.create(null, vertices, indices);
this._fillMesh = mesh;
this._fillConvex = false;
for (var i = 0; i < paths.Count; i++) {
var path = paths[i];
if (path.count <= 2) {
continue;
}
if (this._fillConvex) {
// if more than two paths, convex is false.
this._fillConvex = false;
break;
}
if (!path.convex) {
// if not convex, convex is false.
break;
}
this._fillConvex = true;
}
convex = this._fillConvex;
return this._fillMesh;
}
void _calculateJoins(float w, StrokeJoin lineJoin, float miterLimit) {
float iw = w > 0.0f ? 1.0f / w : 0.0f;

ip0 = path.first + path.count - 1;
ip1 = path.first;
path.convex = true;
for (var j = 0; j < path.count; j++) {
var p0 = points[ip0];
var p1 = points[ip1];

// Keep track of left turns.
float cross = p1.dx * p0.dy - p0.dx * p1.dy;
} else if (cross < 0.0f) {
path.convex = false;
}
// Calculate if we should use bevel or miter for inner join.

ip0 = ip1++;
}
paths[i] = path;
uiList<Vector3> _expandStroke(float w, StrokeCap lineCap, StrokeJoin lineJoin, float miterLimit) {
this._calculateJoins(w, lineJoin, miterLimit);
uiVertexUV _expandStroke(float w, float fringe, StrokeCap lineCap, StrokeJoin lineJoin, float miterLimit) {
float aa = fringe;
float u0 = 0.0f, u1 = 1.0f;
w += aa * 0.5f;
if (aa == 0.0f) {
u0 = 0.5f;
u1 = 0.5f;
}
this._calculateJoins(w, lineJoin, miterLimit);
var points = this._points;
var paths = this._paths;

}
cvertices += path.count * 2;
cvertices += 4;
cvertices += 8;
var _uv = ObjectPool<uiList<Vector2>>.alloc();
_uv.SetCapacity(cvertices);
for (var i = 0; i < paths.Count; i++) {
var path = paths[i];
if (path.count <= 1) {

if (!path.closed) {
if (lineCap == StrokeCap.butt) {
_vertices.buttCapStart(p0, p0.dx, p0.dy, w, 0.0f);
_vertices.buttCapStart(_uv, p0, p0.dx, p0.dy, w, 0.0f, aa, u0, u1);
_vertices.buttCapStart(p0, p0.dx, p0.dy, w, w);
_vertices.buttCapStart(_uv, p0, p0.dx, p0.dy, w, w, aa, u0, u1);
_vertices.roundCapStart(p0, p0.dx, p0.dy, w, ncap);
_vertices.roundCapStart(_uv, p0, p0.dx, p0.dy, w, ncap, u0, u1);
}
}

if ((p1.flags & (uiPointFlags.bevel | uiPointFlags.innerBevel)) != 0) {
if (lineJoin == StrokeJoin.round) {
_vertices.roundJoin(p0, p1, w, w, ncap);
_vertices.roundJoin(_uv, p0, p1, w, w, ncap, u0, u1, aa);
_vertices.bevelJoin(p0, p1, w, w);
_vertices.bevelJoin(_uv, p0, p1, w, w, u0, u1, aa);
_uv.Add(new Vector2(u0, 1));
_uv.Add(new Vector2(u1, 1));
}
ip0 = ip1++;

p0 = points[ip0];
p1 = points[ip1];
if (lineCap == StrokeCap.butt) {
_vertices.buttCapEnd(p1, p0.dx, p0.dy, w, 0.0f);
_vertices.buttCapEnd(_uv, p1, p0.dx, p0.dy, w, 0.0f, aa, u0, u1);
_vertices.buttCapEnd(p1, p0.dx, p0.dy, w, w);
_vertices.buttCapEnd(_uv, p1, p0.dx, p0.dy, w, w, aa, u0, u1);
_vertices.roundCapEnd(p1, p0.dx, p0.dy, w, ncap);
_vertices.roundCapEnd(_uv, p1, p0.dx, p0.dy, w, ncap, u0, u1);
_uv.Add(new Vector2(u0, 1));
_uv.Add(new Vector2(u1, 1));
D.assert(_uv.Count == _vertices.Count);
return _vertices;
return new uiVertexUV {
strokeVertices = _vertices,
strokeUV = _uv,
};
public uiMeshMesh getStrokeMesh(float strokeWidth, StrokeCap lineCap, StrokeJoin lineJoin, float miterLimit) {
uiVertexUV _expandFill(float fringe) {
float aa = fringe;
float woff = aa * 0.5f;
var points = this._points;
var paths = this._paths;
this._calculateJoins(fringe, StrokeJoin.miter, 4.0f);
var cvertices = 0;
for (var i = 0; i < paths.Count; i++) {
var path = paths[i];
if (path.count <= 2) {
continue;
}
cvertices += path.count;
}
this._fillConvex = false;
for (var i = 0; i < paths.Count; i++) {
var path = paths[i];
if (path.count <= 2) {
continue;
}
if (this._fillConvex) {
// if more than two paths, convex is false.
this._fillConvex = false;
break;
}
if (!path.convex) {
// if not convex, convex is false.
break;
}
this._fillConvex = true;
}
var _vertices = ObjectPool<uiList<Vector3>>.alloc();
_vertices.SetCapacity(cvertices);
var _uv = ObjectPool<uiList<Vector2>>.alloc();
_uv.SetCapacity(cvertices);
for (var i = 0; i < paths.Count; i++) {
var path = paths[i];
if (path.count <= 2) {
continue;
}
path.ifill = _vertices.Count;
for (var j = 0; j < path.count; j++) {
var p = points[path.first + j];
if (aa > 0.0f) {
_vertices.Add(new Vector2(p.x + p.dmx * woff, p.y + p.dmy * woff));
}
else {
_vertices.Add(new Vector2(p.x, p.y));
}
_uv.Add(new Vector2(0.5f, 1.0f));
}
path.nfill = _vertices.Count - path.ifill;
paths[i] = path;
}
uiList<Vector3> _strokeVertices = null;
uiList<Vector2> _strokeUV = null;
if (aa > 0.0f) {
_strokeVertices = ObjectPool<uiList<Vector3>>.alloc();
_strokeUV = ObjectPool<uiList<Vector2>>.alloc();
cvertices = 0;
for (var i = 0; i < paths.Count; i++) {
var path = paths[i];
if (path.count <= 2) {
continue;
}
cvertices += path.count * 2;
}
_strokeVertices.SetCapacity(cvertices);
_strokeUV.SetCapacity(cvertices);
float lw = this._fillConvex ? woff : aa + woff;
float rw = aa - woff;
float lu = this._fillConvex ? 0.5f : 0.0f;
float ru = 1.0f;
for (var i = 0; i < paths.Count; i++) {
var path = paths[i];
if (path.count <= 2) {
continue;
}
path.istroke = _strokeVertices.Count;
for (var j = 0; j < path.count; j++) {
var p = points[path.first + j];
_strokeVertices.Add(new Vector2(p.x + p.dmx * lw, p.y + p.dmy * lw));
_strokeUV.Add(new Vector2(lu, 1.0f));
_strokeVertices.Add(new Vector2(p.x - p.dmx * rw, p.y - p.dmy * rw));
_strokeUV.Add(new Vector2(ru, 1.0f));
}
path.nstroke = _strokeVertices.Count - path.istroke;
paths[i] = path;
}
}
return new uiVertexUV {
fillVertices = _vertices,
fillUV = _uv,
strokeVertices = _strokeVertices,
strokeUV = _strokeUV,
};
}
public void computeStrokeMesh(float strokeWidth, float fringe, StrokeCap lineCap, StrokeJoin lineJoin, float miterLimit) {
this._fillMesh == null && // Ensure that the cached stroke mesh was not calculated in computeFillMesh
this._fringe == fringe &&
return this._strokeMesh;
return;
var vertices = this._expandStroke(strokeWidth, lineCap, lineJoin, miterLimit);
var verticesUV = this._expandStroke(strokeWidth, fringe, lineCap, lineJoin, miterLimit);
var paths = this._paths;

D.assert(indices.Count == cindices);
ObjectPool<uiMeshMesh>.release(this._strokeMesh);
this._strokeMesh = uiMeshMesh.create(null, vertices, indices);
this._strokeMesh = uiMeshMesh.create(null, verticesUV.strokeVertices, indices, verticesUV.strokeUV);
ObjectPool<uiMeshMesh>.release(this._fillMesh);
this._fillMesh = null;
this._fringe = fringe;
return this._strokeMesh;
return;
}
public void computeFillMesh(float fringe, out bool convex) {
if (this._fillMesh != null && (fringe != 0.0f || this._strokeMesh != null) && this._fringe == fringe) {
convex = this._fillConvex;
return;
}
var verticesUV = this._expandFill(fringe);
convex = this._fillConvex;
var paths = this._paths;
var cindices = 0;
for (var i = 0; i < paths.Count; i++) {
var path = paths[i];
if (path.count <= 2) {
continue;
}
if (path.nfill > 0) {
D.assert(path.nfill >= 2);
cindices += (path.nfill - 2) * 3;
}
}
var indices = ObjectPool<uiList<int>>.alloc();
indices.SetCapacity(cindices);
for (var i = 0; i < paths.Count; i++) {
var path = paths[i];
if (path.count <= 2) {
continue;
}
if (path.nfill > 0) {
for (var j = 2; j < path.nfill; j++) {
indices.Add(path.ifill);
indices.Add(path.ifill + j);
indices.Add(path.ifill + j - 1);
}
}
}
D.assert(indices.Count == cindices);
if (verticesUV.strokeVertices != null) {
cindices = 0;
for (var i = 0; i < paths.Count; i++) {
var path = paths[i];
if (path.count <= 2) {
continue;
}
if (path.nstroke > 0) {
D.assert(path.nstroke >= 6);
cindices += path.nstroke * 3;
}
}
var strokeIndices = ObjectPool<uiList<int>>.alloc();
strokeIndices.SetCapacity(cindices);
for (var i = 0; i < paths.Count; i++) {
var path = paths[i];
if (path.count <= 2) {
continue;
}
if (path.nstroke > 0) {
strokeIndices.Add(path.istroke + path.nstroke - 1);
strokeIndices.Add(path.istroke + path.nstroke - 2);
strokeIndices.Add(path.istroke);
strokeIndices.Add(path.istroke + path.nstroke - 1);
strokeIndices.Add(path.istroke);
strokeIndices.Add(path.istroke + 1);
for (var j = 2; j < path.nstroke; j++) {
if ((j & 1) == 0) {
strokeIndices.Add(path.istroke + j - 1);
strokeIndices.Add(path.istroke + j - 2);
strokeIndices.Add(path.istroke + j);
}
else {
strokeIndices.Add(path.istroke + j - 2);
strokeIndices.Add(path.istroke + j - 1);
strokeIndices.Add(path.istroke + j);
}
}
}
}
D.assert(strokeIndices.Count == cindices);
ObjectPool<uiMeshMesh>.release(this._strokeMesh);
this._strokeMesh = uiMeshMesh.create(null, verticesUV.strokeVertices, strokeIndices, verticesUV.strokeUV);
}
var mesh = uiMeshMesh.create(null, verticesUV.fillVertices, indices, verticesUV.fillUV);
this._fillMesh = mesh;
this._fringe = fringe;
}
}
}

91
Runtime/ui/renderer/common/geometry/path/path_utils.cs


}
}
struct uiVertexUV {
public uiList<Vector3> fillVertices;
public uiList<Vector2> fillUV;
public uiList<Vector3> strokeVertices;
public uiList<Vector2> strokeUV;
}
static class uiPathUtils {
public static bool ptEquals(float x1, float y1, float x2, float y2, float tol) {
float dx = x2 - x1;

return d;
}
public static void buttCapStart(this uiList<Vector3> dst, uiPathPoint p,
float dx, float dy, float w, float d) {
public static void buttCapStart(this uiList<Vector3> dst, uiList<Vector2> uv, uiPathPoint p,
float dx, float dy, float w, float d, float aa, float u0, float u1) {
dst.Add(new Vector2(px + dlx * w - dx * aa, py + dly * w - dy * aa));
dst.Add(new Vector2(px - dlx * w - dx * aa, py - dly * w - dy * aa));
uv.Add(new Vector2(u0, 0));
uv.Add(new Vector2(u1, 0));
uv.Add(new Vector2(u0, 1));
uv.Add(new Vector2(u1, 1));
public static void buttCapEnd(this uiList<Vector3> dst, uiPathPoint p,
float dx, float dy, float w, float d) {
public static void buttCapEnd(this uiList<Vector3> dst, uiList<Vector2> uv, uiPathPoint p,
float dx, float dy, float w, float d, float aa, float u0, float u1) {
float px = p.x + dx * d;
float py = p.y + dy * d;
float dlx = dy;

dst.Add(new Vector2(px - dlx * w, py - dly * w));
dst.Add(new Vector2(px + dlx * w + dx * aa, py + dly * w + dy * aa));
dst.Add(new Vector2(px - dlx * w + dx * aa, py - dly * w + dy * aa));
uv.Add(new Vector2(u0, 1));
uv.Add(new Vector2(u1, 1));
uv.Add(new Vector2(u0, 0));
uv.Add(new Vector2(u1, 0));
public static void roundCapStart(this uiList<Vector3> dst, uiPathPoint p,
float dx, float dy, float w, int ncap) {
public static void roundCapStart(this uiList<Vector3> dst, uiList<Vector2> uv, uiPathPoint p,
float dx, float dy, float w, int ncap, float u0, float u1) {
float px = p.x;
float py = p.y;
float dlx = dy;

float ax = Mathf.Cos(a) * w, ay = Mathf.Sin(a) * w;
dst.Add(new Vector2(px - dlx * ax - dx * ay, py - dly * ax - dy * ay));
dst.Add(new Vector2(px, py));
uv.Add(new Vector2(u0, 1));
uv.Add(new Vector2(0.5f, 1));
uv.Add(new Vector2(u0, 1));
uv.Add(new Vector2(u1, 1));
public static void roundCapEnd(this uiList<Vector3> dst, uiPathPoint p,
float dx, float dy, float w, int ncap) {
public static void roundCapEnd(this uiList<Vector3> dst, uiList<Vector2> uv, uiPathPoint p,
float dx, float dy, float w, int ncap, float u0, float u1) {
float px = p.x;
float py = p.y;
float dlx = dy;

dst.Add(new Vector2(px - dlx * w, py - dly * w));
uv.Add(new Vector2(u0, 1));
uv.Add(new Vector2(u1, 1));
for (var i = 0; i < ncap; i++) {
float a = (float) i / (ncap - 1) * Mathf.PI;

uv.Add(new Vector2(0.5f, 1));
uv.Add(new Vector2(u0, 1));
}
}

return Mathf.Max(2, Mathf.CeilToInt(arc / da));
}
public static void roundJoin(this uiList<Vector3> dst, uiPathPoint p0, uiPathPoint p1,
float lw, float rw, int ncap) {
public static void roundJoin(this uiList<Vector3> dst, uiList<Vector2> uv, uiPathPoint p0, uiPathPoint p1,
float lw, float rw, int ncap, float lu, float ru, float fringe) {
float dlx0 = p0.dy;
float dly0 = -p0.dx;
float dlx1 = p1.dy;

dst.Add(new Vector2(lx0, ly0));
dst.Add(new Vector2(p1.x - dlx0 * rw, p1.y - dly0 * rw));
uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(ru, 1));
var n = Mathf.CeilToInt((a0 - a1) / Mathf.PI * ncap).clamp(2, ncap);
for (var i = 0; i < n; i++) {

dst.Add(new Vector2(p1.x, p1.y));
dst.Add(new Vector2(rx, ry));
uv.Add(new Vector2(0.5f, 1));
uv.Add(new Vector2(ru, 1));
uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(ru, 1));
}
else {
float rx0, ry0, rx1, ry1;

dst.Add(new Vector2(p1.x + dlx0 * lw, p1.y + dly0 * lw));
dst.Add(new Vector2(rx0, ry0));
uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(ru, 1));
var n = Mathf.CeilToInt((a1 - a0) / Mathf.PI * ncap).clamp(2, ncap);
for (var i = 0; i < n; i++) {

dst.Add(new Vector2(lx, ly));
dst.Add(new Vector2(p1.x, p1.y));
uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(0.5f, 1));
uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(ru, 1));
public static void bevelJoin(this uiList<Vector3> dst, uiPathPoint p0, uiPathPoint p1,
float lw, float rw) {
public static void bevelJoin(this uiList<Vector3> dst, uiList<Vector2> uv, uiPathPoint p0, uiPathPoint p1,
float lw, float rw, float lu, float ru, float fringe) {
float rx0, ry0, rx1, ry1;
float lx0, ly0, lx1, ly1;

dst.Add(new Vector2 {x = lx0, y = ly0});
dst.Add(new Vector2 {x = p1.x - dlx0 * rw, y = p1.y - dly0 * rw});
uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(ru, 1));
if ((p1.flags & uiPointFlags.bevel) != 0) {
dst.Add(new Vector2(lx0, ly0));

uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(ru, 1));
uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(ru, 1));
}
else {
rx0 = p1.x - p1.dmx * rw;

dst.Add(new Vector2(rx0, ry0));
dst.Add(new Vector2(p1.x, p1.y));
dst.Add(new Vector2(p1.x - dlx1 * rw, p1.y - dly1 * rw));
uv.Add(new Vector2(0.5f, 1));
uv.Add(new Vector2(ru, 1));
uv.Add(new Vector2(ru, 1));
uv.Add(new Vector2(ru, 1));
uv.Add(new Vector2(0.5f, 1));
uv.Add(new Vector2(ru, 1));
uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(ru, 1));
}
else {
chooseBevel((p1.flags & uiPointFlags.innerBevel) != 0, p0, p1, -rw,

dst.Add(new Vector2(rx0, ry0));
uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(ru, 1));
if ((p1.flags & uiPointFlags.bevel) != 0) {
dst.Add(new Vector2(p1.x + dlx0 * lw, p1.y + dly0 * lw));

uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(ru, 1));
uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(ru, 1));
}
else {
lx0 = p1.x + p1.dmx * lw;

dst.Add(new Vector2(lx0, ly0));
dst.Add(new Vector2(p1.x + dlx1 * lw, p1.y + dly1 * lw));
dst.Add(new Vector2(p1.x, p1.y));
uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(0.5f, 1));
uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(0.5f, 1));
uv.Add(new Vector2(lu, 1));
uv.Add(new Vector2(ru, 1));
}
}
}

10
Runtime/ui/renderer/common/picture.cs


var rectPathCache = cmd.path.flatten(
scale * Window.instance.devicePixelRatio);
var rectMesh = rectPathCache.getFillMesh(out _);
rectPathCache.computeFillMesh(0.0f, out _);
var rectMesh = rectPathCache.fillMesh;
var transformedMesh = rectMesh.transform(state.xform);
var rect = transformedMesh.bounds;
state.scissor = state.scissor == null ? rect : state.scissor.Value.intersect(rect);

uiMeshMesh mesh;
if (paint.style == PaintingStyle.fill) {
var cache = path.flatten(scale * devicePixelRatio);
var fillMesh = cache.getFillMesh(out _);
cache.computeFillMesh(0.0f, out _);
var fillMesh = cache.fillMesh;
mesh = fillMesh.transform(state.xform);
}
else {

}
var cache = path.flatten(scale * devicePixelRatio);
var strokenMesh = cache.getStrokeMesh(
cache.computeStrokeMesh(
0.0f,
var strokenMesh = cache.strokeMesh;
mesh = strokenMesh.transform(state.xform);
}

65
Runtime/Resources/UIWidgets_canvas_strokeAlpha.shader


Shader "UIWidgets/canvas_strokeAlpha"
{
Properties {
_SrcBlend("_SrcBlend", Int) = 1 // One
_DstBlend("_DstBlend", Int) = 10 // OneMinusSrcAlpha
_StencilComp("_StencilComp", Float) = 3 // - Equal, 8 - Always
}
SubShader {
ZTest Always
ZWrite Off
Blend [_SrcBlend] [_DstBlend]
Stencil {
Ref 128
Comp [_StencilComp]
Pass IncrSat
}
Pass { // 0, color
CGPROGRAM
#define UIWIDGETS_COLOR
#include "UIWidgets_canvas.cginc"
#pragma vertex vert
#pragma fragment frag_stroke_alpha
ENDCG
}
Pass { // 1, linear
CGPROGRAM
#define UIWIDGETS_LINEAR
#include "UIWidgets_canvas.cginc"
#pragma vertex vert
#pragma fragment frag_stroke_alpha
ENDCG
}
Pass { // 2, radial
CGPROGRAM
#define UIWIDGETS_RADIAL
#include "UIWidgets_canvas.cginc"
#pragma vertex vert
#pragma fragment frag_stroke_alpha
ENDCG
}
Pass { // 3, sweep
CGPROGRAM
#define UIWIDGETS_SWEEP
#include "UIWidgets_canvas.cginc"
#pragma vertex vert
#pragma fragment frag_stroke_alpha
ENDCG
}
Pass { // 4, image
CGPROGRAM
#define UIWIDGETS_IMAGE
#include "UIWidgets_canvas.cginc"
#pragma vertex vert
#pragma fragment frag_stroke_alpha
ENDCG
}
}
}

9
Runtime/Resources/UIWidgets_canvas_strokeAlpha.shader.meta


fileFormatVersion: 2
guid: c57ed8e638931f946a7b5e26a54725d8
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:

146
Samples/UIWidgetSample/AntialiasSVGSample.cs


using System.Collections.Generic;
using Unity.UIWidgets.engine;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.material;
using Unity.UIWidgets.ui;
using Unity.UIWidgets.widgets;
using UnityEngine;
using Canvas = Unity.UIWidgets.ui.Canvas;
using Color = Unity.UIWidgets.ui.Color;
using Gradient = Unity.UIWidgets.ui.Gradient;
using Image = Unity.UIWidgets.ui.Image;
using UIWidgetRect = Unity.UIWidgets.ui.Rect;
public class AntialiasSVGSample : UIWidgetsPanel {
protected override void OnEnable() {
FontManager.instance.addFont(Resources.Load<Font>("MaterialIcons-Regular"), "Material Icons");
FontManager.instance.addFont(Resources.Load<Font>("GalleryIcons"), "GalleryIcons");
base.OnEnable();
}
protected override Widget createWidget() {
Debug.Log("[SVG Testbed Panel Created]");
return new SVGTestbedPanelWidget();
}
}
public class SVGTestbedPanelWidget : StatefulWidget {
public SVGTestbedPanelWidget(Key key = null) : base(key) { }
public override State createState() {
return new SVGTestbedPanelWidgetState();
}
}
public class SVGTestbedPanelWidgetState : State<SVGTestbedPanelWidget> {
static Texture2D texture6;
public class CurvePainter : AbstractCustomPainter {
public override void paint(Canvas canvas, Size size) {
var paint = new Paint()
{
color = new Color(0xFFFF0000),
};
paint.color = Colors.yellow;
paint.style = PaintingStyle.stroke;
paint.strokeWidth = 3;
var startPoint = new Offset(0, size.height / 6);
var controlPoint1 = new Offset(size.width / 4, 0);
var controlPoint2 = new Offset(3 * size.width / 4, 0);
var endPoint = new Offset(size.width, size.height / 6);
var path = new Path();
path.moveTo(startPoint.dx, startPoint.dy);
path.cubicTo(
controlPoint1.dx, controlPoint1.dy,
controlPoint2.dx, controlPoint2.dy,
endPoint.dx, endPoint.dy
);
path.moveTo(10, 10);
path.lineTo(90, 10);
path.lineTo(10, 90);
path.lineTo(90, 90);
path.winding(PathWinding.clockwise);
path.close();
path.moveTo(110, 10);
path.lineTo(190, 10);
path.lineTo(110, 90);
path.lineTo(190, 90);
path.close();
path.addRect(UIWidgetRect.fromLTWH(10, 25, 180, 50));
path.addRect(UIWidgetRect.fromLTWH(200, 0, 100, 100));
path.addRRect(RRect.fromRectAndRadius(UIWidgetRect.fromLTWH(225, 25, 50, 50), 10));
path.winding(PathWinding.clockwise);
path.addOval(UIWidgetRect.fromLTWH(200, 50, 100, 100));
path.winding(PathWinding.clockwise);
canvas.drawPath(path, paint);
paint = new Paint {
color = new Color(0xFFFF0000),
shader = Gradient.linear(
new Offset(0, 0),
new Offset(size.width, 200),
new List<Color>() {
Colors.red, Colors.black, Colors.green
}, null, TileMode.clamp),
};
canvas.translate(0, 200);
canvas.drawPath(path, paint);
canvas.translate(0, 200);
// paint.maskFilter = MaskFilter.blur(BlurStyle.normal, 5);
paint.shader = new ImageShader(new Image(texture6, true), TileMode.mirror);
canvas.drawPath(path, paint);
canvas.translate(0, 200);
paint = new Paint {
color = new Color(0xFF00FF00),
shader = Gradient.sweep(
new Offset(size.width / 2, 100),
new List<Color>() {
Colors.red, Colors.black, Colors.green, Colors.red,
}, null, TileMode.clamp, 0 * Mathf.PI / 180, 360 * Mathf.PI / 180),
};
canvas.drawPath(path, paint);
paint.shader = Gradient.radial(
new Offset(size.width / 2, 100), 200f,
new List<Color>()
{
Colors.red, Colors.black, Colors.green
}, null, TileMode.clamp);
canvas.translate(0, 200);
canvas.drawPath(path, paint);
}
public override bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
public override Widget build(BuildContext context) {
texture6 = Resources.Load<Texture2D>("6");
return new SingleChildScrollView(
child: new Container(
child: new CustomPaint(
painter: new CurvePainter(),
child: new Container()
),
height: 1500
)
);
}
}

11
Samples/UIWidgetSample/AntialiasSVGSample.cs.meta


fileFormatVersion: 2
guid: 41696ca988ae5c44da3f545825faf3ac
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
正在加载...
取消
保存