q5 4.3.0 → 4.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. package/deno.json +1 -1
  2. package/package.json +1 -1
  3. package/q5.d.ts +51 -21
  4. package/q5.js +455 -110
  5. package/q5.min.js +2 -2
package/q5.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * q5.js
3
- * @version 4.3
3
+ * @version 4.4
4
4
  * @author quinton-ashley
5
5
  * @contributors evanalulu, Tezumie, ormaq, Dukemz, LingDong-
6
6
  * @license LGPL-3.0
@@ -490,7 +490,7 @@ if (typeof window == 'object') {
490
490
  window.addEventListener('pagehide', cleanup);
491
491
  } else global.window = 0;
492
492
 
493
- Q5.version = Q5.VERSION = '4.3';
493
+ Q5.version = Q5.VERSION = '4.4';
494
494
 
495
495
  if (typeof document == 'object') {
496
496
  document.addEventListener('DOMContentLoaded', () => {
@@ -6153,11 +6153,16 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
6153
6153
  $.pop();
6154
6154
  } else {
6155
6155
  addColor(r, g, b, a);
6156
- let lx = -c.hw,
6156
+ let ci = colorIndex,
6157
+ lx = -c.hw,
6157
6158
  rx = c.hw,
6158
6159
  ty = -c.hh,
6159
6160
  by = c.hh;
6160
- addQuad(lx, ty, rx, ty, rx, by, lx, by, colorIndex, 0);
6161
+ addVert(lx, ty, ci, 0);
6162
+ addVert(rx, ty, ci, 0);
6163
+ addVert(lx, by, ci, 0);
6164
+ addVert(rx, by, ci, 0);
6165
+ drawStack.push(1, 4); // always use the default shapes pipeline
6161
6166
  }
6162
6167
  };
6163
6168
 
@@ -6197,7 +6202,15 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
6197
6202
  shouldClear = false;
6198
6203
  };
6199
6204
 
6200
- let transformsBuffer, colorsBuffer, shapesVertBuff, imgVertBuff, charBuffer, textBuffer;
6205
+ let transformsBuffer,
6206
+ colorsBuffer,
6207
+ shapesVertBuff,
6208
+ imgVertBuff,
6209
+ polygonVertBuff,
6210
+ polyPtsBuffer,
6211
+ polyPtsBindGroup,
6212
+ charBuffer,
6213
+ textBuffer;
6201
6214
  let mainBindGroup, lastTransformsBuffer, lastColorsBuffer;
6202
6215
 
6203
6216
  $._render = () => {
@@ -6276,6 +6289,37 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
6276
6289
 
6277
6290
  // prepare to render images and videos
6278
6291
 
6292
+ if (polygonVertIdx) {
6293
+ let polygonVertSize = polygonVertIdx * 4; // 4 bytes per float
6294
+ if (!polygonVertBuff || polygonVertBuff.size < polygonVertSize) {
6295
+ if (polygonVertBuff) polygonVertBuff.destroy();
6296
+ polygonVertBuff = Q5.device.createBuffer({
6297
+ size: polygonVertSize * 2,
6298
+ usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
6299
+ });
6300
+ }
6301
+
6302
+ Q5.device.queue.writeBuffer(polygonVertBuff, 0, polygonVertStack.subarray(0, polygonVertIdx));
6303
+ $._pass.setVertexBuffer(2, polygonVertBuff);
6304
+
6305
+ if (polyPtsIdx) {
6306
+ let polyPtsSize = polyPtsIdx * 4;
6307
+ if (!polyPtsBuffer || polyPtsBuffer.size < polyPtsSize) {
6308
+ if (polyPtsBuffer) polyPtsBuffer.destroy();
6309
+ polyPtsBuffer = Q5.device.createBuffer({
6310
+ size: Math.max(polyPtsSize * 2, 64),
6311
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
6312
+ });
6313
+ }
6314
+ Q5.device.queue.writeBuffer(polyPtsBuffer, 0, polyPtsStack.subarray(0, polyPtsIdx));
6315
+
6316
+ polyPtsBindGroup = Q5.device.createBindGroup({
6317
+ layout: polygonBindGroupLayout,
6318
+ entries: [{ binding: 0, resource: { buffer: polyPtsBuffer } }]
6319
+ });
6320
+ }
6321
+ }
6322
+
6279
6323
  if (imgVertIdx) {
6280
6324
  $._pass.setPipeline($._pipelines[2]); // images pipeline
6281
6325
 
@@ -6374,6 +6418,7 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
6374
6418
  let drawVertOffset = 0,
6375
6419
  imageVertOffset = 0,
6376
6420
  textCharOffset = 0,
6421
+ polygonVertOffset = 0,
6377
6422
  rectIdx = 0,
6378
6423
  ellipseIdx = 0,
6379
6424
  curPipelineIndex = -1;
@@ -6401,12 +6446,18 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
6401
6446
  } else if (curPipelineIndex == 6) {
6402
6447
  pass.setIndexBuffer(ellipseIndexBuffer, 'uint16');
6403
6448
  pass.setBindGroup(1, ellipseBindGroup);
6449
+ } else if (curPipelineIndex == 7) {
6450
+ pass.setBindGroup(1, polyPtsBindGroup);
6404
6451
  } else if ($._customBindHandlers[curPipelineIndex]) {
6405
6452
  $._customBindHandlers[curPipelineIndex](pass);
6406
6453
  }
6407
6454
  }
6408
6455
 
6409
- if (curPipelineIndex == 6) {
6456
+ if (curPipelineIndex == 7) {
6457
+ // draw a polygon
6458
+ pass.draw(v, 1, polygonVertOffset, 0);
6459
+ polygonVertOffset += v;
6460
+ } else if (curPipelineIndex == 6) {
6410
6461
  // draw an ellipse
6411
6462
  pass.drawIndexed(18, v, 0, 0, ellipseIdx);
6412
6463
  ellipseIdx += v;
@@ -6483,6 +6534,8 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
6483
6534
 
6484
6535
  // reset
6485
6536
  shapesVertIdx = 0;
6537
+ polygonVertIdx = 0;
6538
+ polyPtsIdx = 0;
6486
6539
  imgVertIdx = 0;
6487
6540
  // Remove video frames without creating new array
6488
6541
  if (vidFrames > 0) {
@@ -6633,19 +6686,16 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
6633
6686
  };
6634
6687
 
6635
6688
  $.vertex = (x, y) => {
6636
- if (matrixDirty) saveMatrix();
6637
- sv.push(x, y, fillIdx, matrixIdx);
6689
+ sv.push(x, y, fillIdx);
6638
6690
  shapeVertCount++;
6639
6691
  };
6640
6692
 
6641
6693
  $.curveVertex = (x, y) => {
6642
- if (matrixDirty) saveMatrix();
6643
6694
  curveVertices.push({ x, y });
6644
6695
  };
6645
6696
 
6646
6697
  $.bezierVertex = function (cx1, cy1, cx2, cy2, x, y) {
6647
6698
  if (shapeVertCount === 0) throw new Error('Shape needs a vertex()');
6648
- if (matrixDirty) saveMatrix();
6649
6699
 
6650
6700
  // Get the last vertex as the starting point (P₀)
6651
6701
  let prevIndex = (shapeVertCount - 1) * 4;
@@ -6681,136 +6731,430 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
6681
6731
  vy = mt3 * startY + 3 * mt2 * t * cy1 + 3 * mt * t2 * cy2 + t3 * y;
6682
6732
  }
6683
6733
 
6684
- sv.push(vx, vy, fillIdx, matrixIdx);
6734
+ sv.push(vx, vy, fillIdx);
6685
6735
  shapeVertCount++;
6686
6736
  }
6687
6737
  };
6688
6738
 
6689
6739
  $.quadraticVertex = (cx, cy, x, y) => $.bezierVertex(cx, cy, x, y);
6690
6740
 
6741
+ function addQuad(x1, y1, x2, y2, x3, y3, x4, y4, ci, ti) {
6742
+ addVert(x1, y1, ci, ti); // v0
6743
+ addVert(x2, y2, ci, ti); // v1
6744
+ addVert(x4, y4, ci, ti); // v3
6745
+ addVert(x3, y3, ci, ti); // v2
6746
+ drawStack.push(shapesPL, 4);
6747
+ }
6748
+
6749
+ $.plane = (x, y, w, h) => {
6750
+ h ??= w;
6751
+ let [l, r, t, b] = calcBox(x, y, w, h, 'center');
6752
+ if (matrixDirty) saveMatrix();
6753
+ addQuad(l, t, r, t, r, b, l, b, fillIdx, matrixIdx);
6754
+ };
6755
+
6756
+ $.curve = (x1, y1, x2, y2, x3, y3, x4, y4) => {
6757
+ $.beginShape();
6758
+ $.curveVertex(x1, y1);
6759
+ $.curveVertex(x2, y2);
6760
+ $.curveVertex(x3, y3);
6761
+ $.curveVertex(x4, y4);
6762
+ $.endShape();
6763
+ };
6764
+
6765
+ $.bezier = (x1, y1, x2, y2, x3, y3, x4, y4) => {
6766
+ $.beginShape();
6767
+ $.vertex(x1, y1);
6768
+ $.bezierVertex(x2, y2, x3, y3, x4, y4);
6769
+ $.endShape();
6770
+ };
6771
+
6772
+ /* POLYGONS */
6773
+
6774
+ let polygonPL = 7;
6775
+
6776
+ $._polygonShaderCode =
6777
+ $._baseShaderCode +
6778
+ /* wgsl */ `
6779
+ struct VertexParams {
6780
+ @builtin(vertex_index) vertexIndex : u32,
6781
+ @location(0) pos: vec2f,
6782
+ @location(1) polyStart: f32,
6783
+ @location(2) polyCount: f32,
6784
+ @location(3) fillIndex: f32,
6785
+ @location(4) strokeIndex: f32,
6786
+ @location(5) strokeWeight: f32,
6787
+ @location(6) matrixIndex: f32
6788
+ }
6789
+
6790
+ struct FragParams {
6791
+ @builtin(position) position: vec4f,
6792
+ @location(0) localPos: vec2f,
6793
+ @location(1) @interpolate(flat) polyStart: u32,
6794
+ @location(2) @interpolate(flat) polyCount: u32,
6795
+ @location(3) @interpolate(flat) fillIndex: f32,
6796
+ @location(4) @interpolate(flat) strokeIndex: f32,
6797
+ @location(5) @interpolate(flat) strokeWeight: f32,
6798
+ @location(6) @interpolate(flat) isClosed: f32
6799
+ }
6800
+
6801
+ @group(0) @binding(0) var<uniform> q: Q5;
6802
+ @group(0) @binding(1) var<storage> transforms: array<mat4x4<f32>>;
6803
+ @group(0) @binding(2) var<storage> colors : array<vec4f>;
6804
+
6805
+ @group(1) @binding(0) var<storage, read> polyPts: array<vec4f>;
6806
+
6807
+ fn transformVertex(pos: vec2f, matrixIndex: f32) -> vec4f {
6808
+ var vert = vec4f(pos, 0.0, 1.0);
6809
+ vert = transforms[i32(matrixIndex)] * vert;
6810
+ vert.x /= q.halfWidth;
6811
+ vert.y /= q.halfHeight;
6812
+ return vert;
6813
+ }
6814
+
6815
+ fn getPolyColor(p: vec2f, start: u32, count: u32, fIdx: f32) -> vec4f {
6816
+ let uniformColor = colors[i32(fIdx)];
6817
+ if (uniformColor.a == 0.0) {
6818
+ return uniformColor;
6819
+ }
6820
+
6821
+ var sumWeight: f32 = 0.0;
6822
+ var sumColor = vec4f(0.0);
6823
+ for (var i: u32 = 0u; i < count; i = i + 1u) {
6824
+ let pt = polyPts[start + i];
6825
+ let d = distance(p, pt.xy);
6826
+ if (d < 0.1) {
6827
+ return colors[i32(pt.z)];
6828
+ }
6829
+ let w = 1.0 / (d * d * d);
6830
+ sumWeight += w;
6831
+ sumColor += colors[i32(pt.z)] * w;
6832
+ }
6833
+ return sumColor / sumWeight;
6834
+ }
6835
+
6836
+ fn sdPolygon(p: vec2f, start: u32, count: u32, isClosed: f32) -> f32 {
6837
+ var d: f32 = dot(p - polyPts[start].xy, p - polyPts[start].xy);
6838
+ var s: f32 = 1.0;
6839
+ var j: u32 = count - 1u;
6840
+ for (var i: u32 = 0u; i < count; i = i + 1u) {
6841
+ let vi = polyPts[start + i].xy;
6842
+ let vj = polyPts[start + j].xy;
6843
+ let e = vj - vi;
6844
+ let w = p - vi;
6845
+ let b = w - e * clamp(dot(w, e) / dot(e, e), 0.0, 1.0);
6846
+ let bSq = dot(b, b);
6847
+ if (isClosed != 0.0 || i != 0u) {
6848
+ if (bSq < d) { d = bSq; }
6849
+ }
6850
+
6851
+ let condX = p.y >= vi.y;
6852
+ let condY = p.y < vj.y;
6853
+ let condZ = e.x * w.y > e.y * w.x;
6854
+ if ((condX && condY && condZ) || (!condX && !condY && !condZ)) {
6855
+ s = -s;
6856
+ }
6857
+ j = i;
6858
+ }
6859
+ if (isClosed == 0.0) {
6860
+ return sqrt(d);
6861
+ }
6862
+ return s * sqrt(d);
6863
+ }
6864
+
6865
+ @vertex
6866
+ fn vertexMain(v: VertexParams) -> FragParams {
6867
+ var f: FragParams;
6868
+
6869
+ // manually apply transform
6870
+ var vert = vec4f(v.pos, 0.0, 1.0);
6871
+ vert = transforms[i32(v.matrixIndex)] * vert;
6872
+ vert.x /= q.halfWidth;
6873
+ vert.y /= q.halfHeight;
6874
+
6875
+ f.position = vert;
6876
+ f.localPos = v.pos;
6877
+ f.polyStart = u32(v.polyStart + 0.1);
6878
+ f.polyCount = u32(abs(v.polyCount) + 0.1);
6879
+ f.isClosed = step(0.0, v.polyCount);
6880
+ f.fillIndex = v.fillIndex;
6881
+ f.strokeIndex = v.strokeIndex;
6882
+ f.strokeWeight = v.strokeWeight;
6883
+ return f;
6884
+ }
6885
+
6886
+ @fragment
6887
+ fn fragMain(f: FragParams) -> @location(0) vec4f {
6888
+ let fill = getPolyColor(f.localPos, f.polyStart, f.polyCount, f.fillIndex);
6889
+ let stroke = colors[i32(f.strokeIndex)];
6890
+
6891
+ // f.isClosed is derived from the sign of v.polyCount (used for fill closure)
6892
+ let distFill = sdPolygon(f.localPos, f.polyStart, f.polyCount, f.isClosed);
6893
+
6894
+ // encode stroke-closure in the sign of strokeWeight; use absolute stroke weight for width
6895
+ let isClosedStroke = step(0.0, f.strokeWeight);
6896
+ let strokeW = abs(f.strokeWeight);
6897
+ let distStroke = sdPolygon(f.localPos, f.polyStart, f.polyCount, isClosedStroke);
6898
+
6899
+ // compute AA using gradients from both distances (conservative)
6900
+ let dpdx_fill = dpdx(distFill);
6901
+ let dpdy_fill = dpdy(distFill);
6902
+ let gradFill = sqrt(dpdx_fill * dpdx_fill + dpdy_fill * dpdy_fill);
6903
+
6904
+ let dpdx_stroke = dpdx(distStroke);
6905
+ let dpdy_stroke = dpdy(distStroke);
6906
+ let gradStroke = sqrt(dpdx_stroke * dpdx_stroke + dpdy_stroke * dpdy_stroke);
6907
+
6908
+ let aa = clamp(max(gradFill, gradStroke) * 1.5, 0.001, 2.0);
6909
+
6910
+ let halfStroke = strokeW * 0.5;
6911
+
6912
+ var outFragColor: vec4f;
6913
+
6914
+ if (fill.a != 0.0 && strokeW == 0.0) {
6915
+ let fillAlpha = 1.0 - smoothstep(-aa, aa, distFill);
6916
+ if (fillAlpha <= 0.0) { discard; }
6917
+ outFragColor = vec4f(fill.rgb, fill.a * fillAlpha);
6918
+ } else if (fill.a != 0.0) {
6919
+ let fillAlpha = 1.0 - smoothstep(-aa, aa, distFill);
6920
+ let strokeDist = abs(distStroke) - halfStroke;
6921
+ let strokeAlphaMask = 1.0 - smoothstep(-aa, aa, strokeDist);
6922
+
6923
+ if (fillAlpha <= 0.0 && strokeAlphaMask <= 0.0) { discard; }
6924
+
6925
+ let sA = stroke.a * strokeAlphaMask;
6926
+ let fA = fill.a * fillAlpha;
6927
+ let outAlpha = sA + fA * (1.0 - sA);
6928
+ let outCol = stroke.rgb * sA + fill.rgb * fA * (1.0 - sA);
6929
+ outFragColor = vec4f(outCol / max(outAlpha, 1e-5), outAlpha);
6930
+ } else {
6931
+ let strokeDist = abs(distStroke) - halfStroke;
6932
+ let strokeAlpha = 1.0 - smoothstep(-aa, aa, strokeDist);
6933
+
6934
+ if (strokeAlpha <= 0.0) { discard; }
6935
+ outFragColor = vec4f(stroke.rgb, stroke.a * strokeAlpha);
6936
+ }
6937
+ return outFragColor;
6938
+ }
6939
+ `;
6940
+
6941
+ let polygonShader = Q5.device.createShaderModule({
6942
+ label: 'polygonShader',
6943
+ code: $._polygonShaderCode
6944
+ });
6945
+
6946
+ let polygonVertStack = new Float32Array($._isGraphics ? 1000 : 1e7),
6947
+ polygonVertIdx = 0;
6948
+ let polyPtsStack = new Float32Array($._isGraphics ? 1000 : 1e7),
6949
+ polyPtsIdx = 0;
6950
+
6951
+ let polygonVertBuffLayout = {
6952
+ arrayStride: 32, // 8 floats * 4 bytes
6953
+ attributes: [
6954
+ { format: 'float32x2', offset: 0, shaderLocation: 0 },
6955
+ { format: 'float32', offset: 8, shaderLocation: 1 },
6956
+ { format: 'float32', offset: 12, shaderLocation: 2 },
6957
+ { format: 'float32', offset: 16, shaderLocation: 3 },
6958
+ { format: 'float32', offset: 20, shaderLocation: 4 },
6959
+ { format: 'float32', offset: 24, shaderLocation: 5 },
6960
+ { format: 'float32', offset: 28, shaderLocation: 6 }
6961
+ ]
6962
+ };
6963
+
6964
+ let polygonBindGroupLayout = Q5.device.createBindGroupLayout({
6965
+ entries: [{ binding: 0, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'read-only-storage' } }]
6966
+ });
6967
+
6968
+ let polygonPipelineLayout = Q5.device.createPipelineLayout({
6969
+ label: 'polygonPipelineLayout',
6970
+ bindGroupLayouts: [mainLayout, polygonBindGroupLayout]
6971
+ });
6972
+
6973
+ $._pipelineConfigs[7] = {
6974
+ label: 'polygonPipeline',
6975
+ layout: polygonPipelineLayout,
6976
+ vertex: { module: polygonShader, entryPoint: 'vertexMain', buffers: [null, null, polygonVertBuffLayout] },
6977
+ fragment: {
6978
+ module: polygonShader,
6979
+ entryPoint: 'fragMain',
6980
+ targets: [{ format: 'bgra8unorm', blend: $.blendConfigs['source-over'] }]
6981
+ },
6982
+ primitive: { topology: 'triangle-list' },
6983
+ multisample: { count: 4 }
6984
+ };
6985
+ $._pipelines[7] = Q5.device.createRenderPipeline($._pipelineConfigs[7]);
6986
+
6987
+ const addPolygonVert = (x, y, start, count, fIdx, sIdx, sWeight, mIdx) => {
6988
+ let v = polygonVertStack,
6989
+ i = polygonVertIdx;
6990
+ v[i++] = x;
6991
+ v[i++] = y;
6992
+ v[i++] = start;
6993
+ v[i++] = count;
6994
+ v[i++] = fIdx;
6995
+ v[i++] = sIdx;
6996
+ v[i++] = sWeight;
6997
+ v[i++] = mIdx;
6998
+ polygonVertIdx = i;
6999
+ };
7000
+
6691
7001
  $.endShape = (close) => {
6692
7002
  if (curveVertices.length > 0) {
6693
- // duplicate start and end points if necessary
6694
7003
  let points = [...curveVertices];
6695
7004
  if (points.length < 4) {
6696
- // duplicate first and last points
6697
7005
  while (points.length < 4) {
6698
7006
  points.unshift(points[0]);
6699
7007
  points.push(points[points.length - 1]);
6700
7008
  }
6701
7009
  }
6702
-
6703
- // Use curveSegments to determine step size
6704
- let step = 1 / curveSegments;
6705
-
6706
- // calculate catmull-rom spline curve points
6707
7010
  for (let i = 0; i < points.length - 3; i++) {
6708
- let p0 = points[i];
6709
- let p1 = points[i + 1];
6710
- let p2 = points[i + 2];
6711
- let p3 = points[i + 3];
6712
-
6713
- for (let t = 0; t <= 1; t += step) {
6714
- let t2 = t * t;
6715
- let t3 = t2 * t;
6716
-
7011
+ let p0 = points[i],
7012
+ p1 = points[i + 1],
7013
+ p2 = points[i + 2],
7014
+ p3 = points[i + 3];
7015
+ let startT = i === 0 ? 0 : 1;
7016
+ for (let j = startT; j <= curveSegments; j++) {
7017
+ let t = j / curveSegments;
7018
+ let t2 = t * t,
7019
+ t3 = t2 * t;
6717
7020
  let x =
6718
7021
  0.5 *
6719
7022
  (2 * p1.x +
6720
7023
  (-p0.x + p2.x) * t +
6721
7024
  (2 * p0.x - 5 * p1.x + 4 * p2.x - p3.x) * t2 +
6722
7025
  (-p0.x + 3 * p1.x - 3 * p2.x + p3.x) * t3);
6723
-
6724
7026
  let y =
6725
7027
  0.5 *
6726
7028
  (2 * p1.y +
6727
7029
  (-p0.y + p2.y) * t +
6728
7030
  (2 * p0.y - 5 * p1.y + 4 * p2.y - p3.y) * t2 +
6729
7031
  (-p0.y + 3 * p1.y - 3 * p2.y + p3.y) * t3);
6730
-
6731
- sv.push(x, y, fillIdx, matrixIdx);
7032
+ sv.push(x, y, fillIdx);
6732
7033
  shapeVertCount++;
6733
7034
  }
6734
7035
  }
6735
7036
  }
6736
7037
 
6737
7038
  if (!shapeVertCount) return;
6738
- if (shapeVertCount == 1) return $.point(sv[0], sv[1]);
6739
- if (shapeVertCount == 2) return $.line(sv[0], sv[1], sv[4], sv[5]);
6740
-
6741
- // close the shape if requested
6742
- if (close) {
6743
- let firstIndex = 0;
6744
- let lastIndex = (shapeVertCount - 1) * 4;
6745
-
6746
- let firstX = sv[firstIndex];
6747
- let firstY = sv[firstIndex + 1];
6748
- let lastX = sv[lastIndex];
6749
- let lastY = sv[lastIndex + 1];
6750
-
6751
- if (firstX !== lastX || firstY !== lastY) {
6752
- sv.push(firstX, firstY, sv[firstIndex + 2], sv[firstIndex + 3]);
6753
- shapeVertCount++;
6754
- }
7039
+ if (shapeVertCount == 1) {
7040
+ $.point(sv[0], sv[1]);
7041
+ shapeVertCount = 0;
7042
+ sv = [];
7043
+ curveVertices = [];
7044
+ return;
7045
+ }
7046
+ if (shapeVertCount == 2) {
7047
+ $.line(sv[0], sv[1], sv[3], sv[4]);
7048
+ shapeVertCount = 0;
7049
+ sv = [];
7050
+ curveVertices = [];
7051
+ return;
6755
7052
  }
6756
7053
 
6757
- if (doFill) {
6758
- if (shapeVertCount == 5) {
6759
- // for quads, draw two triangles
6760
- addVert(sv[0], sv[1], sv[2], sv[3]); // v0
6761
- addVert(sv[4], sv[5], sv[6], sv[7]); // v1
6762
- addVert(sv[12], sv[13], sv[14], sv[15]); // v3
6763
- addVert(sv[8], sv[9], sv[10], sv[11]); // v2
6764
- drawStack.push(shapesPL, 4);
6765
- } else {
6766
- // triangulate the shape
6767
- for (let i = 1; i < shapeVertCount - 1; i++) {
6768
- let v0 = 0;
6769
- let v1 = i * 4;
6770
- let v2 = (i + 1) * 4;
6771
-
6772
- addVert(sv[v0], sv[v0 + 1], sv[v0 + 2], sv[v0 + 3]);
6773
- addVert(sv[v1], sv[v1 + 1], sv[v1 + 2], sv[v1 + 3]);
6774
- addVert(sv[v2], sv[v2 + 1], sv[v2 + 2], sv[v2 + 3]);
6775
- }
6776
- drawStack.push(shapesPL, (shapeVertCount - 2) * 3);
6777
- }
7054
+ if (matrixDirty) saveMatrix();
7055
+ let ti = matrixIdx;
7056
+
7057
+ let isAutoClosed = false;
7058
+ let isClosedPath = false;
7059
+ let isClosedForStroke = false;
7060
+ let firstX = sv[0],
7061
+ firstY = sv[1];
7062
+ let lastX = sv[(shapeVertCount - 1) * 3],
7063
+ lastY = sv[(shapeVertCount - 1) * 3 + 1];
7064
+
7065
+ if (firstX === lastX && firstY === lastY) {
7066
+ isClosedPath = true;
7067
+ isClosedForStroke = true;
7068
+ } else if (close || doFill) {
7069
+ sv.push(firstX, firstY, sv[2]);
7070
+ shapeVertCount++;
7071
+ isAutoClosed = !close;
7072
+ isClosedPath = true;
7073
+ isClosedForStroke = !!close;
6778
7074
  }
6779
7075
 
6780
- if (doStroke) {
6781
- // draw lines between vertices
6782
- for (let i = 0; i < shapeVertCount - 1; i++) {
6783
- let v1 = i * 4;
6784
- let v2 = (i + 1) * 4;
6785
- $.line(sv[v1], sv[v1 + 1], sv[v2], sv[v2 + 1]);
7076
+ let runSDF = shapeVertCount >= 3 && doStroke && hswScaled > 3;
7077
+
7078
+ if (runSDF) {
7079
+ let fi = doFill ? fillIdx : 0,
7080
+ si = strokeIdx;
7081
+
7082
+ let polyStart = polyPtsIdx / 4,
7083
+ polyCount = isClosedPath ? shapeVertCount - 1 : shapeVertCount;
7084
+ for (let i = 0; i < polyCount; i++) {
7085
+ polyPtsStack[polyPtsIdx++] = sv[i * 3];
7086
+ polyPtsStack[polyPtsIdx++] = sv[i * 3 + 1];
7087
+ polyPtsStack[polyPtsIdx++] = sv[i * 3 + 2];
7088
+ polyPtsStack[polyPtsIdx++] = ti;
7089
+ }
7090
+
7091
+ let minX = Infinity,
7092
+ minY = Infinity,
7093
+ maxX = -Infinity,
7094
+ maxY = -Infinity;
7095
+ for (let i = 0; i < shapeVertCount; i++) {
7096
+ let vx = sv[i * 3],
7097
+ vy = sv[i * 3 + 1];
7098
+ if (vx < minX) minX = vx;
7099
+ if (vx > maxX) maxX = vx;
7100
+ if (vy < minY) minY = vy;
7101
+ if (vy > maxY) maxY = vy;
7102
+ }
7103
+ let padding = sw * 0.5 + 1.0; // padding for stroke and AA
7104
+ minX -= padding;
7105
+ minY -= padding;
7106
+ maxX += padding;
7107
+ maxY += padding;
7108
+
7109
+ // single SDF pass: encode fill-closure in polyCount sign, stroke-closure in strokeWeight sign
7110
+ let passedCount = doFill ? polyCount : -polyCount;
7111
+ let strokeWeightSigned = isClosedForStroke ? sw : -sw;
7112
+ addPolygonVert(minX, minY, polyStart, passedCount, fi, si, strokeWeightSigned, ti);
7113
+ addPolygonVert(maxX, minY, polyStart, passedCount, fi, si, strokeWeightSigned, ti);
7114
+ addPolygonVert(minX, maxY, polyStart, passedCount, fi, si, strokeWeightSigned, ti);
7115
+
7116
+ addPolygonVert(maxX, minY, polyStart, passedCount, fi, si, strokeWeightSigned, ti);
7117
+ addPolygonVert(maxX, maxY, polyStart, passedCount, fi, si, strokeWeightSigned, ti);
7118
+ addPolygonVert(minX, maxY, polyStart, passedCount, fi, si, strokeWeightSigned, ti);
7119
+
7120
+ drawStack.push(polygonPL, 6);
7121
+ } else {
7122
+ if (doFill) {
7123
+ if (shapeVertCount == 5) {
7124
+ // Quads
7125
+ addVert(sv[0], sv[1], sv[2], ti);
7126
+ addVert(sv[3], sv[4], sv[5], ti);
7127
+ addVert(sv[9], sv[10], sv[11], ti);
7128
+ addVert(sv[6], sv[7], sv[8], ti);
7129
+ drawStack.push(shapesPL, 4);
7130
+ } else {
7131
+ // Triangulation fan
7132
+ for (let i = 1; i < shapeVertCount - 1; i++) {
7133
+ let v0 = 0,
7134
+ v1 = i * 3,
7135
+ v2 = (i + 1) * 3;
7136
+ addVert(sv[v0], sv[v0 + 1], sv[v0 + 2], ti);
7137
+ addVert(sv[v1], sv[v1 + 1], sv[v1 + 2], ti);
7138
+ addVert(sv[v2], sv[v2 + 1], sv[v2 + 2], ti);
7139
+ }
7140
+ drawStack.push(shapesPL, (shapeVertCount - 2) * 3);
7141
+ }
7142
+ }
7143
+ if (doStroke) {
7144
+ let maxLines = isAutoClosed ? shapeVertCount - 2 : shapeVertCount - 1;
7145
+ for (let i = 0; i < maxLines; i++) {
7146
+ let v1 = i * 3,
7147
+ v2 = (i + 1) * 3;
7148
+ $.line(sv[v1], sv[v1 + 1], sv[v2], sv[v2 + 1]);
7149
+ }
6786
7150
  }
6787
- let v1 = (shapeVertCount - 1) * 4;
6788
- let v2 = 0;
6789
- if (close) $.line(sv[v1], sv[v1 + 1], sv[v2], sv[v2 + 1]);
6790
7151
  }
6791
7152
 
6792
- // reset for the next shape
6793
7153
  shapeVertCount = 0;
6794
7154
  sv = [];
6795
7155
  curveVertices = [];
6796
7156
  };
6797
7157
 
6798
- $.curve = (x1, y1, x2, y2, x3, y3, x4, y4) => {
6799
- $.beginShape();
6800
- $.curveVertex(x1, y1);
6801
- $.curveVertex(x2, y2);
6802
- $.curveVertex(x3, y3);
6803
- $.curveVertex(x4, y4);
6804
- $.endShape();
6805
- };
6806
-
6807
- $.bezier = (x1, y1, x2, y2, x3, y3, x4, y4) => {
6808
- $.beginShape();
6809
- $.vertex(x1, y1);
6810
- $.bezierVertex(x2, y2, x3, y3, x4, y4);
6811
- $.endShape();
6812
- };
6813
-
6814
7158
  $.triangle = (x1, y1, x2, y2, x3, y3) => {
6815
7159
  $.beginShape();
6816
7160
  $.vertex(x1, y1);
@@ -6828,21 +7172,6 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
6828
7172
  $.endShape(true);
6829
7173
  };
6830
7174
 
6831
- function addQuad(x1, y1, x2, y2, x3, y3, x4, y4, ci, ti) {
6832
- addVert(x1, y1, ci, ti); // v0
6833
- addVert(x2, y2, ci, ti); // v1
6834
- addVert(x4, y4, ci, ti); // v3
6835
- addVert(x3, y3, ci, ti); // v2
6836
- drawStack.push(shapesPL, 4);
6837
- }
6838
-
6839
- $.plane = (x, y, w, h) => {
6840
- h ??= w;
6841
- let [l, r, t, b] = calcBox(x, y, w, h, 'center');
6842
- if (matrixDirty) saveMatrix();
6843
- addQuad(l, t, r, t, r, b, l, b, fillIdx, matrixIdx);
6844
- };
6845
-
6846
7175
  /* RECT */
6847
7176
 
6848
7177
  let rectPL = 5;
@@ -7135,7 +7464,21 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
7135
7464
  };
7136
7465
 
7137
7466
  $.line = (x1, y1, x2, y2) => {
7138
- if (doStroke) addCapsule(x1, y1, x2, y2, qsw, hsw, 0);
7467
+ if (!doStroke) return;
7468
+ if (matrixDirty) saveMatrix();
7469
+
7470
+ let dx = x2 - x1,
7471
+ dy = y2 - y1,
7472
+ sqLen = dx * dx + dy * dy;
7473
+
7474
+ if (sqLen === 0) return;
7475
+
7476
+ let len = Math.sqrt(sqLen),
7477
+ ratio = hsw / len,
7478
+ nx = -dy * ratio,
7479
+ ny = dx * ratio;
7480
+
7481
+ addQuad(x1 + nx, y1 + ny, x1 - nx, y1 - ny, x2 - nx, y2 - ny, x2 + nx, y2 + ny, strokeIdx, matrixIdx);
7139
7482
  };
7140
7483
 
7141
7484
  /* ELLIPSE */
@@ -8778,6 +9121,8 @@ fn fragMain(f : FragParams) -> @location(0) vec4f {
8778
9121
  transformsBuffer?.destroy();
8779
9122
  colorsBuffer?.destroy();
8780
9123
  shapesVertBuff?.destroy();
9124
+ polygonVertBuff?.destroy();
9125
+ polyPtsBuffer?.destroy();
8781
9126
  imgVertBuff?.destroy();
8782
9127
  charBuffer?.destroy();
8783
9128
  textBuffer?.destroy();