q5 3.0.6 → 3.1.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 +48 -2
  4. package/q5.js +693 -390
  5. package/q5.min.js +2 -2
package/q5.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * q5.js
3
- * @version 3.0
3
+ * @version 3.1
4
4
  * @author quinton-ashley
5
5
  * @contributors evanalulu, Tezumie, ormaq, Dukemz, LingDong-
6
6
  * @license LGPL-3.0
@@ -385,7 +385,7 @@ function createCanvas(w, h, opt) {
385
385
  }
386
386
  }
387
387
 
388
- Q5.version = Q5.VERSION = '3.0';
388
+ Q5.version = Q5.VERSION = '3.1';
389
389
 
390
390
  if (typeof document == 'object') {
391
391
  document.addEventListener('DOMContentLoaded', () => {
@@ -1354,6 +1354,8 @@ Q5.renderers.c2d.image = ($, q) => {
1354
1354
  return g;
1355
1355
  };
1356
1356
 
1357
+ $._imageMode = Q5.CORNER;
1358
+
1357
1359
  $.imageMode = (mode) => ($._imageMode = mode);
1358
1360
 
1359
1361
  $.image = (img, dx, dy, dw, dh, sx = 0, sy = 0, sw, sh) => {
@@ -4926,8 +4928,8 @@ struct Q5 {
4926
4928
  frameLayout,
4927
4929
  frameSampler,
4928
4930
  frameBindGroup,
4929
- colorIndex = 1,
4930
- colorStackIndex = 8,
4931
+ colorIndex = 2,
4932
+ colorStackIndex = 12,
4931
4933
  prevFramePL = 0,
4932
4934
  framePL = 0;
4933
4935
 
@@ -4945,6 +4947,7 @@ struct Q5 {
4945
4947
 
4946
4948
  // prettier-ignore
4947
4949
  colorStack.set([
4950
+ 0, 0, 0, 0, // transparent
4948
4951
  0, 0, 0, 1, // black
4949
4952
  1, 1, 1, 1 // white
4950
4953
  ]);
@@ -5166,12 +5169,13 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
5166
5169
  doStroke = true,
5167
5170
  fillSet = false,
5168
5171
  strokeSet = false,
5169
- strokeIdx = 0,
5170
- fillIdx = 1,
5171
- tintIdx = 1,
5172
+ strokeIdx = 1,
5173
+ fillIdx = 2,
5174
+ tintIdx = 2,
5172
5175
  globalAlpha = 1,
5173
5176
  sw = 1, // stroke weight
5174
- hsw = 0.5, // half the stroke weight
5177
+ hsw = 0.5, // half of the stroke weight
5178
+ qsw = 0.25, // quarter of the stroke weight
5175
5179
  scaledHSW = 0.5;
5176
5180
 
5177
5181
  $.fill = (r, g, b, a) => {
@@ -5191,7 +5195,7 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
5191
5195
  $.opacity = (a) => (globalAlpha = a);
5192
5196
  $.noFill = () => (doFill = false);
5193
5197
  $.noStroke = () => (doStroke = false);
5194
- $.noTint = () => (tintIdx = 1);
5198
+ $.noTint = () => (tintIdx = 2);
5195
5199
 
5196
5200
  $.strokeWeight = (v) => {
5197
5201
  if (v === undefined) return sw;
@@ -5202,6 +5206,7 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
5202
5206
  v = Math.abs(v);
5203
5207
  sw = v;
5204
5208
  hsw = v / 2;
5209
+ qsw = v / 4;
5205
5210
  scaledHSW = hsw * _scale;
5206
5211
  };
5207
5212
 
@@ -5250,13 +5255,20 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
5250
5255
  matrixDirty = true;
5251
5256
  };
5252
5257
 
5253
- $.rotate = $.rotateZ = (a) => {
5258
+ $.rotate = $.rotateZ = (a, a1) => {
5254
5259
  if (!a) return;
5255
- if ($._angleMode) a *= $._DEGTORAD;
5256
5260
 
5257
- let cosR = Math.cos(a),
5258
- sinR = Math.sin(a),
5259
- m = matrix,
5261
+ let cosR, sinR;
5262
+ if (a1 === undefined) {
5263
+ if ($._angleMode) a *= $._DEGTORAD;
5264
+ cosR = Math.cos(a);
5265
+ sinR = Math.sin(a);
5266
+ } else {
5267
+ cosR = a;
5268
+ sinR = a1;
5269
+ }
5270
+
5271
+ let m = matrix,
5260
5272
  m0 = m[0],
5261
5273
  m1 = m[1],
5262
5274
  m4 = m[4],
@@ -5560,7 +5572,11 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
5560
5572
  $.pop();
5561
5573
  } else {
5562
5574
  addColor(r, g, b, a);
5563
- addRect(-c.hw, c.hh, c.hw, c.hh, c.hw, -c.hh, -c.hw, -c.hh, colorIndex, 0);
5575
+ let lx = -c.hw,
5576
+ rx = c.hw,
5577
+ ty = -c.hh,
5578
+ by = c.hh;
5579
+ addQuad(lx, ty, rx, ty, rx, by, lx, by, colorIndex, 0);
5564
5580
  }
5565
5581
  };
5566
5582
 
@@ -5695,6 +5711,8 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
5695
5711
  }
5696
5712
  }
5697
5713
 
5714
+ // prepare to render text
5715
+
5698
5716
  if (charStack.length) {
5699
5717
  // calculate total buffer size for text data
5700
5718
  let totalTextSize = 0;
@@ -5741,9 +5759,33 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
5741
5759
  });
5742
5760
  }
5743
5761
 
5762
+ // prepare to render rectangles
5763
+
5764
+ // prettier-ignore
5765
+ Q5.device.queue.writeBuffer(
5766
+ rectBuffer,
5767
+ 0,
5768
+ rectStack.buffer,
5769
+ rectStack.byteOffset,
5770
+ rectStackIdx * 4
5771
+ );
5772
+
5773
+ // prepare to render ellipses
5774
+
5775
+ // prettier-ignore
5776
+ Q5.device.queue.writeBuffer(
5777
+ ellipseBuffer,
5778
+ 0,
5779
+ ellipseStack.buffer,
5780
+ ellipseStack.byteOffset,
5781
+ ellipseStackIdx * 4
5782
+ );
5783
+
5744
5784
  let drawVertOffset = 0,
5745
5785
  imageVertOffset = 0,
5746
5786
  textCharOffset = 0,
5787
+ rectIdx = 0,
5788
+ ellipseIdx = 0,
5747
5789
  curPipelineIndex = -1;
5748
5790
 
5749
5791
  for (let i = 0; i < drawStack.length; i += 2) {
@@ -5762,9 +5804,25 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
5762
5804
 
5763
5805
  curPipelineIndex = drawStack[i];
5764
5806
  pass.setPipeline($._pipelines[curPipelineIndex]);
5807
+
5808
+ if (curPipelineIndex == 5) {
5809
+ pass.setIndexBuffer(rectIndexBuffer, 'uint16');
5810
+ pass.setBindGroup(1, rectBindGroup);
5811
+ } else if (curPipelineIndex == 6) {
5812
+ pass.setIndexBuffer(ellipseIndexBuffer, 'uint16');
5813
+ pass.setBindGroup(1, ellipseBindGroup);
5814
+ }
5765
5815
  }
5766
5816
 
5767
- if (curPipelineIndex == 4 || curPipelineIndex >= 4000) {
5817
+ if (curPipelineIndex == 6) {
5818
+ // draw an ellipse
5819
+ pass.drawIndexed(18, v, 0, 0, ellipseIdx);
5820
+ ellipseIdx += v;
5821
+ } else if (curPipelineIndex == 5) {
5822
+ // draw a rectangle
5823
+ pass.drawIndexed(6, v, 0, 0, rectIdx);
5824
+ rectIdx += v;
5825
+ } else if (curPipelineIndex == 4 || curPipelineIndex >= 4000) {
5768
5826
  // draw text
5769
5827
  let o = drawStack[i + 2];
5770
5828
  pass.setBindGroup(1, fontsArr[o].bindGroup);
@@ -5826,8 +5884,8 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
5826
5884
 
5827
5885
  // clear the stacks for the next frame
5828
5886
  drawStack.splice(0, drawStack.length);
5829
- colorIndex = 1;
5830
- colorStackIndex = 8;
5887
+ colorIndex = 2;
5888
+ colorStackIndex = 12;
5831
5889
  matrices = [matrices[0]];
5832
5890
  matricesIdxStack = [];
5833
5891
 
@@ -5841,6 +5899,10 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
5841
5899
  vidFrames = 0;
5842
5900
  charStack = [];
5843
5901
  textStack = [];
5902
+ rectStack = new Float32Array(Q5.MAX_RECTS * 16);
5903
+ rectStackIdx = 0;
5904
+ ellipseStack = new Float32Array(Q5.MAX_ELLIPSES * 16);
5905
+ ellipseStackIdx = 0;
5844
5906
 
5845
5907
  // destroy buffers
5846
5908
  Q5.device.queue.onSubmittedWorkDone().then(() => {
@@ -5903,8 +5965,7 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
5903
5965
  let shapesVertStack = new Float32Array($._isGraphics ? 1000 : 1e7),
5904
5966
  shapesVertIdx = 0;
5905
5967
  const TAU = Math.PI * 2,
5906
- HALF_PI = Math.PI / 2,
5907
- THREE_HALF_PI = Math.PI * 1.5;
5968
+ HALF_PI = Math.PI / 2;
5908
5969
 
5909
5970
  let shapesVertBuffLayout = {
5910
5971
  arrayStride: 16, // 4 floats * 4 bytes
@@ -5915,14 +5976,14 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
5915
5976
  ]
5916
5977
  };
5917
5978
 
5918
- let pipelineLayout = Q5.device.createPipelineLayout({
5979
+ let shapesPipelineLayout = Q5.device.createPipelineLayout({
5919
5980
  label: 'shapesPipelineLayout',
5920
5981
  bindGroupLayouts: $._bindGroupLayouts
5921
5982
  });
5922
5983
 
5923
5984
  $._pipelineConfigs[1] = {
5924
5985
  label: 'shapesPipeline',
5925
- layout: pipelineLayout,
5986
+ layout: shapesPipelineLayout,
5926
5987
  vertex: {
5927
5988
  module: shapesShader,
5928
5989
  entryPoint: 'vertexMain',
@@ -5949,344 +6010,6 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
5949
6010
  shapesVertIdx = i;
5950
6011
  };
5951
6012
 
5952
- const addRect = (x1, y1, x2, y2, x3, y3, x4, y4, ci, ti) => {
5953
- let v = shapesVertStack,
5954
- i = shapesVertIdx;
5955
-
5956
- v[i++] = x1;
5957
- v[i++] = y1;
5958
- v[i++] = ci;
5959
- v[i++] = ti;
5960
-
5961
- v[i++] = x2;
5962
- v[i++] = y2;
5963
- v[i++] = ci;
5964
- v[i++] = ti;
5965
-
5966
- v[i++] = x4;
5967
- v[i++] = y4;
5968
- v[i++] = ci;
5969
- v[i++] = ti;
5970
-
5971
- v[i++] = x3;
5972
- v[i++] = y3;
5973
- v[i++] = ci;
5974
- v[i++] = ti;
5975
-
5976
- shapesVertIdx = i;
5977
- drawStack.push(shapesPL, 4);
5978
- };
5979
-
5980
- const addArc = (x, y, a, b, startAngle, endAngle, n, ci, ti) => {
5981
- let angleRange = endAngle - startAngle;
5982
- let angleIncrement = angleRange / n;
5983
- let t = startAngle;
5984
-
5985
- let v = shapesVertStack,
5986
- i = shapesVertIdx;
5987
-
5988
- for (let j = 0; j <= n; j++) {
5989
- // add center vertex
5990
- v[i++] = x;
5991
- v[i++] = y;
5992
- v[i++] = ci;
5993
- v[i++] = ti;
5994
-
5995
- // calculate perimeter vertex
5996
- let vx = x + a * Math.cos(t);
5997
- let vy = y + b * Math.sin(t);
5998
-
5999
- // add perimeter vertex
6000
- v[i++] = vx;
6001
- v[i++] = vy;
6002
- v[i++] = ci;
6003
- v[i++] = ti;
6004
-
6005
- t += angleIncrement;
6006
- }
6007
-
6008
- shapesVertIdx = i;
6009
- drawStack.push(shapesPL, (n + 1) * 2);
6010
- };
6011
-
6012
- const addArcStroke = (x, y, outerA, outerB, innerA, innerB, startAngle, endAngle, n, ci, ti) => {
6013
- let angleRange = endAngle - startAngle;
6014
- let angleIncrement = angleRange / n;
6015
- let t = startAngle;
6016
-
6017
- let v = shapesVertStack,
6018
- i = shapesVertIdx;
6019
-
6020
- for (let j = 0; j <= n; j++) {
6021
- // Outer vertex
6022
- let vxOuter = x + outerA * Math.cos(t);
6023
- let vyOuter = y + outerB * Math.sin(t);
6024
-
6025
- // Inner vertex
6026
- let vxInner = x + innerA * Math.cos(t);
6027
- let vyInner = y + innerB * Math.sin(t);
6028
-
6029
- // Add vertices for triangle strip
6030
- v[i++] = vxOuter;
6031
- v[i++] = vyOuter;
6032
- v[i++] = ci;
6033
- v[i++] = ti;
6034
-
6035
- v[i++] = vxInner;
6036
- v[i++] = vyInner;
6037
- v[i++] = ci;
6038
- v[i++] = ti;
6039
-
6040
- t += angleIncrement;
6041
- }
6042
-
6043
- shapesVertIdx = i;
6044
- drawStack.push(shapesPL, (n + 1) * 2);
6045
- };
6046
-
6047
- let _rectMode = 'corner';
6048
-
6049
- $.rectMode = (x) => (_rectMode = x);
6050
-
6051
- $.rect = (x, y, w, h, rr = 0) => {
6052
- h ??= w;
6053
- let [l, r, t, b] = calcBox(x, y, w, h, _rectMode);
6054
- let ci, ti;
6055
- if (matrixDirty) saveMatrix();
6056
- ti = matrixIdx;
6057
-
6058
- if (!rr) {
6059
- if (doFill) {
6060
- ci = fillIdx;
6061
- addRect(l, t, r, t, r, b, l, b, ci, ti);
6062
- }
6063
-
6064
- if (doStroke) {
6065
- ci = strokeIdx;
6066
-
6067
- // Calculate stroke positions
6068
- let lsw = l - hsw,
6069
- rsw = r + hsw,
6070
- tsw = t + hsw,
6071
- bsw = b - hsw,
6072
- lpsw = l + hsw,
6073
- rpsw = r - hsw,
6074
- tpsw = t - hsw,
6075
- bpsw = b + hsw;
6076
-
6077
- addRect(lsw, tpsw, rsw, tpsw, rsw, tsw, lsw, tsw, ci, ti); // Top
6078
- addRect(lsw, bsw, rsw, bsw, rsw, bpsw, lsw, bpsw, ci, ti); // Bottom
6079
-
6080
- // Adjust side strokes to avoid overlapping corners
6081
- tsw = t - hsw;
6082
- bsw = b + hsw;
6083
-
6084
- addRect(lsw, tsw, lpsw, tsw, lpsw, bsw, lsw, bsw, ci, ti); // Left
6085
- addRect(rpsw, tsw, rsw, tsw, rsw, bsw, rpsw, bsw, ci, ti); // Right
6086
- }
6087
- return;
6088
- }
6089
-
6090
- l += rr;
6091
- r -= rr;
6092
- t += rr;
6093
- b -= rr;
6094
-
6095
- // Clamp radius
6096
- rr = Math.min(rr, Math.min(w, h) / 2);
6097
-
6098
- let n = getArcSegments(rr * _scale);
6099
-
6100
- let trr = t - rr,
6101
- brr = b + rr,
6102
- lrr = l - rr,
6103
- rrr = r + rr;
6104
-
6105
- if (doFill) {
6106
- ci = fillIdx;
6107
- // Corner arcs
6108
- addArc(l, t, rr, -rr, HALF_PI, Math.PI, n, ci, ti); // top-left
6109
- addArc(r, t, rr, -rr, HALF_PI, 0, n, ci, ti); // top-right
6110
- addArc(r, b, rr, -rr, 0, -HALF_PI, n, ci, ti); // bottom-right
6111
- addArc(l, b, rr, -rr, -HALF_PI, -Math.PI, n, ci, ti); // bottom-left
6112
-
6113
- addRect(l, trr, r, trr, r, brr, l, brr, ci, ti); // center
6114
- addRect(l, t, lrr, t, lrr, b, l, b, ci, ti); // Left
6115
- addRect(rrr, t, r, t, r, b, rrr, b, ci, ti); // Right
6116
- }
6117
-
6118
- if (doStroke) {
6119
- ci = strokeIdx;
6120
-
6121
- let outerA = rr + hsw,
6122
- outerB = rr + hsw,
6123
- innerA = rr - hsw,
6124
- innerB = rr - hsw;
6125
-
6126
- // Corner arc strokes
6127
- addArcStroke(l, t, outerA, -outerB, innerA, -innerB, Math.PI, HALF_PI, n, ci, ti); // top-left
6128
- addArcStroke(r, t, outerA, -outerB, innerA, -innerB, HALF_PI, 0, n, ci, ti); // top-right
6129
- addArcStroke(r, b, outerA, -outerB, innerA, -innerB, 0, -HALF_PI, n, ci, ti); // bottom-right
6130
- addArcStroke(l, b, outerA, -outerB, innerA, -innerB, -HALF_PI, -Math.PI, n, ci, ti); // bottom-left
6131
-
6132
- let lrrMin = lrr - hsw,
6133
- lrrMax = lrr + hsw,
6134
- rrrMin = rrr - hsw,
6135
- rrrMax = rrr + hsw,
6136
- trrMin = trr - hsw,
6137
- trrMax = trr + hsw,
6138
- brrMin = brr - hsw,
6139
- brrMax = brr + hsw;
6140
-
6141
- // Side strokes - positioned outside
6142
- addRect(lrrMin, t, lrrMax, t, lrrMax, b, lrrMin, b, ci, ti); // Left
6143
- addRect(rrrMin, t, rrrMax, t, rrrMax, b, rrrMin, b, ci, ti); // Right
6144
- addRect(l, trrMin, r, trrMin, r, trrMax, l, trrMax, ci, ti); // Top
6145
- addRect(l, brrMin, r, brrMin, r, brrMax, l, brrMax, ci, ti); // Bottom
6146
- }
6147
- };
6148
-
6149
- $.square = (x, y, s) => $.rect(x, y, s, s);
6150
-
6151
- $.plane = (x, y, w, h) => {
6152
- h ??= w;
6153
- let [l, r, t, b] = calcBox(x, y, w, h, 'center');
6154
- if (matrixDirty) saveMatrix();
6155
- addRect(l, t, r, t, r, b, l, b, fillIdx, matrixIdx);
6156
- };
6157
-
6158
- // prettier-ignore
6159
- const getArcSegments = (d) =>
6160
- d < 2 ? 6 :
6161
- d < 3 ? 8 :
6162
- d < 5 ? 10 :
6163
- d < 8 ? 12 :
6164
- d < 10 ? 14 :
6165
- d < 11 ? 16 :
6166
- d < 12 ? 18 :
6167
- d < 14 ? 20 :
6168
- d < 17 ? 22 :
6169
- d < 21 ? 24 :
6170
- d < 24 ? 26 :
6171
- d < 28 ? 28 :
6172
- d < 32 ? 30 :
6173
- d < 36 ? 32 :
6174
- d < 42 ? 34 :
6175
- d < 48 ? 36 :
6176
- d < 49 ? 38 :
6177
- d < 57 ? 40 :
6178
- d < 75 ? 44 :
6179
- d < 100 ? 48 :
6180
- d < 130 ? 52 :
6181
- d < 176 ? 56 :
6182
- d < 230 ? 60 :
6183
- d < 292 ? 64 :
6184
- d < 600 ? 70 :
6185
- d < 900 ? 80 :
6186
- d < 1200 ? 90 :
6187
- 100;
6188
-
6189
- let _ellipseMode = 'center';
6190
-
6191
- $.ellipseMode = (x) => (_ellipseMode = x);
6192
-
6193
- function applyEllipseMode(x, y, w, h) {
6194
- h ??= w;
6195
- let a, b;
6196
- if (_ellipseMode == $.CENTER) {
6197
- a = w / 2;
6198
- b = h / 2;
6199
- } else if (_ellipseMode == $.RADIUS) {
6200
- a = w;
6201
- b = h;
6202
- } else if (_ellipseMode == $.CORNER) {
6203
- x += w / 2;
6204
- y += h / 2;
6205
- a = w / 2;
6206
- b = h / 2;
6207
- } else if (_ellipseMode == $.CORNERS) {
6208
- x = (x + w) / 2;
6209
- y = (y + h) / 2;
6210
- a = (w - x) / 2;
6211
- b = (h - y) / 2;
6212
- }
6213
- return [x, y, a, b];
6214
- }
6215
-
6216
- $.ellipse = (x, y, w, h) => {
6217
- let a, b;
6218
- [x, y, a, b] = applyEllipseMode(x, y, w, h);
6219
-
6220
- let n = getArcSegments(Math.max(Math.abs(a), Math.abs(b)) * _scale);
6221
-
6222
- if (matrixDirty) saveMatrix();
6223
- let ti = matrixIdx;
6224
-
6225
- if (doFill) {
6226
- addArc(x, y, a, b, 0, TAU, n, fillIdx, ti);
6227
- }
6228
- if (doStroke) {
6229
- // Draw the stroke as a ring using triangle strips
6230
- addArcStroke(x, y, a + hsw, b + hsw, a - hsw, b - hsw, 0, TAU, n, strokeIdx, ti);
6231
- }
6232
- };
6233
-
6234
- $.circle = (x, y, d) => $.ellipse(x, y, d, d);
6235
-
6236
- $.arc = (x, y, w, h, start, stop) => {
6237
- if (start === stop) return $.ellipse(x, y, w, h);
6238
-
6239
- // Convert angles if needed
6240
- if ($._angleMode) {
6241
- start = $.radians(start);
6242
- stop = $.radians(stop);
6243
- }
6244
-
6245
- // Normalize angles
6246
- start %= TAU;
6247
- stop %= TAU;
6248
- if (start < 0) start += TAU;
6249
- if (stop < 0) stop += TAU;
6250
- if (start > stop) stop += TAU;
6251
- if (start == stop) return $.ellipse(x, y, w, h);
6252
-
6253
- // Calculate position based on ellipseMode
6254
- let a, b;
6255
- [x, y, a, b] = applyEllipseMode(x, y, w, h);
6256
-
6257
- if (matrixDirty) saveMatrix();
6258
- let ti = matrixIdx;
6259
- let n = getArcSegments(Math.max(Math.abs(a), Math.abs(b)) * _scale);
6260
-
6261
- // Draw fill
6262
- if (doFill) {
6263
- addArc(x, y, a, b, start, stop, n, fillIdx, ti);
6264
- }
6265
-
6266
- // Draw stroke
6267
- if (doStroke) {
6268
- addArcStroke(x, y, a + hsw, b + hsw, a - hsw, b - hsw, start, stop, n, strokeIdx, ti);
6269
- if (_strokeCap == 'round') {
6270
- addArc(x + a * Math.cos(start), y + b * Math.sin(start), hsw, hsw, 0, TAU, n, strokeIdx, ti);
6271
- addArc(x + a * Math.cos(stop), y + b * Math.sin(stop), hsw, hsw, 0, TAU, n, strokeIdx, ti);
6272
- }
6273
- }
6274
- };
6275
-
6276
- $.point = (x, y) => {
6277
- if (matrixDirty) saveMatrix();
6278
- let ti = matrixIdx,
6279
- ci = strokeIdx;
6280
-
6281
- if (scaledHSW < 1) {
6282
- let [l, r, t, b] = calcBox(x, y, sw, sw, 'corner');
6283
- addRect(l, t, r, t, r, b, l, b, ci, ti);
6284
- } else {
6285
- let n = getArcSegments(scaledHSW);
6286
- addArc(x, y, hsw, hsw, 0, TAU, n, ci, ti);
6287
- }
6288
- };
6289
-
6290
6013
  let _strokeCap = 'round',
6291
6014
  _strokeJoin = 'round';
6292
6015
 
@@ -6297,29 +6020,6 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
6297
6020
  _strokeJoin = 'none';
6298
6021
  };
6299
6022
 
6300
- $.line = (x1, y1, x2, y2) => {
6301
- if (matrixDirty) saveMatrix();
6302
- let ti = matrixIdx,
6303
- ci = strokeIdx;
6304
-
6305
- // calculate the direction vector and length
6306
- let dx = x2 - x1,
6307
- dy = y2 - y1,
6308
- length = Math.hypot(dx, dy);
6309
-
6310
- // calculate the perpendicular vector for line thickness
6311
- let px = -(dy / length) * hsw,
6312
- py = (dx / length) * hsw;
6313
-
6314
- addRect(x1 + px, y1 + py, x1 - px, y1 - py, x2 - px, y2 - py, x2 + px, y2 + py, ci, ti);
6315
-
6316
- if (scaledHSW > 1 && _strokeCap != 'square') {
6317
- let n = getArcSegments(scaledHSW);
6318
- addArc(x1, y1, hsw, hsw, 0, TAU, n, ci, ti);
6319
- addArc(x2, y2, hsw, hsw, 0, TAU, n, ci, ti);
6320
- }
6321
- };
6322
-
6323
6023
  let curveSegments = 20;
6324
6024
  $.curveDetail = (v) => (curveSegments = v);
6325
6025
 
@@ -6482,23 +6182,18 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
6482
6182
  }
6483
6183
 
6484
6184
  if (doStroke) {
6485
- let n = getArcSegments(scaledHSW),
6486
- ti = matrixIdx,
6487
- ogStrokeCap = _strokeCap;
6488
- _strokeCap = 'square';
6489
6185
  // draw lines between vertices
6490
6186
  for (let i = 0; i < shapeVertCount - 1; i++) {
6491
6187
  let v1 = i * 4;
6492
6188
  let v2 = (i + 1) * 4;
6493
6189
  $.line(sv[v1], sv[v1 + 1], sv[v2], sv[v2 + 1]);
6494
6190
 
6495
- addArc(sv[v1], sv[v1 + 1], hsw, hsw, 0, TAU, n, strokeIdx, ti);
6191
+ // addEllipse(sv[v1], sv[v1 + 1], qsw, qsw, 0, TAU, hsw, 0);
6496
6192
  }
6497
6193
  let v1 = (shapeVertCount - 1) * 4;
6498
6194
  let v2 = 0;
6499
6195
  if (close) $.line(sv[v1], sv[v1 + 1], sv[v2], sv[v2 + 1]);
6500
- addArc(sv[v1], sv[v1 + 1], hsw, hsw, 0, TAU, n, strokeIdx, ti);
6501
- _strokeCap = ogStrokeCap;
6196
+ // addEllipse(sv[v1], sv[v1 + 1], qsw, qsw, 0, TAU, hsw, 0);
6502
6197
  }
6503
6198
 
6504
6199
  // reset for the next shape
@@ -6540,6 +6235,610 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
6540
6235
  $.endShape(true);
6541
6236
  };
6542
6237
 
6238
+ function addQuad(x1, y1, x2, y2, x3, y3, x4, y4, ci, ti) {
6239
+ addVert(x1, y1, ci, ti); // v0
6240
+ addVert(x2, y2, ci, ti); // v1
6241
+ addVert(x4, y4, ci, ti); // v3
6242
+ addVert(x3, y3, ci, ti); // v2
6243
+ drawStack.push(shapesPL, 4);
6244
+ }
6245
+
6246
+ $.plane = (x, y, w, h) => {
6247
+ h ??= w;
6248
+ let [l, r, t, b] = calcBox(x, y, w, h, 'center');
6249
+ if (matrixDirty) saveMatrix();
6250
+ addQuad(l, t, r, t, r, b, l, b, fillIdx, matrixIdx);
6251
+ };
6252
+
6253
+ /* RECT */
6254
+
6255
+ let rectPL = 5;
6256
+
6257
+ $._rectShaderCode =
6258
+ $._baseShaderCode +
6259
+ /* wgsl */ `
6260
+ struct Rect {
6261
+ center: vec2f,
6262
+ extents: vec2f,
6263
+ roundedRadius: f32,
6264
+ strokeWeight: f32,
6265
+ fillIndex: f32,
6266
+ strokeIndex: f32,
6267
+ matrixIndex: f32,
6268
+ padding0: f32, // can't use vec3f for alignment
6269
+ padding1: vec2f,
6270
+ padding2: vec4f
6271
+ };
6272
+
6273
+ struct VertexParams {
6274
+ @builtin(vertex_index) vertIndex: u32,
6275
+ @builtin(instance_index) instIndex: u32
6276
+ };
6277
+
6278
+ struct FragParams {
6279
+ @builtin(position) position: vec4f,
6280
+ @location(0) local: vec2f,
6281
+ @location(1) extents: vec2f,
6282
+ @location(2) roundedRadius: f32,
6283
+ @location(3) strokeWeight: f32,
6284
+ @location(4) fill: vec4f,
6285
+ @location(5) stroke: vec4f,
6286
+ @location(6) blend: vec4f
6287
+ };
6288
+
6289
+ @group(0) @binding(0) var<uniform> q: Q5;
6290
+ @group(0) @binding(1) var<storage> transforms: array<mat4x4<f32>>;
6291
+ @group(0) @binding(2) var<storage> colors : array<vec4f>;
6292
+
6293
+ @group(1) @binding(0) var<storage, read> rects: array<Rect>;
6294
+
6295
+ const quad = array(
6296
+ vec2f(-1.0, -1.0),
6297
+ vec2f( 1.0, -1.0),
6298
+ vec2f(-1.0, 1.0),
6299
+ vec2f( 1.0, 1.0)
6300
+ );
6301
+ const transparent = vec4f(0.0);
6302
+
6303
+ fn transformVertex(pos: vec2f, matrixIndex: f32) -> vec4f {
6304
+ var vert = vec4f(pos, 0.0, 1.0);
6305
+ vert = transforms[i32(matrixIndex)] * vert;
6306
+ vert.x /= q.halfWidth;
6307
+ vert.y /= q.halfHeight;
6308
+ return vert;
6309
+ }
6310
+
6311
+ @vertex
6312
+ fn vertexMain(v: VertexParams) -> FragParams {
6313
+ let rect = rects[v.instIndex];
6314
+
6315
+ let halfStrokeSize = vec2f(rect.strokeWeight * 0.5);
6316
+ let quadSize = rect.extents + halfStrokeSize;
6317
+ let pos = (quad[v.vertIndex] * quadSize) + rect.center;
6318
+
6319
+ let local = pos - rect.center;
6320
+
6321
+ var f: FragParams;
6322
+ f.position = transformVertex(pos, rect.matrixIndex);
6323
+
6324
+ f.local = local;
6325
+ f.extents = rect.extents;
6326
+ f.roundedRadius = rect.roundedRadius;
6327
+ f.strokeWeight = rect.strokeWeight;
6328
+
6329
+ let fill = colors[i32(rect.fillIndex)];
6330
+ let stroke = colors[i32(rect.strokeIndex)];
6331
+ f.fill = fill;
6332
+ f.stroke = stroke;
6333
+
6334
+ // Source-over blend: stroke over fill (pre-multiplied alpha)
6335
+ if (fill.a != 0.0 && stroke.a != 1.0) {
6336
+ let outAlpha = stroke.a + fill.a * (1.0 - stroke.a);
6337
+ let outColor = stroke.rgb * stroke.a + fill.rgb * fill.a * (1.0 - stroke.a);
6338
+ f.blend = vec4f(outColor / max(outAlpha, 1e-5), outAlpha);
6339
+ }
6340
+ return f;
6341
+ }
6342
+
6343
+ fn sdRoundRect(p: vec2f, extents: vec2f, radius: f32) -> f32 {
6344
+ let q = abs(p) - extents + vec2f(radius);
6345
+ return length(max(q, vec2f(0.0))) - radius + min(max(q.x, q.y), 0.0);
6346
+ }
6347
+
6348
+ @fragment
6349
+ fn fragMain(f: FragParams) -> @location(0) vec4f {
6350
+ let dist = select(
6351
+ max(abs(f.local.x) - f.extents.x, abs(f.local.y) - f.extents.y), // sharp
6352
+ sdRoundRect(f.local, f.extents, f.roundedRadius), // rounded
6353
+ f.roundedRadius > 0.0
6354
+ );
6355
+
6356
+ // fill only
6357
+ if (f.fill.a != 0.0 && f.strokeWeight == 0.0) {
6358
+ if (dist <= 0.0) {
6359
+ return f.fill;
6360
+ }
6361
+ return transparent;
6362
+ }
6363
+
6364
+ let halfStroke = f.strokeWeight * 0.5;
6365
+ let inner = dist + halfStroke;
6366
+
6367
+ if (f.fill.a != 0.0) {
6368
+ if (inner <= 0.0) {
6369
+ return f.fill;
6370
+ }
6371
+ if (dist <= 0.0 && f.stroke.a != 1.0) {
6372
+ return f.blend;
6373
+ }
6374
+ }
6375
+
6376
+ let outer = dist - halfStroke;
6377
+
6378
+ if (outer <= 0.0 && inner >= 0.0) {
6379
+ return f.stroke;
6380
+ }
6381
+
6382
+ return transparent;
6383
+ }
6384
+ `;
6385
+
6386
+ let rectShader = Q5.device.createShaderModule({
6387
+ label: 'rectShader',
6388
+ code: $._rectShaderCode
6389
+ });
6390
+
6391
+ let rectIndices = new Uint16Array([0, 1, 2, 2, 1, 3]);
6392
+
6393
+ let rectIndexBuffer = Q5.device.createBuffer({
6394
+ size: rectIndices.byteLength,
6395
+ usage: GPUBufferUsage.INDEX,
6396
+ mappedAtCreation: true
6397
+ });
6398
+ new Uint16Array(rectIndexBuffer.getMappedRange()).set(rectIndices);
6399
+ rectIndexBuffer.unmap();
6400
+
6401
+ let rectBindGroupLayout = Q5.device.createBindGroupLayout({
6402
+ entries: [
6403
+ {
6404
+ binding: 0,
6405
+ visibility: GPUShaderStage.VERTEX,
6406
+ buffer: { type: 'read-only-storage' }
6407
+ }
6408
+ ]
6409
+ });
6410
+
6411
+ let rectPipelineLayout = Q5.device.createPipelineLayout({
6412
+ label: 'rectPipelineLayout',
6413
+ bindGroupLayouts: [...$._bindGroupLayouts, rectBindGroupLayout]
6414
+ });
6415
+
6416
+ $._pipelineConfigs[5] = {
6417
+ label: 'rectPipeline',
6418
+ layout: rectPipelineLayout,
6419
+ vertex: {
6420
+ module: rectShader,
6421
+ entryPoint: 'vertexMain',
6422
+ buffers: []
6423
+ },
6424
+ fragment: {
6425
+ module: rectShader,
6426
+ entryPoint: 'fragMain',
6427
+ targets: [
6428
+ {
6429
+ format: 'bgra8unorm',
6430
+ blend: $.blendConfigs['source-over']
6431
+ }
6432
+ ]
6433
+ },
6434
+ primitive: { topology: 'triangle-list' },
6435
+ multisample: { count: 4 }
6436
+ };
6437
+
6438
+ $._pipelines[5] = Q5.device.createRenderPipeline($._pipelineConfigs[5]);
6439
+
6440
+ let rectStack = new Float32Array(Q5.MAX_RECTS * 16);
6441
+ let rectStackIdx = 0;
6442
+
6443
+ let rectBuffer = Q5.device.createBuffer({
6444
+ size: rectStack.byteLength,
6445
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
6446
+ });
6447
+
6448
+ let rectBindGroup = Q5.device.createBindGroup({
6449
+ layout: rectBindGroupLayout,
6450
+ entries: [{ binding: 0, resource: { buffer: rectBuffer } }]
6451
+ });
6452
+
6453
+ function addRect(x, y, hw, hh, roundedRadius, strokeW, fillRect) {
6454
+ let s = rectStack,
6455
+ i = rectStackIdx;
6456
+
6457
+ s[i] = x;
6458
+ s[i + 1] = y;
6459
+ s[i + 2] = hw;
6460
+ s[i + 3] = hh;
6461
+ s[i + 4] = roundedRadius;
6462
+ s[i + 5] = strokeW;
6463
+ s[i + 6] = fillRect;
6464
+ s[i + 7] = strokeIdx;
6465
+ s[i + 8] = matrixIdx;
6466
+
6467
+ rectStackIdx += 16;
6468
+ drawStack.push(rectPL, 1);
6469
+ }
6470
+
6471
+ let _rectMode = 'corner';
6472
+
6473
+ $.rectMode = (x) => (_rectMode = x);
6474
+ $._getRectMode = () => _rectMode;
6475
+
6476
+ function applyRectMode(x, y, w, h) {
6477
+ let hw = w / 2,
6478
+ hh = h / 2;
6479
+ if (_rectMode != 'center') {
6480
+ if (_rectMode == 'corner') {
6481
+ x += hw;
6482
+ y += hh;
6483
+ } else if (_rectMode == 'radius') {
6484
+ hw = w;
6485
+ hh = h;
6486
+ } else if (_rectMode == 'corners') {
6487
+ hw = (x - w) / 2;
6488
+ hh = (y - h) / 2;
6489
+ x += hw;
6490
+ y += hh;
6491
+ }
6492
+ }
6493
+ return [x, y, hw, hh];
6494
+ }
6495
+
6496
+ $.rect = (x, y, w, h, rr = 0) => {
6497
+ if (matrixDirty) saveMatrix();
6498
+
6499
+ let hw, hh;
6500
+ [x, y, hw, hh] = applyRectMode(x, y, w, h);
6501
+
6502
+ addRect(x, y, hw, hh, rr, doStroke ? sw : 0, doFill ? fillIdx : 0);
6503
+ };
6504
+
6505
+ $.square = (x, y, s) => $.rect(x, y, s, s);
6506
+
6507
+ function addCapsule(x1, y1, x2, y2, r, strokeW, fillCapsule) {
6508
+ let dx = x2 - x1,
6509
+ dy = y2 - y1,
6510
+ len = Math.hypot(dx, dy);
6511
+
6512
+ if (len === 0) return;
6513
+
6514
+ let angle = Math.atan2(dy, dx),
6515
+ cx = (x1 + x2) / 2,
6516
+ cy = (y1 + y2) / 2;
6517
+
6518
+ if ($._angleMode) angle *= $._RADTODEG;
6519
+
6520
+ $.pushMatrix();
6521
+ $.translate(cx, cy);
6522
+ $.rotate(angle);
6523
+
6524
+ if (matrixDirty) saveMatrix();
6525
+
6526
+ addRect(0, 0, len / 2 + r, r, r, strokeW, fillCapsule);
6527
+
6528
+ $.popMatrix();
6529
+ }
6530
+
6531
+ $.capsule = (x1, y1, x2, y2, r) => {
6532
+ addCapsule(x1, y1, x2, y2, r, doStroke ? sw : 0, doFill ? fillIdx : 0);
6533
+ };
6534
+
6535
+ $.line = (x1, y1, x2, y2) => {
6536
+ if (doStroke) addCapsule(x1, y1, x2, y2, qsw, hsw, 0);
6537
+ };
6538
+
6539
+ /* ELLIPSE */
6540
+
6541
+ let ellipsePL = 6;
6542
+
6543
+ $._ellipseShaderCode =
6544
+ $._baseShaderCode +
6545
+ /* wgsl */ `
6546
+ struct Ellipse {
6547
+ center: vec2f,
6548
+ size: vec2f,
6549
+ startAngle: f32,
6550
+ endAngle: f32,
6551
+ strokeWeight: f32,
6552
+ fillIndex: f32,
6553
+ strokeIndex: f32,
6554
+ matrixIndex: f32,
6555
+ padding0: vec2f,
6556
+ padding1: vec4f
6557
+ };
6558
+
6559
+ struct VertexParams {
6560
+ @builtin(vertex_index) vertIndex: u32,
6561
+ @builtin(instance_index) instIndex: u32
6562
+ };
6563
+
6564
+ struct FragParams {
6565
+ @builtin(position) position: vec4f,
6566
+ @location(0) outerEdge: vec2f,
6567
+ @location(1) fillEdge: vec2f,
6568
+ @location(2) innerEdge: vec2f,
6569
+ @location(3) strokeWeight: f32,
6570
+ @location(4) fill: vec4f,
6571
+ @location(5) stroke: vec4f,
6572
+ @location(6) blend: vec4f
6573
+ };
6574
+
6575
+ @group(0) @binding(0) var<uniform> q: Q5;
6576
+ @group(0) @binding(1) var<storage> transforms: array<mat4x4<f32>>;
6577
+ @group(0) @binding(2) var<storage> colors : array<vec4f>;
6578
+
6579
+ @group(1) @binding(0) var<storage, read> ellipses: array<Ellipse>;
6580
+
6581
+ const PI = 3.141592653589793;
6582
+ const segments = 6.0;
6583
+ const expansion = 1.0 / cos(PI / segments);
6584
+ const antiAlias = 0.015625; // 1/64
6585
+ const transparent = vec4f(0.0);
6586
+
6587
+ fn transformVertex(pos: vec2f, matrixIndex: f32) -> vec4f {
6588
+ var vert = vec4f(pos, 0.0, 1.0);
6589
+ vert = transforms[i32(matrixIndex)] * vert;
6590
+ vert.x /= q.halfWidth;
6591
+ vert.y /= q.halfHeight;
6592
+ return vert;
6593
+ }
6594
+
6595
+ @vertex
6596
+ fn vertexMain(v: VertexParams) -> FragParams {
6597
+ let ellipse = ellipses[v.instIndex];
6598
+ var pos = ellipse.center;
6599
+ var local = vec2f(0.0);
6600
+ let start = ellipse.startAngle;
6601
+ let end = ellipse.endAngle;
6602
+ let arc = end - start;
6603
+ let halfStrokeSize = vec2f(ellipse.strokeWeight * 0.5);
6604
+
6605
+ let fanSize = (ellipse.size + halfStrokeSize) * expansion;
6606
+
6607
+ if (v.vertIndex != 0) {
6608
+ let theta = start + (f32(v.vertIndex - 1) / segments) * arc;
6609
+ local = vec2f(cos(theta), sin(theta));
6610
+ pos = ellipse.center + local * fanSize;
6611
+ }
6612
+
6613
+ let dist = pos - ellipse.center;
6614
+
6615
+ var f: FragParams;
6616
+ f.position = transformVertex(pos, ellipse.matrixIndex);
6617
+ f.outerEdge = dist / (ellipse.size + halfStrokeSize);
6618
+ f.fillEdge = dist / ellipse.size;
6619
+ f.innerEdge = dist / (ellipse.size - halfStrokeSize);
6620
+ f.strokeWeight = ellipse.strokeWeight;
6621
+
6622
+ let fill = colors[i32(ellipse.fillIndex)];
6623
+ let stroke = colors[i32(ellipse.strokeIndex)];
6624
+ f.fill = fill;
6625
+ f.stroke = stroke;
6626
+
6627
+ // Source-over blend: stroke over fill (pre-multiplied alpha)
6628
+ if (fill.a != 0.0 && stroke.a != 1.0) {
6629
+ let outAlpha = stroke.a + fill.a * (1.0 - stroke.a);
6630
+ let outColor = stroke.rgb * stroke.a + fill.rgb * fill.a * (1.0 - stroke.a);
6631
+ f.blend = vec4f(outColor / max(outAlpha, 1e-5), outAlpha);
6632
+ }
6633
+ return f;
6634
+ }
6635
+
6636
+ @fragment
6637
+ fn fragMain(f: FragParams) -> @location(0) vec4f {
6638
+ let fillEdge = length(f.fillEdge);
6639
+
6640
+ // disable AA for very thin strokes
6641
+ let aa = select(antiAlias, 0.0, f.strokeWeight <= 1.0);
6642
+
6643
+ if (f.fill.a != 0.0 && f.strokeWeight == 0.0) {
6644
+ if (fillEdge > 1.0) {
6645
+ return transparent;
6646
+ }
6647
+ let fillAlpha = 1.0 - smoothstep(1.0 - aa, 1.0, fillEdge);
6648
+ return vec4f(f.fill.rgb, f.fill.a * fillAlpha);
6649
+ }
6650
+
6651
+ let innerEdge = length(f.innerEdge);
6652
+
6653
+ if (f.fill.a != 0.0 && fillEdge < 1.0) {
6654
+ if (innerEdge < 1.0) {
6655
+ return f.fill;
6656
+ }
6657
+ let tInner = smoothstep(1.0, 1.0 + aa, innerEdge);
6658
+ let tOuter = smoothstep(1.0 - aa, 1.0, fillEdge);
6659
+ if (f.stroke.a != 1.0) {
6660
+ let fillBlend = mix(f.fill, f.blend, tInner);
6661
+ return mix(fillBlend, f.stroke, tOuter);
6662
+ } else {
6663
+ let fillBlend = mix(f.fill, f.stroke, tInner);
6664
+ return mix(fillBlend, f.stroke, tOuter);
6665
+ }
6666
+ }
6667
+
6668
+ if (innerEdge < 1.0) {
6669
+ return transparent;
6670
+ }
6671
+
6672
+ let outerEdge = length(f.outerEdge);
6673
+ let outerAlpha = 1.0 - smoothstep(1.0 - aa, 1.0, outerEdge);
6674
+ let innerAlpha = smoothstep(1.0, 1.0 + aa, innerEdge);
6675
+ let strokeAlpha = innerAlpha * outerAlpha;
6676
+ return vec4f(f.stroke.rgb, f.stroke.a * strokeAlpha);
6677
+ }
6678
+ `;
6679
+
6680
+ let ellipseShader = Q5.device.createShaderModule({
6681
+ label: 'ellipseShader',
6682
+ code: $._ellipseShaderCode
6683
+ });
6684
+
6685
+ let fanIndices = new Uint16Array([0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 5, 6, 0, 6, 7]);
6686
+
6687
+ let ellipseIndexBuffer = Q5.device.createBuffer({
6688
+ size: fanIndices.byteLength,
6689
+ usage: GPUBufferUsage.INDEX,
6690
+ mappedAtCreation: true
6691
+ });
6692
+ new Uint16Array(ellipseIndexBuffer.getMappedRange()).set(fanIndices);
6693
+ ellipseIndexBuffer.unmap();
6694
+
6695
+ let ellipseBindGroupLayout = Q5.device.createBindGroupLayout({
6696
+ entries: [
6697
+ {
6698
+ binding: 0,
6699
+ visibility: GPUShaderStage.VERTEX,
6700
+ buffer: { type: 'read-only-storage' }
6701
+ }
6702
+ ]
6703
+ });
6704
+
6705
+ let ellipsePipelineLayout = Q5.device.createPipelineLayout({
6706
+ label: 'ellipsePipelineLayout',
6707
+ bindGroupLayouts: [...$._bindGroupLayouts, ellipseBindGroupLayout]
6708
+ });
6709
+
6710
+ $._pipelineConfigs[6] = {
6711
+ label: 'ellipsePipeline',
6712
+ layout: ellipsePipelineLayout,
6713
+ vertex: {
6714
+ module: ellipseShader,
6715
+ entryPoint: 'vertexMain',
6716
+ buffers: []
6717
+ },
6718
+ fragment: {
6719
+ module: ellipseShader,
6720
+ entryPoint: 'fragMain',
6721
+ targets: [
6722
+ {
6723
+ format: 'bgra8unorm',
6724
+ blend: $.blendConfigs['source-over']
6725
+ }
6726
+ ]
6727
+ },
6728
+ primitive: { topology: 'triangle-list' },
6729
+ multisample: { count: 4 }
6730
+ };
6731
+
6732
+ $._pipelines[6] = Q5.device.createRenderPipeline($._pipelineConfigs[6]);
6733
+
6734
+ let ellipseStack = new Float32Array(Q5.MAX_ELLIPSES * 16);
6735
+ let ellipseStackIdx = 0;
6736
+
6737
+ let ellipseBuffer = Q5.device.createBuffer({
6738
+ size: ellipseStack.byteLength,
6739
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
6740
+ });
6741
+
6742
+ let ellipseBindGroup = Q5.device.createBindGroup({
6743
+ layout: ellipseBindGroupLayout,
6744
+ entries: [{ binding: 0, resource: { buffer: ellipseBuffer } }]
6745
+ });
6746
+
6747
+ function addEllipse(x, y, a, b, start, stop, strokeW, fillEllipse) {
6748
+ let s = ellipseStack,
6749
+ i = ellipseStackIdx;
6750
+
6751
+ s[i] = x;
6752
+ s[i + 1] = y;
6753
+ s[i + 2] = a;
6754
+ s[i + 3] = b;
6755
+ s[i + 4] = start;
6756
+ s[i + 5] = stop;
6757
+ s[i + 6] = strokeW;
6758
+ s[i + 7] = fillEllipse ? fillIdx : 0;
6759
+ s[i + 8] = strokeIdx;
6760
+ s[i + 9] = matrixIdx;
6761
+
6762
+ ellipseStackIdx += 16;
6763
+ drawStack.push(ellipsePL, 1);
6764
+ }
6765
+
6766
+ let _ellipseMode = 'center';
6767
+
6768
+ $.ellipseMode = (x) => (_ellipseMode = x);
6769
+ $._getEllipseMode = () => _ellipseMode;
6770
+
6771
+ function applyEllipseMode(x, y, w, h) {
6772
+ h ??= w;
6773
+ let a, b;
6774
+ if (_ellipseMode == 'center') {
6775
+ a = w / 2;
6776
+ b = h / 2;
6777
+ } else if (_ellipseMode == 'radius') {
6778
+ a = w;
6779
+ b = h;
6780
+ } else if (_ellipseMode == 'corner') {
6781
+ x += w / 2;
6782
+ y += h / 2;
6783
+ a = w / 2;
6784
+ b = h / 2;
6785
+ } else if (_ellipseMode == 'corners') {
6786
+ x = (x + w) / 2;
6787
+ y = (y + h) / 2;
6788
+ a = (w - x) / 2;
6789
+ b = (h - y) / 2;
6790
+ }
6791
+ return [x, y, a, b];
6792
+ }
6793
+
6794
+ $.ellipse = (x, y, w, h) => {
6795
+ let a, b;
6796
+ [x, y, a, b] = applyEllipseMode(x, y, w, h);
6797
+
6798
+ if (matrixDirty) saveMatrix();
6799
+
6800
+ addEllipse(x, y, a, b, 0, TAU, sw, doFill);
6801
+ };
6802
+
6803
+ $.circle = (x, y, d) => $.ellipse(x, y, d, d);
6804
+
6805
+ $.arc = (x, y, w, h, start, stop) => {
6806
+ if (start === stop) return $.ellipse(x, y, w, h);
6807
+
6808
+ // Convert angles if needed
6809
+ if ($._angleMode) {
6810
+ start = $.radians(start);
6811
+ stop = $.radians(stop);
6812
+ }
6813
+
6814
+ // Normalize angles
6815
+ start %= TAU;
6816
+ stop %= TAU;
6817
+ if (start < 0) start += TAU;
6818
+ if (stop < 0) stop += TAU;
6819
+ if (start > stop) stop += TAU;
6820
+ if (start == stop) return $.ellipse(x, y, w, h);
6821
+
6822
+ // Calculate position based on ellipseMode
6823
+ let a, b;
6824
+ [x, y, a, b] = applyEllipseMode(x, y, w, h);
6825
+
6826
+ if (matrixDirty) saveMatrix();
6827
+
6828
+ addEllipse(x, y, a, b, start, stop, sw, doFill);
6829
+ };
6830
+
6831
+ $.point = (x, y) => {
6832
+ if (matrixDirty) saveMatrix();
6833
+
6834
+ if (scaledHSW < 1) {
6835
+ addRect(x, y, hsw, hsw, 0, sw, 0);
6836
+ } else {
6837
+ // dimensions of the point needs to be set to half the stroke weight
6838
+ addEllipse(x, y, hsw, hsw, 0, TAU, sw, 0);
6839
+ }
6840
+ };
6841
+
6543
6842
  /* IMAGE */
6544
6843
 
6545
6844
  let imagePL = 2,
@@ -6882,6 +7181,7 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
6882
7181
  let _imageMode = 'corner';
6883
7182
 
6884
7183
  $.imageMode = (x) => (_imageMode = x);
7184
+ $._getImageMode = () => _imageMode;
6885
7185
 
6886
7186
  const addImgVert = (x, y, u, v, ci, ti, ia) => {
6887
7187
  let s = imgVertStack,
@@ -7675,6 +7975,9 @@ Q5.DILATE = 6;
7675
7975
  Q5.ERODE = 7;
7676
7976
  Q5.BLUR = 8;
7677
7977
 
7978
+ Q5.MAX_RECTS = 200200;
7979
+ Q5.MAX_ELLIPSES = 200200;
7980
+
7678
7981
  Q5.initWebGPU = async () => {
7679
7982
  if (!navigator.gpu) {
7680
7983
  console.warn('q5 WebGPU not supported on this browser! Use Google Chrome or Edge.');