q5 3.0.5 → 3.1.0
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.
- package/deno.json +1 -1
- package/package.json +1 -1
- package/q5.d.ts +48 -2
- package/q5.js +712 -382
- package/q5.min.js +2 -2
package/q5.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* q5.js
|
|
3
|
-
* @version 3.
|
|
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.
|
|
388
|
+
Q5.version = Q5.VERSION = '3.1';
|
|
389
389
|
|
|
390
390
|
if (typeof document == 'object') {
|
|
391
391
|
document.addEventListener('DOMContentLoaded', () => {
|
|
@@ -815,6 +815,12 @@ Q5.renderers.c2d.canvas = ($, q) => {
|
|
|
815
815
|
$.noStroke = () => ($._doStroke = false);
|
|
816
816
|
$.opacity = (a) => ($.ctx.globalAlpha = a);
|
|
817
817
|
|
|
818
|
+
// polyfill for q5 WebGPU functions (used by q5play)
|
|
819
|
+
$._getFillIdx = () => $._fill;
|
|
820
|
+
$._setFillIdx = (v) => ($._fill = v);
|
|
821
|
+
$._getStrokeIdx = () => $._stroke;
|
|
822
|
+
$._setStrokeIdx = (v) => ($._stroke = v);
|
|
823
|
+
|
|
818
824
|
$._doShadow = false;
|
|
819
825
|
$._shadowOffsetX = $._shadowOffsetY = $._shadowBlur = 10;
|
|
820
826
|
|
|
@@ -1098,6 +1104,27 @@ Q5.renderers.c2d.shapes = ($) => {
|
|
|
1098
1104
|
return $.rect(x, y, s, s, tl, tr, br, bl);
|
|
1099
1105
|
};
|
|
1100
1106
|
|
|
1107
|
+
$.capsule = (x1, y1, x2, y2, r) => {
|
|
1108
|
+
const dx = x2 - x1,
|
|
1109
|
+
dy = y2 - y1,
|
|
1110
|
+
len = Math.hypot(dx, dy);
|
|
1111
|
+
|
|
1112
|
+
if (len === 0) return $.circle(x1, y1, r * 2);
|
|
1113
|
+
|
|
1114
|
+
const angle = Math.atan2(dy, dx),
|
|
1115
|
+
px = (-dy / len) * r,
|
|
1116
|
+
py = (dx / len) * r;
|
|
1117
|
+
|
|
1118
|
+
$.ctx.beginPath();
|
|
1119
|
+
$.ctx.moveTo(x1 - px, y1 - py);
|
|
1120
|
+
$.ctx.arc(x1, y1, r, angle - $.HALF_PI, angle + $.HALF_PI, true);
|
|
1121
|
+
$.ctx.lineTo(x2 + px, y2 + py);
|
|
1122
|
+
$.ctx.arc(x2, y2, r, angle + $.HALF_PI, angle - $.HALF_PI, true);
|
|
1123
|
+
$.ctx.closePath();
|
|
1124
|
+
|
|
1125
|
+
ink();
|
|
1126
|
+
};
|
|
1127
|
+
|
|
1101
1128
|
$.beginShape = () => {
|
|
1102
1129
|
curveBuff = [];
|
|
1103
1130
|
$.ctx.beginPath();
|
|
@@ -4899,8 +4926,8 @@ struct Q5 {
|
|
|
4899
4926
|
frameLayout,
|
|
4900
4927
|
frameSampler,
|
|
4901
4928
|
frameBindGroup,
|
|
4902
|
-
colorIndex =
|
|
4903
|
-
colorStackIndex =
|
|
4929
|
+
colorIndex = 2,
|
|
4930
|
+
colorStackIndex = 12,
|
|
4904
4931
|
prevFramePL = 0,
|
|
4905
4932
|
framePL = 0;
|
|
4906
4933
|
|
|
@@ -4918,6 +4945,7 @@ struct Q5 {
|
|
|
4918
4945
|
|
|
4919
4946
|
// prettier-ignore
|
|
4920
4947
|
colorStack.set([
|
|
4948
|
+
0, 0, 0, 0, // transparent
|
|
4921
4949
|
0, 0, 0, 1, // black
|
|
4922
4950
|
1, 1, 1, 1 // white
|
|
4923
4951
|
]);
|
|
@@ -5139,13 +5167,14 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
|
|
|
5139
5167
|
doStroke = true,
|
|
5140
5168
|
fillSet = false,
|
|
5141
5169
|
strokeSet = false,
|
|
5142
|
-
strokeIdx =
|
|
5143
|
-
fillIdx =
|
|
5144
|
-
tintIdx =
|
|
5170
|
+
strokeIdx = 1,
|
|
5171
|
+
fillIdx = 2,
|
|
5172
|
+
tintIdx = 2,
|
|
5145
5173
|
globalAlpha = 1,
|
|
5146
5174
|
sw = 1, // stroke weight
|
|
5147
|
-
hsw = 0.5, // half the stroke weight
|
|
5148
|
-
|
|
5175
|
+
hsw = 0.5, // half of the stroke weight
|
|
5176
|
+
qsw = 0.25, // quarter of the stroke weight
|
|
5177
|
+
scaledHSW = 0.5;
|
|
5149
5178
|
|
|
5150
5179
|
$.fill = (r, g, b, a) => {
|
|
5151
5180
|
addColor(r, g, b, a);
|
|
@@ -5168,10 +5197,15 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
|
|
|
5168
5197
|
|
|
5169
5198
|
$.strokeWeight = (v) => {
|
|
5170
5199
|
if (v === undefined) return sw;
|
|
5200
|
+
if (!v) {
|
|
5201
|
+
doStroke = false;
|
|
5202
|
+
return;
|
|
5203
|
+
}
|
|
5171
5204
|
v = Math.abs(v);
|
|
5172
5205
|
sw = v;
|
|
5173
|
-
scaledSW = v * _scale;
|
|
5174
5206
|
hsw = v / 2;
|
|
5207
|
+
qsw = v / 4;
|
|
5208
|
+
scaledHSW = hsw * _scale;
|
|
5175
5209
|
};
|
|
5176
5210
|
|
|
5177
5211
|
$._getFillIdx = () => fillIdx;
|
|
@@ -5254,7 +5288,7 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
|
|
|
5254
5288
|
y ??= x;
|
|
5255
5289
|
|
|
5256
5290
|
_scale = Math.max(Math.abs(x), Math.abs(y));
|
|
5257
|
-
|
|
5291
|
+
scaledHSW = sw * 0.5 * _scale;
|
|
5258
5292
|
|
|
5259
5293
|
let m = matrix;
|
|
5260
5294
|
|
|
@@ -5358,7 +5392,7 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
|
|
|
5358
5392
|
strokeIdx,
|
|
5359
5393
|
sw,
|
|
5360
5394
|
hsw,
|
|
5361
|
-
|
|
5395
|
+
scaledHSW,
|
|
5362
5396
|
doFill,
|
|
5363
5397
|
doStroke,
|
|
5364
5398
|
fillSet,
|
|
@@ -5387,7 +5421,7 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
|
|
|
5387
5421
|
strokeIdx,
|
|
5388
5422
|
sw,
|
|
5389
5423
|
hsw,
|
|
5390
|
-
|
|
5424
|
+
scaledHSW,
|
|
5391
5425
|
doFill,
|
|
5392
5426
|
doStroke,
|
|
5393
5427
|
fillSet,
|
|
@@ -5529,7 +5563,11 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
|
|
|
5529
5563
|
$.pop();
|
|
5530
5564
|
} else {
|
|
5531
5565
|
addColor(r, g, b, a);
|
|
5532
|
-
|
|
5566
|
+
let lx = -c.hw,
|
|
5567
|
+
rx = c.hw,
|
|
5568
|
+
ty = -c.hh,
|
|
5569
|
+
by = c.hh;
|
|
5570
|
+
addQuad(lx, ty, rx, ty, rx, by, lx, by, colorIndex, 0);
|
|
5533
5571
|
}
|
|
5534
5572
|
};
|
|
5535
5573
|
|
|
@@ -5664,6 +5702,8 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
|
|
|
5664
5702
|
}
|
|
5665
5703
|
}
|
|
5666
5704
|
|
|
5705
|
+
// prepare to render text
|
|
5706
|
+
|
|
5667
5707
|
if (charStack.length) {
|
|
5668
5708
|
// calculate total buffer size for text data
|
|
5669
5709
|
let totalTextSize = 0;
|
|
@@ -5710,9 +5750,33 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
|
|
|
5710
5750
|
});
|
|
5711
5751
|
}
|
|
5712
5752
|
|
|
5753
|
+
// prepare to render rectangles
|
|
5754
|
+
|
|
5755
|
+
// prettier-ignore
|
|
5756
|
+
Q5.device.queue.writeBuffer(
|
|
5757
|
+
rectBuffer,
|
|
5758
|
+
0,
|
|
5759
|
+
rectStack.buffer,
|
|
5760
|
+
rectStack.byteOffset,
|
|
5761
|
+
rectStackIdx * 4
|
|
5762
|
+
);
|
|
5763
|
+
|
|
5764
|
+
// prepare to render ellipses
|
|
5765
|
+
|
|
5766
|
+
// prettier-ignore
|
|
5767
|
+
Q5.device.queue.writeBuffer(
|
|
5768
|
+
ellipseBuffer,
|
|
5769
|
+
0,
|
|
5770
|
+
ellipseStack.buffer,
|
|
5771
|
+
ellipseStack.byteOffset,
|
|
5772
|
+
ellipseStackIdx * 4
|
|
5773
|
+
);
|
|
5774
|
+
|
|
5713
5775
|
let drawVertOffset = 0,
|
|
5714
5776
|
imageVertOffset = 0,
|
|
5715
5777
|
textCharOffset = 0,
|
|
5778
|
+
rectIdx = 0,
|
|
5779
|
+
ellipseIdx = 0,
|
|
5716
5780
|
curPipelineIndex = -1;
|
|
5717
5781
|
|
|
5718
5782
|
for (let i = 0; i < drawStack.length; i += 2) {
|
|
@@ -5731,9 +5795,25 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
|
|
|
5731
5795
|
|
|
5732
5796
|
curPipelineIndex = drawStack[i];
|
|
5733
5797
|
pass.setPipeline($._pipelines[curPipelineIndex]);
|
|
5798
|
+
|
|
5799
|
+
if (curPipelineIndex == 5) {
|
|
5800
|
+
pass.setIndexBuffer(rectIndexBuffer, 'uint16');
|
|
5801
|
+
pass.setBindGroup(1, rectBindGroup);
|
|
5802
|
+
} else if (curPipelineIndex == 6) {
|
|
5803
|
+
pass.setIndexBuffer(ellipseIndexBuffer, 'uint16');
|
|
5804
|
+
pass.setBindGroup(1, ellipseBindGroup);
|
|
5805
|
+
}
|
|
5734
5806
|
}
|
|
5735
5807
|
|
|
5736
|
-
if (curPipelineIndex ==
|
|
5808
|
+
if (curPipelineIndex == 6) {
|
|
5809
|
+
// draw an ellipse
|
|
5810
|
+
pass.drawIndexed(18, v, 0, 0, ellipseIdx);
|
|
5811
|
+
ellipseIdx += v;
|
|
5812
|
+
} else if (curPipelineIndex == 5) {
|
|
5813
|
+
// draw a rectangle
|
|
5814
|
+
pass.drawIndexed(6, v, 0, 0, rectIdx);
|
|
5815
|
+
rectIdx += v;
|
|
5816
|
+
} else if (curPipelineIndex == 4 || curPipelineIndex >= 4000) {
|
|
5737
5817
|
// draw text
|
|
5738
5818
|
let o = drawStack[i + 2];
|
|
5739
5819
|
pass.setBindGroup(1, fontsArr[o].bindGroup);
|
|
@@ -5795,8 +5875,8 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
|
|
|
5795
5875
|
|
|
5796
5876
|
// clear the stacks for the next frame
|
|
5797
5877
|
drawStack.splice(0, drawStack.length);
|
|
5798
|
-
colorIndex =
|
|
5799
|
-
colorStackIndex =
|
|
5878
|
+
colorIndex = 2;
|
|
5879
|
+
colorStackIndex = 12;
|
|
5800
5880
|
matrices = [matrices[0]];
|
|
5801
5881
|
matricesIdxStack = [];
|
|
5802
5882
|
|
|
@@ -5810,6 +5890,10 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
|
|
|
5810
5890
|
vidFrames = 0;
|
|
5811
5891
|
charStack = [];
|
|
5812
5892
|
textStack = [];
|
|
5893
|
+
rectStack = new Float32Array(Q5.MAX_RECTS * 16);
|
|
5894
|
+
rectStackIdx = 0;
|
|
5895
|
+
ellipseStack = new Float32Array(Q5.MAX_ELLIPSES * 16);
|
|
5896
|
+
ellipseStackIdx = 0;
|
|
5813
5897
|
|
|
5814
5898
|
// destroy buffers
|
|
5815
5899
|
Q5.device.queue.onSubmittedWorkDone().then(() => {
|
|
@@ -5871,8 +5955,8 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
|
|
|
5871
5955
|
|
|
5872
5956
|
let shapesVertStack = new Float32Array($._isGraphics ? 1000 : 1e7),
|
|
5873
5957
|
shapesVertIdx = 0;
|
|
5874
|
-
const TAU = Math.PI * 2
|
|
5875
|
-
|
|
5958
|
+
const TAU = Math.PI * 2,
|
|
5959
|
+
HALF_PI = Math.PI / 2;
|
|
5876
5960
|
|
|
5877
5961
|
let shapesVertBuffLayout = {
|
|
5878
5962
|
arrayStride: 16, // 4 floats * 4 bytes
|
|
@@ -5883,14 +5967,14 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
|
|
|
5883
5967
|
]
|
|
5884
5968
|
};
|
|
5885
5969
|
|
|
5886
|
-
let
|
|
5970
|
+
let shapesPipelineLayout = Q5.device.createPipelineLayout({
|
|
5887
5971
|
label: 'shapesPipelineLayout',
|
|
5888
5972
|
bindGroupLayouts: $._bindGroupLayouts
|
|
5889
5973
|
});
|
|
5890
5974
|
|
|
5891
5975
|
$._pipelineConfigs[1] = {
|
|
5892
5976
|
label: 'shapesPipeline',
|
|
5893
|
-
layout:
|
|
5977
|
+
layout: shapesPipelineLayout,
|
|
5894
5978
|
vertex: {
|
|
5895
5979
|
module: shapesShader,
|
|
5896
5980
|
entryPoint: 'vertexMain',
|
|
@@ -5917,337 +6001,6 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
|
|
|
5917
6001
|
shapesVertIdx = i;
|
|
5918
6002
|
};
|
|
5919
6003
|
|
|
5920
|
-
const addRect = (x1, y1, x2, y2, x3, y3, x4, y4, ci, ti) => {
|
|
5921
|
-
let v = shapesVertStack,
|
|
5922
|
-
i = shapesVertIdx;
|
|
5923
|
-
|
|
5924
|
-
v[i++] = x1;
|
|
5925
|
-
v[i++] = y1;
|
|
5926
|
-
v[i++] = ci;
|
|
5927
|
-
v[i++] = ti;
|
|
5928
|
-
|
|
5929
|
-
v[i++] = x2;
|
|
5930
|
-
v[i++] = y2;
|
|
5931
|
-
v[i++] = ci;
|
|
5932
|
-
v[i++] = ti;
|
|
5933
|
-
|
|
5934
|
-
v[i++] = x4;
|
|
5935
|
-
v[i++] = y4;
|
|
5936
|
-
v[i++] = ci;
|
|
5937
|
-
v[i++] = ti;
|
|
5938
|
-
|
|
5939
|
-
v[i++] = x3;
|
|
5940
|
-
v[i++] = y3;
|
|
5941
|
-
v[i++] = ci;
|
|
5942
|
-
v[i++] = ti;
|
|
5943
|
-
|
|
5944
|
-
shapesVertIdx = i;
|
|
5945
|
-
drawStack.push(shapesPL, 4);
|
|
5946
|
-
};
|
|
5947
|
-
|
|
5948
|
-
const addArc = (x, y, a, b, startAngle, endAngle, n, ci, ti) => {
|
|
5949
|
-
let angleRange = endAngle - startAngle;
|
|
5950
|
-
let angleIncrement = angleRange / n;
|
|
5951
|
-
let t = startAngle;
|
|
5952
|
-
|
|
5953
|
-
let v = shapesVertStack,
|
|
5954
|
-
i = shapesVertIdx;
|
|
5955
|
-
|
|
5956
|
-
for (let j = 0; j <= n; j++) {
|
|
5957
|
-
// add center vertex
|
|
5958
|
-
v[i++] = x;
|
|
5959
|
-
v[i++] = y;
|
|
5960
|
-
v[i++] = ci;
|
|
5961
|
-
v[i++] = ti;
|
|
5962
|
-
|
|
5963
|
-
// calculate perimeter vertex
|
|
5964
|
-
let vx = x + a * Math.cos(t);
|
|
5965
|
-
let vy = y + b * Math.sin(t);
|
|
5966
|
-
|
|
5967
|
-
// add perimeter vertex
|
|
5968
|
-
v[i++] = vx;
|
|
5969
|
-
v[i++] = vy;
|
|
5970
|
-
v[i++] = ci;
|
|
5971
|
-
v[i++] = ti;
|
|
5972
|
-
|
|
5973
|
-
t += angleIncrement;
|
|
5974
|
-
}
|
|
5975
|
-
|
|
5976
|
-
shapesVertIdx = i;
|
|
5977
|
-
drawStack.push(shapesPL, (n + 1) * 2);
|
|
5978
|
-
};
|
|
5979
|
-
|
|
5980
|
-
const addArcStroke = (x, y, outerA, outerB, innerA, innerB, 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
|
-
// Outer vertex
|
|
5990
|
-
let vxOuter = x + outerA * Math.cos(t);
|
|
5991
|
-
let vyOuter = y + outerB * Math.sin(t);
|
|
5992
|
-
|
|
5993
|
-
// Inner vertex
|
|
5994
|
-
let vxInner = x + innerA * Math.cos(t);
|
|
5995
|
-
let vyInner = y + innerB * Math.sin(t);
|
|
5996
|
-
|
|
5997
|
-
// Add vertices for triangle strip
|
|
5998
|
-
v[i++] = vxOuter;
|
|
5999
|
-
v[i++] = vyOuter;
|
|
6000
|
-
v[i++] = ci;
|
|
6001
|
-
v[i++] = ti;
|
|
6002
|
-
|
|
6003
|
-
v[i++] = vxInner;
|
|
6004
|
-
v[i++] = vyInner;
|
|
6005
|
-
v[i++] = ci;
|
|
6006
|
-
v[i++] = ti;
|
|
6007
|
-
|
|
6008
|
-
t += angleIncrement;
|
|
6009
|
-
}
|
|
6010
|
-
|
|
6011
|
-
shapesVertIdx = i;
|
|
6012
|
-
drawStack.push(shapesPL, (n + 1) * 2);
|
|
6013
|
-
};
|
|
6014
|
-
|
|
6015
|
-
let _rectMode = 'corner';
|
|
6016
|
-
|
|
6017
|
-
$.rectMode = (x) => (_rectMode = x);
|
|
6018
|
-
|
|
6019
|
-
$.rect = (x, y, w, h, rr = 0) => {
|
|
6020
|
-
h ??= w;
|
|
6021
|
-
let [l, r, t, b] = calcBox(x, y, w, h, _rectMode);
|
|
6022
|
-
let ci, ti;
|
|
6023
|
-
if (matrixDirty) saveMatrix();
|
|
6024
|
-
ti = matrixIdx;
|
|
6025
|
-
|
|
6026
|
-
if (!rr) {
|
|
6027
|
-
if (doFill) {
|
|
6028
|
-
ci = fillIdx;
|
|
6029
|
-
addRect(l, t, r, t, r, b, l, b, ci, ti);
|
|
6030
|
-
}
|
|
6031
|
-
|
|
6032
|
-
if (doStroke) {
|
|
6033
|
-
ci = strokeIdx;
|
|
6034
|
-
|
|
6035
|
-
// Calculate stroke positions
|
|
6036
|
-
let lsw = l - hsw,
|
|
6037
|
-
rsw = r + hsw,
|
|
6038
|
-
tsw = t + hsw,
|
|
6039
|
-
bsw = b - hsw,
|
|
6040
|
-
lpsw = l + hsw,
|
|
6041
|
-
rpsw = r - hsw,
|
|
6042
|
-
tpsw = t - hsw,
|
|
6043
|
-
bpsw = b + hsw;
|
|
6044
|
-
|
|
6045
|
-
addRect(lsw, tpsw, rsw, tpsw, rsw, tsw, lsw, tsw, ci, ti); // Top
|
|
6046
|
-
addRect(lsw, bsw, rsw, bsw, rsw, bpsw, lsw, bpsw, ci, ti); // Bottom
|
|
6047
|
-
|
|
6048
|
-
// Adjust side strokes to avoid overlapping corners
|
|
6049
|
-
tsw = t - hsw;
|
|
6050
|
-
bsw = b + hsw;
|
|
6051
|
-
|
|
6052
|
-
addRect(lsw, tsw, lpsw, tsw, lpsw, bsw, lsw, bsw, ci, ti); // Left
|
|
6053
|
-
addRect(rpsw, tsw, rsw, tsw, rsw, bsw, rpsw, bsw, ci, ti); // Right
|
|
6054
|
-
}
|
|
6055
|
-
return;
|
|
6056
|
-
}
|
|
6057
|
-
|
|
6058
|
-
l += rr;
|
|
6059
|
-
r -= rr;
|
|
6060
|
-
t += rr;
|
|
6061
|
-
b -= rr;
|
|
6062
|
-
|
|
6063
|
-
// Clamp radius
|
|
6064
|
-
rr = Math.min(rr, Math.min(w, h) / 2);
|
|
6065
|
-
|
|
6066
|
-
let n = getArcSegments(rr * _scale);
|
|
6067
|
-
|
|
6068
|
-
let trr = t - rr,
|
|
6069
|
-
brr = b + rr,
|
|
6070
|
-
lrr = l - rr,
|
|
6071
|
-
rrr = r + rr;
|
|
6072
|
-
|
|
6073
|
-
if (doFill) {
|
|
6074
|
-
ci = fillIdx;
|
|
6075
|
-
// Corner arcs
|
|
6076
|
-
addArc(l, t, rr, -rr, -HALF_PI, Math.PI, n, ci, ti); // top-left
|
|
6077
|
-
addArc(r, t, rr, -rr, HALF_PI, 0, n, ci, ti); // top-right
|
|
6078
|
-
addArc(r, b, rr, -rr, 0, -HALF_PI, n, ci, ti); // bottom-right
|
|
6079
|
-
addArc(l, b, rr, -rr, -HALF_PI, -Math.PI, n, ci, ti); // bottom-left
|
|
6080
|
-
|
|
6081
|
-
addRect(l, trr, r, trr, r, brr, l, brr, ci, ti); // center
|
|
6082
|
-
addRect(l, t, lrr, t, lrr, b, l, b, ci, ti); // Left
|
|
6083
|
-
addRect(rrr, t, r, t, r, b, rrr, b, ci, ti); // Right
|
|
6084
|
-
}
|
|
6085
|
-
|
|
6086
|
-
if (doStroke) {
|
|
6087
|
-
ci = strokeIdx;
|
|
6088
|
-
|
|
6089
|
-
let outerA = rr + hsw,
|
|
6090
|
-
outerB = rr + hsw,
|
|
6091
|
-
innerA = rr - hsw,
|
|
6092
|
-
innerB = rr - hsw;
|
|
6093
|
-
|
|
6094
|
-
// Corner arc strokes
|
|
6095
|
-
addArcStroke(l, t, outerA, -outerB, innerA, -innerB, Math.PI, HALF_PI, n, ci, ti); // top-left
|
|
6096
|
-
addArcStroke(r, t, outerA, -outerB, innerA, -innerB, HALF_PI, 0, n, ci, ti); // top-right
|
|
6097
|
-
addArcStroke(r, b, outerA, -outerB, innerA, -innerB, 0, -HALF_PI, n, ci, ti); // bottom-right
|
|
6098
|
-
addArcStroke(l, b, outerA, -outerB, innerA, -innerB, -HALF_PI, -Math.PI, n, ci, ti); // bottom-left
|
|
6099
|
-
|
|
6100
|
-
let lrrMin = lrr - hsw,
|
|
6101
|
-
lrrMax = lrr + hsw,
|
|
6102
|
-
rrrMin = rrr - hsw,
|
|
6103
|
-
rrrMax = rrr + hsw,
|
|
6104
|
-
trrMin = trr - hsw,
|
|
6105
|
-
trrMax = trr + hsw,
|
|
6106
|
-
brrMin = brr - hsw,
|
|
6107
|
-
brrMax = brr + hsw;
|
|
6108
|
-
|
|
6109
|
-
// Side strokes - positioned outside
|
|
6110
|
-
addRect(lrrMin, t, lrrMax, t, lrrMax, b, lrrMin, b, ci, ti); // Left
|
|
6111
|
-
addRect(rrrMin, t, rrrMax, t, rrrMax, b, rrrMin, b, ci, ti); // Right
|
|
6112
|
-
addRect(l, trrMin, r, trrMin, r, trrMax, l, trrMax, ci, ti); // Top
|
|
6113
|
-
addRect(l, brrMin, r, brrMin, r, brrMax, l, brrMax, ci, ti); // Bottom
|
|
6114
|
-
}
|
|
6115
|
-
};
|
|
6116
|
-
|
|
6117
|
-
$.square = (x, y, s) => $.rect(x, y, s, s);
|
|
6118
|
-
|
|
6119
|
-
$.plane = (x, y, w, h) => {
|
|
6120
|
-
h ??= w;
|
|
6121
|
-
let [l, r, t, b] = calcBox(x, y, w, h, 'center');
|
|
6122
|
-
if (matrixDirty) saveMatrix();
|
|
6123
|
-
addRect(l, t, r, t, r, b, l, b, fillIdx, matrixIdx);
|
|
6124
|
-
};
|
|
6125
|
-
|
|
6126
|
-
// prettier-ignore
|
|
6127
|
-
const getArcSegments = (d) =>
|
|
6128
|
-
d < 4 ? 6 :
|
|
6129
|
-
d < 6 ? 8 :
|
|
6130
|
-
d < 10 ? 10 :
|
|
6131
|
-
d < 16 ? 12 :
|
|
6132
|
-
d < 20 ? 14 :
|
|
6133
|
-
d < 22 ? 16 :
|
|
6134
|
-
d < 24 ? 18 :
|
|
6135
|
-
d < 28 ? 20 :
|
|
6136
|
-
d < 34 ? 22 :
|
|
6137
|
-
d < 42 ? 24 :
|
|
6138
|
-
d < 48 ? 26 :
|
|
6139
|
-
d < 56 ? 28 :
|
|
6140
|
-
d < 64 ? 30 :
|
|
6141
|
-
d < 72 ? 32 :
|
|
6142
|
-
d < 84 ? 34 :
|
|
6143
|
-
d < 96 ? 36 :
|
|
6144
|
-
d < 98 ? 38 :
|
|
6145
|
-
d < 113 ? 40 :
|
|
6146
|
-
d < 149 ? 44 :
|
|
6147
|
-
d < 199 ? 48 :
|
|
6148
|
-
d < 261 ? 52 :
|
|
6149
|
-
d < 353 ? 56 :
|
|
6150
|
-
d < 461 ? 60 :
|
|
6151
|
-
d < 585 ? 64 :
|
|
6152
|
-
d < 1200 ? 70 :
|
|
6153
|
-
d < 1800 ? 80 :
|
|
6154
|
-
d < 2400 ? 90 :
|
|
6155
|
-
100;
|
|
6156
|
-
|
|
6157
|
-
let _ellipseMode = 'center';
|
|
6158
|
-
|
|
6159
|
-
$.ellipseMode = (x) => (_ellipseMode = x);
|
|
6160
|
-
|
|
6161
|
-
$.ellipse = (x, y, w, h) => {
|
|
6162
|
-
let n = getArcSegments(Math.max(Math.abs(w), Math.abs(h)) * _scale);
|
|
6163
|
-
let a = w / 2;
|
|
6164
|
-
let b = w == h ? a : h / 2;
|
|
6165
|
-
|
|
6166
|
-
if (matrixDirty) saveMatrix();
|
|
6167
|
-
let ti = matrixIdx;
|
|
6168
|
-
|
|
6169
|
-
if (doFill) {
|
|
6170
|
-
addArc(x, y, a, b, 0, TAU, n, fillIdx, ti);
|
|
6171
|
-
}
|
|
6172
|
-
if (doStroke) {
|
|
6173
|
-
// Draw the stroke as a ring using triangle strips
|
|
6174
|
-
addArcStroke(x, y, a + hsw, b + hsw, a - hsw, b - hsw, 0, TAU, n, strokeIdx, ti);
|
|
6175
|
-
}
|
|
6176
|
-
};
|
|
6177
|
-
|
|
6178
|
-
$.circle = (x, y, d) => $.ellipse(x, y, d, d);
|
|
6179
|
-
|
|
6180
|
-
$.arc = (x, y, w, h, start, stop) => {
|
|
6181
|
-
if (start === stop) return $.ellipse(x, y, w, h);
|
|
6182
|
-
|
|
6183
|
-
// Convert angles if needed
|
|
6184
|
-
if ($._angleMode) {
|
|
6185
|
-
start = $.radians(start);
|
|
6186
|
-
stop = $.radians(stop);
|
|
6187
|
-
}
|
|
6188
|
-
|
|
6189
|
-
// Normalize angles
|
|
6190
|
-
start %= TAU;
|
|
6191
|
-
stop %= TAU;
|
|
6192
|
-
if (start < 0) start += TAU;
|
|
6193
|
-
if (stop < 0) stop += TAU;
|
|
6194
|
-
if (start > stop) stop += TAU;
|
|
6195
|
-
if (start == stop) return $.ellipse(x, y, w, h);
|
|
6196
|
-
|
|
6197
|
-
// Calculate position based on ellipseMode
|
|
6198
|
-
let a, b;
|
|
6199
|
-
if (_ellipseMode == $.CENTER) {
|
|
6200
|
-
a = w / 2;
|
|
6201
|
-
b = h / 2;
|
|
6202
|
-
} else if (_ellipseMode == $.RADIUS) {
|
|
6203
|
-
a = w;
|
|
6204
|
-
b = h;
|
|
6205
|
-
} else if (_ellipseMode == $.CORNER) {
|
|
6206
|
-
x += w / 2;
|
|
6207
|
-
y += h / 2;
|
|
6208
|
-
a = w / 2;
|
|
6209
|
-
b = h / 2;
|
|
6210
|
-
} else if (_ellipseMode == $.CORNERS) {
|
|
6211
|
-
x = (x + w) / 2;
|
|
6212
|
-
y = (y + h) / 2;
|
|
6213
|
-
a = (w - x) / 2;
|
|
6214
|
-
b = (h - y) / 2;
|
|
6215
|
-
}
|
|
6216
|
-
|
|
6217
|
-
if (matrixDirty) saveMatrix();
|
|
6218
|
-
let ti = matrixIdx;
|
|
6219
|
-
let n = getArcSegments(Math.max(Math.abs(w), Math.abs(h)) * _scale);
|
|
6220
|
-
|
|
6221
|
-
// Draw fill
|
|
6222
|
-
if (doFill) {
|
|
6223
|
-
addArc(x, y, a, b, start, stop, n, fillIdx, ti);
|
|
6224
|
-
}
|
|
6225
|
-
|
|
6226
|
-
// Draw stroke
|
|
6227
|
-
if (doStroke) {
|
|
6228
|
-
addArcStroke(x, y, a + hsw, b + hsw, a - hsw, b - hsw, start, stop, n, strokeIdx, ti);
|
|
6229
|
-
if (_strokeCap == 'round') {
|
|
6230
|
-
addArc(x + a * Math.cos(start), y + b * Math.sin(start), hsw, hsw, 0, TAU, n, strokeIdx, ti);
|
|
6231
|
-
addArc(x + a * Math.cos(stop), y + b * Math.sin(stop), hsw, hsw, 0, TAU, n, strokeIdx, ti);
|
|
6232
|
-
}
|
|
6233
|
-
}
|
|
6234
|
-
};
|
|
6235
|
-
|
|
6236
|
-
$.point = (x, y) => {
|
|
6237
|
-
if (matrixDirty) saveMatrix();
|
|
6238
|
-
let ti = matrixIdx,
|
|
6239
|
-
ci = strokeIdx;
|
|
6240
|
-
|
|
6241
|
-
if (scaledSW < 2) {
|
|
6242
|
-
let [l, r, t, b] = calcBox(x, y, sw, sw, 'corner');
|
|
6243
|
-
addRect(l, t, r, t, r, b, l, b, ci, ti);
|
|
6244
|
-
} else {
|
|
6245
|
-
let n = getArcSegments(scaledSW);
|
|
6246
|
-
sw /= 2;
|
|
6247
|
-
addArc(x, y, sw, sw, 0, TAU, n, ci, ti);
|
|
6248
|
-
}
|
|
6249
|
-
};
|
|
6250
|
-
|
|
6251
6004
|
let _strokeCap = 'round',
|
|
6252
6005
|
_strokeJoin = 'round';
|
|
6253
6006
|
|
|
@@ -6258,29 +6011,6 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
|
|
|
6258
6011
|
_strokeJoin = 'none';
|
|
6259
6012
|
};
|
|
6260
6013
|
|
|
6261
|
-
$.line = (x1, y1, x2, y2) => {
|
|
6262
|
-
if (matrixDirty) saveMatrix();
|
|
6263
|
-
let ti = matrixIdx,
|
|
6264
|
-
ci = strokeIdx;
|
|
6265
|
-
|
|
6266
|
-
// calculate the direction vector and length
|
|
6267
|
-
let dx = x2 - x1,
|
|
6268
|
-
dy = y2 - y1,
|
|
6269
|
-
length = Math.hypot(dx, dy);
|
|
6270
|
-
|
|
6271
|
-
// calculate the perpendicular vector for line thickness
|
|
6272
|
-
let px = -(dy / length) * hsw,
|
|
6273
|
-
py = (dx / length) * hsw;
|
|
6274
|
-
|
|
6275
|
-
addRect(x1 + px, y1 + py, x1 - px, y1 - py, x2 - px, y2 - py, x2 + px, y2 + py, ci, ti);
|
|
6276
|
-
|
|
6277
|
-
if (scaledSW > 2 && _strokeCap != 'square') {
|
|
6278
|
-
let n = getArcSegments(scaledSW);
|
|
6279
|
-
addArc(x1, y1, hsw, hsw, 0, TAU, n, ci, ti);
|
|
6280
|
-
addArc(x2, y2, hsw, hsw, 0, TAU, n, ci, ti);
|
|
6281
|
-
}
|
|
6282
|
-
};
|
|
6283
|
-
|
|
6284
6014
|
let curveSegments = 20;
|
|
6285
6015
|
$.curveDetail = (v) => (curveSegments = v);
|
|
6286
6016
|
|
|
@@ -6443,23 +6173,18 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
|
|
|
6443
6173
|
}
|
|
6444
6174
|
|
|
6445
6175
|
if (doStroke) {
|
|
6446
|
-
let n = getArcSegments(scaledSW),
|
|
6447
|
-
ti = matrixIdx,
|
|
6448
|
-
ogStrokeCap = _strokeCap;
|
|
6449
|
-
_strokeCap = 'square';
|
|
6450
6176
|
// draw lines between vertices
|
|
6451
6177
|
for (let i = 0; i < shapeVertCount - 1; i++) {
|
|
6452
6178
|
let v1 = i * 4;
|
|
6453
6179
|
let v2 = (i + 1) * 4;
|
|
6454
6180
|
$.line(sv[v1], sv[v1 + 1], sv[v2], sv[v2 + 1]);
|
|
6455
6181
|
|
|
6456
|
-
|
|
6182
|
+
// addEllipse(sv[v1], sv[v1 + 1], qsw, qsw, 0, TAU, hsw, 0);
|
|
6457
6183
|
}
|
|
6458
6184
|
let v1 = (shapeVertCount - 1) * 4;
|
|
6459
6185
|
let v2 = 0;
|
|
6460
6186
|
if (close) $.line(sv[v1], sv[v1 + 1], sv[v2], sv[v2 + 1]);
|
|
6461
|
-
|
|
6462
|
-
_strokeCap = ogStrokeCap;
|
|
6187
|
+
// addEllipse(sv[v1], sv[v1 + 1], qsw, qsw, 0, TAU, hsw, 0);
|
|
6463
6188
|
}
|
|
6464
6189
|
|
|
6465
6190
|
// reset for the next shape
|
|
@@ -6501,6 +6226,608 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
|
|
|
6501
6226
|
$.endShape(true);
|
|
6502
6227
|
};
|
|
6503
6228
|
|
|
6229
|
+
function addQuad(x1, y1, x2, y2, x3, y3, x4, y4, ci, ti) {
|
|
6230
|
+
addVert(x1, y1, ci, ti); // v0
|
|
6231
|
+
addVert(x2, y2, ci, ti); // v1
|
|
6232
|
+
addVert(x4, y4, ci, ti); // v3
|
|
6233
|
+
addVert(x3, y3, ci, ti); // v2
|
|
6234
|
+
drawStack.push(shapesPL, 4);
|
|
6235
|
+
}
|
|
6236
|
+
|
|
6237
|
+
$.plane = (x, y, w, h) => {
|
|
6238
|
+
h ??= w;
|
|
6239
|
+
let [l, r, t, b] = calcBox(x, y, w, h, 'center');
|
|
6240
|
+
if (matrixDirty) saveMatrix();
|
|
6241
|
+
addQuad(l, t, r, t, r, b, l, b, fillIdx, matrixIdx);
|
|
6242
|
+
};
|
|
6243
|
+
|
|
6244
|
+
/* RECT */
|
|
6245
|
+
|
|
6246
|
+
let rectPL = 5;
|
|
6247
|
+
|
|
6248
|
+
$._rectShaderCode =
|
|
6249
|
+
$._baseShaderCode +
|
|
6250
|
+
/* wgsl */ `
|
|
6251
|
+
struct Rect {
|
|
6252
|
+
center: vec2f,
|
|
6253
|
+
extents: vec2f,
|
|
6254
|
+
roundedRadius: f32,
|
|
6255
|
+
strokeWeight: f32,
|
|
6256
|
+
fillIndex: f32,
|
|
6257
|
+
strokeIndex: f32,
|
|
6258
|
+
matrixIndex: f32,
|
|
6259
|
+
padding0: f32, // can't use vec3f for alignment
|
|
6260
|
+
padding1: vec2f,
|
|
6261
|
+
padding2: vec4f
|
|
6262
|
+
};
|
|
6263
|
+
|
|
6264
|
+
struct VertexParams {
|
|
6265
|
+
@builtin(vertex_index) vertIndex: u32,
|
|
6266
|
+
@builtin(instance_index) instIndex: u32
|
|
6267
|
+
};
|
|
6268
|
+
|
|
6269
|
+
struct FragParams {
|
|
6270
|
+
@builtin(position) position: vec4f,
|
|
6271
|
+
@location(0) local: vec2f,
|
|
6272
|
+
@location(1) extents: vec2f,
|
|
6273
|
+
@location(2) roundedRadius: f32,
|
|
6274
|
+
@location(3) strokeWeight: f32,
|
|
6275
|
+
@location(4) fill: vec4f,
|
|
6276
|
+
@location(5) stroke: vec4f,
|
|
6277
|
+
@location(6) blend: vec4f
|
|
6278
|
+
};
|
|
6279
|
+
|
|
6280
|
+
@group(0) @binding(0) var<uniform> q: Q5;
|
|
6281
|
+
@group(0) @binding(1) var<storage> transforms: array<mat4x4<f32>>;
|
|
6282
|
+
@group(0) @binding(2) var<storage> colors : array<vec4f>;
|
|
6283
|
+
|
|
6284
|
+
@group(1) @binding(0) var<storage, read> rects: array<Rect>;
|
|
6285
|
+
|
|
6286
|
+
const quad = array(
|
|
6287
|
+
vec2f(-1.0, -1.0),
|
|
6288
|
+
vec2f( 1.0, -1.0),
|
|
6289
|
+
vec2f(-1.0, 1.0),
|
|
6290
|
+
vec2f( 1.0, 1.0)
|
|
6291
|
+
);
|
|
6292
|
+
const transparent = vec4f(0.0);
|
|
6293
|
+
|
|
6294
|
+
fn transformVertex(pos: vec2f, matrixIndex: f32) -> vec4f {
|
|
6295
|
+
var vert = vec4f(pos, 0.0, 1.0);
|
|
6296
|
+
vert = transforms[i32(matrixIndex)] * vert;
|
|
6297
|
+
vert.x /= q.halfWidth;
|
|
6298
|
+
vert.y /= q.halfHeight;
|
|
6299
|
+
return vert;
|
|
6300
|
+
}
|
|
6301
|
+
|
|
6302
|
+
@vertex
|
|
6303
|
+
fn vertexMain(v: VertexParams) -> FragParams {
|
|
6304
|
+
let rect = rects[v.instIndex];
|
|
6305
|
+
|
|
6306
|
+
let halfStrokeSize = vec2f(rect.strokeWeight * 0.5);
|
|
6307
|
+
let quadSize = rect.extents + halfStrokeSize;
|
|
6308
|
+
let pos = (quad[v.vertIndex] * quadSize) + rect.center;
|
|
6309
|
+
|
|
6310
|
+
let local = pos - rect.center;
|
|
6311
|
+
|
|
6312
|
+
var f: FragParams;
|
|
6313
|
+
f.position = transformVertex(pos, rect.matrixIndex);
|
|
6314
|
+
|
|
6315
|
+
f.local = local;
|
|
6316
|
+
f.extents = rect.extents;
|
|
6317
|
+
f.roundedRadius = rect.roundedRadius;
|
|
6318
|
+
f.strokeWeight = rect.strokeWeight;
|
|
6319
|
+
|
|
6320
|
+
let fill = colors[i32(rect.fillIndex)];
|
|
6321
|
+
let stroke = colors[i32(rect.strokeIndex)];
|
|
6322
|
+
f.fill = fill;
|
|
6323
|
+
f.stroke = stroke;
|
|
6324
|
+
|
|
6325
|
+
// Source-over blend: stroke over fill (pre-multiplied alpha)
|
|
6326
|
+
if (fill.a != 0.0 && stroke.a != 1.0) {
|
|
6327
|
+
let outAlpha = stroke.a + fill.a * (1.0 - stroke.a);
|
|
6328
|
+
let outColor = stroke.rgb * stroke.a + fill.rgb * fill.a * (1.0 - stroke.a);
|
|
6329
|
+
f.blend = vec4f(outColor / max(outAlpha, 1e-5), outAlpha);
|
|
6330
|
+
}
|
|
6331
|
+
return f;
|
|
6332
|
+
}
|
|
6333
|
+
|
|
6334
|
+
fn sdRoundRect(p: vec2f, extents: vec2f, radius: f32) -> f32 {
|
|
6335
|
+
let q = abs(p) - extents + vec2f(radius);
|
|
6336
|
+
return length(max(q, vec2f(0.0))) - radius + min(max(q.x, q.y), 0.0);
|
|
6337
|
+
}
|
|
6338
|
+
|
|
6339
|
+
@fragment
|
|
6340
|
+
fn fragMain(f: FragParams) -> @location(0) vec4f {
|
|
6341
|
+
let dist = select(
|
|
6342
|
+
max(abs(f.local.x) - f.extents.x, abs(f.local.y) - f.extents.y), // sharp
|
|
6343
|
+
sdRoundRect(f.local, f.extents, f.roundedRadius), // rounded
|
|
6344
|
+
f.roundedRadius > 0.0
|
|
6345
|
+
);
|
|
6346
|
+
|
|
6347
|
+
// fill only
|
|
6348
|
+
if (f.fill.a != 0.0 && f.strokeWeight == 0.0) {
|
|
6349
|
+
if (dist <= 0.0) {
|
|
6350
|
+
return f.fill;
|
|
6351
|
+
}
|
|
6352
|
+
return transparent;
|
|
6353
|
+
}
|
|
6354
|
+
|
|
6355
|
+
let halfStroke = f.strokeWeight * 0.5;
|
|
6356
|
+
let inner = dist + halfStroke;
|
|
6357
|
+
|
|
6358
|
+
if (f.fill.a != 0.0) {
|
|
6359
|
+
if (inner <= 0.0) {
|
|
6360
|
+
return f.fill;
|
|
6361
|
+
}
|
|
6362
|
+
if (dist <= 0.0 && f.stroke.a != 1.0) {
|
|
6363
|
+
return f.blend;
|
|
6364
|
+
}
|
|
6365
|
+
}
|
|
6366
|
+
|
|
6367
|
+
let outer = dist - halfStroke;
|
|
6368
|
+
|
|
6369
|
+
if (outer <= 0.0 && inner >= 0.0) {
|
|
6370
|
+
return f.stroke;
|
|
6371
|
+
}
|
|
6372
|
+
|
|
6373
|
+
return transparent;
|
|
6374
|
+
}
|
|
6375
|
+
`;
|
|
6376
|
+
|
|
6377
|
+
let rectShader = Q5.device.createShaderModule({
|
|
6378
|
+
label: 'rectShader',
|
|
6379
|
+
code: $._rectShaderCode
|
|
6380
|
+
});
|
|
6381
|
+
|
|
6382
|
+
let rectIndices = new Uint16Array([0, 1, 2, 2, 1, 3]);
|
|
6383
|
+
|
|
6384
|
+
let rectIndexBuffer = Q5.device.createBuffer({
|
|
6385
|
+
size: rectIndices.byteLength,
|
|
6386
|
+
usage: GPUBufferUsage.INDEX,
|
|
6387
|
+
mappedAtCreation: true
|
|
6388
|
+
});
|
|
6389
|
+
new Uint16Array(rectIndexBuffer.getMappedRange()).set(rectIndices);
|
|
6390
|
+
rectIndexBuffer.unmap();
|
|
6391
|
+
|
|
6392
|
+
let rectBindGroupLayout = Q5.device.createBindGroupLayout({
|
|
6393
|
+
entries: [
|
|
6394
|
+
{
|
|
6395
|
+
binding: 0,
|
|
6396
|
+
visibility: GPUShaderStage.VERTEX,
|
|
6397
|
+
buffer: { type: 'read-only-storage' }
|
|
6398
|
+
}
|
|
6399
|
+
]
|
|
6400
|
+
});
|
|
6401
|
+
|
|
6402
|
+
let rectPipelineLayout = Q5.device.createPipelineLayout({
|
|
6403
|
+
label: 'rectPipelineLayout',
|
|
6404
|
+
bindGroupLayouts: [...$._bindGroupLayouts, rectBindGroupLayout]
|
|
6405
|
+
});
|
|
6406
|
+
|
|
6407
|
+
$._pipelineConfigs[5] = {
|
|
6408
|
+
label: 'rectPipeline',
|
|
6409
|
+
layout: rectPipelineLayout,
|
|
6410
|
+
vertex: {
|
|
6411
|
+
module: rectShader,
|
|
6412
|
+
entryPoint: 'vertexMain',
|
|
6413
|
+
buffers: []
|
|
6414
|
+
},
|
|
6415
|
+
fragment: {
|
|
6416
|
+
module: rectShader,
|
|
6417
|
+
entryPoint: 'fragMain',
|
|
6418
|
+
targets: [
|
|
6419
|
+
{
|
|
6420
|
+
format: 'bgra8unorm',
|
|
6421
|
+
blend: $.blendConfigs['source-over']
|
|
6422
|
+
}
|
|
6423
|
+
]
|
|
6424
|
+
},
|
|
6425
|
+
primitive: { topology: 'triangle-list' },
|
|
6426
|
+
multisample: { count: 4 }
|
|
6427
|
+
};
|
|
6428
|
+
|
|
6429
|
+
$._pipelines[5] = Q5.device.createRenderPipeline($._pipelineConfigs[5]);
|
|
6430
|
+
|
|
6431
|
+
let rectStack = new Float32Array(Q5.MAX_RECTS * 16);
|
|
6432
|
+
let rectStackIdx = 0;
|
|
6433
|
+
|
|
6434
|
+
let rectBuffer = Q5.device.createBuffer({
|
|
6435
|
+
size: rectStack.byteLength,
|
|
6436
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
6437
|
+
});
|
|
6438
|
+
|
|
6439
|
+
let rectBindGroup = Q5.device.createBindGroup({
|
|
6440
|
+
layout: rectBindGroupLayout,
|
|
6441
|
+
entries: [{ binding: 0, resource: { buffer: rectBuffer } }]
|
|
6442
|
+
});
|
|
6443
|
+
|
|
6444
|
+
function addRect(x, y, hw, hh, roundedRadius, strokeW, fillRect) {
|
|
6445
|
+
let s = rectStack,
|
|
6446
|
+
i = rectStackIdx;
|
|
6447
|
+
|
|
6448
|
+
s[i] = x;
|
|
6449
|
+
s[i + 1] = y;
|
|
6450
|
+
s[i + 2] = hw;
|
|
6451
|
+
s[i + 3] = hh;
|
|
6452
|
+
s[i + 4] = roundedRadius;
|
|
6453
|
+
s[i + 5] = strokeW;
|
|
6454
|
+
s[i + 6] = fillRect;
|
|
6455
|
+
s[i + 7] = strokeIdx;
|
|
6456
|
+
s[i + 8] = matrixIdx;
|
|
6457
|
+
|
|
6458
|
+
rectStackIdx += 16;
|
|
6459
|
+
drawStack.push(rectPL, 1);
|
|
6460
|
+
}
|
|
6461
|
+
|
|
6462
|
+
let _rectMode = 'corner';
|
|
6463
|
+
|
|
6464
|
+
$.rectMode = (x) => (_rectMode = x);
|
|
6465
|
+
|
|
6466
|
+
function applyRectMode(x, y, w, h) {
|
|
6467
|
+
let hw = w / 2,
|
|
6468
|
+
hh = h / 2;
|
|
6469
|
+
if (_rectMode != 'center') {
|
|
6470
|
+
if (_rectMode == 'corner') {
|
|
6471
|
+
x += hw;
|
|
6472
|
+
y += hh;
|
|
6473
|
+
} else if (_rectMode == 'radius') {
|
|
6474
|
+
hw = w;
|
|
6475
|
+
hh = h;
|
|
6476
|
+
} else if (_rectMode == 'corners') {
|
|
6477
|
+
hw = (x - w) / 2;
|
|
6478
|
+
hh = (y - h) / 2;
|
|
6479
|
+
x += hw;
|
|
6480
|
+
y += hh;
|
|
6481
|
+
}
|
|
6482
|
+
}
|
|
6483
|
+
return [x, y, hw, hh];
|
|
6484
|
+
}
|
|
6485
|
+
|
|
6486
|
+
$.rect = (x, y, w, h, rr = 0) => {
|
|
6487
|
+
if (matrixDirty) saveMatrix();
|
|
6488
|
+
|
|
6489
|
+
let hw, hh;
|
|
6490
|
+
[x, y, hw, hh] = applyRectMode(x, y, w, h);
|
|
6491
|
+
|
|
6492
|
+
addRect(x, y, hw, hh, rr, doStroke ? sw : 0, doFill ? fillIdx : 0);
|
|
6493
|
+
};
|
|
6494
|
+
|
|
6495
|
+
$.square = (x, y, s) => $.rect(x, y, s, s);
|
|
6496
|
+
|
|
6497
|
+
function addCapsule(x1, y1, x2, y2, r, strokeW, fillCapsule) {
|
|
6498
|
+
let dx = x2 - x1,
|
|
6499
|
+
dy = y2 - y1,
|
|
6500
|
+
len = Math.hypot(dx, dy);
|
|
6501
|
+
|
|
6502
|
+
if (len === 0) return;
|
|
6503
|
+
|
|
6504
|
+
let angle = Math.atan2(dy, dx),
|
|
6505
|
+
cx = (x1 + x2) / 2,
|
|
6506
|
+
cy = (y1 + y2) / 2;
|
|
6507
|
+
|
|
6508
|
+
if ($._angleMode) angle *= $._RADTODEG;
|
|
6509
|
+
|
|
6510
|
+
$.pushMatrix();
|
|
6511
|
+
$.translate(cx, cy);
|
|
6512
|
+
$.rotate(angle);
|
|
6513
|
+
|
|
6514
|
+
if (matrixDirty) saveMatrix();
|
|
6515
|
+
|
|
6516
|
+
addRect(0, 0, len / 2 + r, r, r, strokeW, fillCapsule);
|
|
6517
|
+
|
|
6518
|
+
$.popMatrix();
|
|
6519
|
+
}
|
|
6520
|
+
|
|
6521
|
+
$.capsule = (x1, y1, x2, y2, r) => {
|
|
6522
|
+
addCapsule(x1, y1, x2, y2, r, doStroke ? sw : 0, doFill ? fillIdx : 0);
|
|
6523
|
+
};
|
|
6524
|
+
|
|
6525
|
+
$.line = (x1, y1, x2, y2) => {
|
|
6526
|
+
if (doStroke) addCapsule(x1, y1, x2, y2, qsw, hsw, 0);
|
|
6527
|
+
};
|
|
6528
|
+
|
|
6529
|
+
/* ELLIPSE */
|
|
6530
|
+
|
|
6531
|
+
let ellipsePL = 6;
|
|
6532
|
+
|
|
6533
|
+
$._ellipseShaderCode =
|
|
6534
|
+
$._baseShaderCode +
|
|
6535
|
+
/* wgsl */ `
|
|
6536
|
+
struct Ellipse {
|
|
6537
|
+
center: vec2f,
|
|
6538
|
+
size: vec2f,
|
|
6539
|
+
startAngle: f32,
|
|
6540
|
+
endAngle: f32,
|
|
6541
|
+
strokeWeight: f32,
|
|
6542
|
+
fillIndex: f32,
|
|
6543
|
+
strokeIndex: f32,
|
|
6544
|
+
matrixIndex: f32,
|
|
6545
|
+
padding0: vec2f,
|
|
6546
|
+
padding1: vec4f
|
|
6547
|
+
};
|
|
6548
|
+
|
|
6549
|
+
struct VertexParams {
|
|
6550
|
+
@builtin(vertex_index) vertIndex: u32,
|
|
6551
|
+
@builtin(instance_index) instIndex: u32
|
|
6552
|
+
};
|
|
6553
|
+
|
|
6554
|
+
struct FragParams {
|
|
6555
|
+
@builtin(position) position: vec4f,
|
|
6556
|
+
@location(0) outerEdge: vec2f,
|
|
6557
|
+
@location(1) fillEdge: vec2f,
|
|
6558
|
+
@location(2) innerEdge: vec2f,
|
|
6559
|
+
@location(3) strokeWeight: f32,
|
|
6560
|
+
@location(4) fill: vec4f,
|
|
6561
|
+
@location(5) stroke: vec4f,
|
|
6562
|
+
@location(6) blend: vec4f
|
|
6563
|
+
};
|
|
6564
|
+
|
|
6565
|
+
@group(0) @binding(0) var<uniform> q: Q5;
|
|
6566
|
+
@group(0) @binding(1) var<storage> transforms: array<mat4x4<f32>>;
|
|
6567
|
+
@group(0) @binding(2) var<storage> colors : array<vec4f>;
|
|
6568
|
+
|
|
6569
|
+
@group(1) @binding(0) var<storage, read> ellipses: array<Ellipse>;
|
|
6570
|
+
|
|
6571
|
+
const PI = 3.141592653589793;
|
|
6572
|
+
const segments = 6.0;
|
|
6573
|
+
const expansion = 1.0 / cos(PI / segments);
|
|
6574
|
+
const antiAlias = 0.015625; // 1/64
|
|
6575
|
+
const transparent = vec4f(0.0);
|
|
6576
|
+
|
|
6577
|
+
fn transformVertex(pos: vec2f, matrixIndex: f32) -> vec4f {
|
|
6578
|
+
var vert = vec4f(pos, 0.0, 1.0);
|
|
6579
|
+
vert = transforms[i32(matrixIndex)] * vert;
|
|
6580
|
+
vert.x /= q.halfWidth;
|
|
6581
|
+
vert.y /= q.halfHeight;
|
|
6582
|
+
return vert;
|
|
6583
|
+
}
|
|
6584
|
+
|
|
6585
|
+
@vertex
|
|
6586
|
+
fn vertexMain(v: VertexParams) -> FragParams {
|
|
6587
|
+
let ellipse = ellipses[v.instIndex];
|
|
6588
|
+
var pos = ellipse.center;
|
|
6589
|
+
var local = vec2f(0.0);
|
|
6590
|
+
let start = ellipse.startAngle;
|
|
6591
|
+
let end = ellipse.endAngle;
|
|
6592
|
+
let arc = end - start;
|
|
6593
|
+
let halfStrokeSize = vec2f(ellipse.strokeWeight * 0.5);
|
|
6594
|
+
|
|
6595
|
+
let fanSize = (ellipse.size + halfStrokeSize) * expansion;
|
|
6596
|
+
|
|
6597
|
+
if (v.vertIndex != 0) {
|
|
6598
|
+
let theta = start + (f32(v.vertIndex - 1) / segments) * arc;
|
|
6599
|
+
local = vec2f(cos(theta), sin(theta));
|
|
6600
|
+
pos = ellipse.center + local * fanSize;
|
|
6601
|
+
}
|
|
6602
|
+
|
|
6603
|
+
let dist = pos - ellipse.center;
|
|
6604
|
+
|
|
6605
|
+
var f: FragParams;
|
|
6606
|
+
f.position = transformVertex(pos, ellipse.matrixIndex);
|
|
6607
|
+
f.outerEdge = dist / (ellipse.size + halfStrokeSize);
|
|
6608
|
+
f.fillEdge = dist / ellipse.size;
|
|
6609
|
+
f.innerEdge = dist / (ellipse.size - halfStrokeSize);
|
|
6610
|
+
f.strokeWeight = ellipse.strokeWeight;
|
|
6611
|
+
|
|
6612
|
+
let fill = colors[i32(ellipse.fillIndex)];
|
|
6613
|
+
let stroke = colors[i32(ellipse.strokeIndex)];
|
|
6614
|
+
f.fill = fill;
|
|
6615
|
+
f.stroke = stroke;
|
|
6616
|
+
|
|
6617
|
+
// Source-over blend: stroke over fill (pre-multiplied alpha)
|
|
6618
|
+
if (fill.a != 0.0 && stroke.a != 1.0) {
|
|
6619
|
+
let outAlpha = stroke.a + fill.a * (1.0 - stroke.a);
|
|
6620
|
+
let outColor = stroke.rgb * stroke.a + fill.rgb * fill.a * (1.0 - stroke.a);
|
|
6621
|
+
f.blend = vec4f(outColor / max(outAlpha, 1e-5), outAlpha);
|
|
6622
|
+
}
|
|
6623
|
+
return f;
|
|
6624
|
+
}
|
|
6625
|
+
|
|
6626
|
+
@fragment
|
|
6627
|
+
fn fragMain(f: FragParams) -> @location(0) vec4f {
|
|
6628
|
+
let fillEdge = length(f.fillEdge);
|
|
6629
|
+
|
|
6630
|
+
// disable AA for very thin strokes
|
|
6631
|
+
let aa = select(antiAlias, 0.0, f.strokeWeight <= 1.0);
|
|
6632
|
+
|
|
6633
|
+
if (f.fill.a != 0.0 && f.strokeWeight == 0.0) {
|
|
6634
|
+
if (fillEdge > 1.0) {
|
|
6635
|
+
return transparent;
|
|
6636
|
+
}
|
|
6637
|
+
let fillAlpha = 1.0 - smoothstep(1.0 - aa, 1.0, fillEdge);
|
|
6638
|
+
return vec4f(f.fill.rgb, f.fill.a * fillAlpha);
|
|
6639
|
+
}
|
|
6640
|
+
|
|
6641
|
+
let innerEdge = length(f.innerEdge);
|
|
6642
|
+
|
|
6643
|
+
if (f.fill.a != 0.0 && fillEdge < 1.0) {
|
|
6644
|
+
if (innerEdge < 1.0) {
|
|
6645
|
+
return f.fill;
|
|
6646
|
+
}
|
|
6647
|
+
let tInner = smoothstep(1.0, 1.0 + aa, innerEdge);
|
|
6648
|
+
let tOuter = smoothstep(1.0 - aa, 1.0, fillEdge);
|
|
6649
|
+
if (f.stroke.a != 1.0) {
|
|
6650
|
+
let fillBlend = mix(f.fill, f.blend, tInner);
|
|
6651
|
+
return mix(fillBlend, f.stroke, tOuter);
|
|
6652
|
+
} else {
|
|
6653
|
+
let fillBlend = mix(f.fill, f.stroke, tInner);
|
|
6654
|
+
return mix(fillBlend, f.stroke, tOuter);
|
|
6655
|
+
}
|
|
6656
|
+
}
|
|
6657
|
+
|
|
6658
|
+
if (innerEdge < 1.0) {
|
|
6659
|
+
return transparent;
|
|
6660
|
+
}
|
|
6661
|
+
|
|
6662
|
+
let outerEdge = length(f.outerEdge);
|
|
6663
|
+
let outerAlpha = 1.0 - smoothstep(1.0 - aa, 1.0, outerEdge);
|
|
6664
|
+
let innerAlpha = smoothstep(1.0, 1.0 + aa, innerEdge);
|
|
6665
|
+
let strokeAlpha = innerAlpha * outerAlpha;
|
|
6666
|
+
return vec4f(f.stroke.rgb, f.stroke.a * strokeAlpha);
|
|
6667
|
+
}
|
|
6668
|
+
`;
|
|
6669
|
+
|
|
6670
|
+
let ellipseShader = Q5.device.createShaderModule({
|
|
6671
|
+
label: 'ellipseShader',
|
|
6672
|
+
code: $._ellipseShaderCode
|
|
6673
|
+
});
|
|
6674
|
+
|
|
6675
|
+
let fanIndices = new Uint16Array([0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 5, 6, 0, 6, 7]);
|
|
6676
|
+
|
|
6677
|
+
let ellipseIndexBuffer = Q5.device.createBuffer({
|
|
6678
|
+
size: fanIndices.byteLength,
|
|
6679
|
+
usage: GPUBufferUsage.INDEX,
|
|
6680
|
+
mappedAtCreation: true
|
|
6681
|
+
});
|
|
6682
|
+
new Uint16Array(ellipseIndexBuffer.getMappedRange()).set(fanIndices);
|
|
6683
|
+
ellipseIndexBuffer.unmap();
|
|
6684
|
+
|
|
6685
|
+
let ellipseBindGroupLayout = Q5.device.createBindGroupLayout({
|
|
6686
|
+
entries: [
|
|
6687
|
+
{
|
|
6688
|
+
binding: 0,
|
|
6689
|
+
visibility: GPUShaderStage.VERTEX,
|
|
6690
|
+
buffer: { type: 'read-only-storage' }
|
|
6691
|
+
}
|
|
6692
|
+
]
|
|
6693
|
+
});
|
|
6694
|
+
|
|
6695
|
+
let ellipsePipelineLayout = Q5.device.createPipelineLayout({
|
|
6696
|
+
label: 'ellipsePipelineLayout',
|
|
6697
|
+
bindGroupLayouts: [...$._bindGroupLayouts, ellipseBindGroupLayout]
|
|
6698
|
+
});
|
|
6699
|
+
|
|
6700
|
+
$._pipelineConfigs[6] = {
|
|
6701
|
+
label: 'ellipsePipeline',
|
|
6702
|
+
layout: ellipsePipelineLayout,
|
|
6703
|
+
vertex: {
|
|
6704
|
+
module: ellipseShader,
|
|
6705
|
+
entryPoint: 'vertexMain',
|
|
6706
|
+
buffers: []
|
|
6707
|
+
},
|
|
6708
|
+
fragment: {
|
|
6709
|
+
module: ellipseShader,
|
|
6710
|
+
entryPoint: 'fragMain',
|
|
6711
|
+
targets: [
|
|
6712
|
+
{
|
|
6713
|
+
format: 'bgra8unorm',
|
|
6714
|
+
blend: $.blendConfigs['source-over']
|
|
6715
|
+
}
|
|
6716
|
+
]
|
|
6717
|
+
},
|
|
6718
|
+
primitive: { topology: 'triangle-list' },
|
|
6719
|
+
multisample: { count: 4 }
|
|
6720
|
+
};
|
|
6721
|
+
|
|
6722
|
+
$._pipelines[6] = Q5.device.createRenderPipeline($._pipelineConfigs[6]);
|
|
6723
|
+
|
|
6724
|
+
let ellipseStack = new Float32Array(Q5.MAX_ELLIPSES * 16);
|
|
6725
|
+
let ellipseStackIdx = 0;
|
|
6726
|
+
|
|
6727
|
+
let ellipseBuffer = Q5.device.createBuffer({
|
|
6728
|
+
size: ellipseStack.byteLength,
|
|
6729
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
6730
|
+
});
|
|
6731
|
+
|
|
6732
|
+
let ellipseBindGroup = Q5.device.createBindGroup({
|
|
6733
|
+
layout: ellipseBindGroupLayout,
|
|
6734
|
+
entries: [{ binding: 0, resource: { buffer: ellipseBuffer } }]
|
|
6735
|
+
});
|
|
6736
|
+
|
|
6737
|
+
function addEllipse(x, y, a, b, start, stop, strokeW, fillEllipse) {
|
|
6738
|
+
let s = ellipseStack,
|
|
6739
|
+
i = ellipseStackIdx;
|
|
6740
|
+
|
|
6741
|
+
s[i] = x;
|
|
6742
|
+
s[i + 1] = y;
|
|
6743
|
+
s[i + 2] = a;
|
|
6744
|
+
s[i + 3] = b;
|
|
6745
|
+
s[i + 4] = start;
|
|
6746
|
+
s[i + 5] = stop;
|
|
6747
|
+
s[i + 6] = strokeW;
|
|
6748
|
+
s[i + 7] = fillEllipse ? fillIdx : 0;
|
|
6749
|
+
s[i + 8] = strokeIdx;
|
|
6750
|
+
s[i + 9] = matrixIdx;
|
|
6751
|
+
|
|
6752
|
+
ellipseStackIdx += 16;
|
|
6753
|
+
drawStack.push(ellipsePL, 1);
|
|
6754
|
+
}
|
|
6755
|
+
|
|
6756
|
+
let _ellipseMode = 'center';
|
|
6757
|
+
|
|
6758
|
+
$.ellipseMode = (x) => (_ellipseMode = x);
|
|
6759
|
+
|
|
6760
|
+
function applyEllipseMode(x, y, w, h) {
|
|
6761
|
+
h ??= w;
|
|
6762
|
+
let a, b;
|
|
6763
|
+
if (_ellipseMode == 'center') {
|
|
6764
|
+
a = w / 2;
|
|
6765
|
+
b = h / 2;
|
|
6766
|
+
} else if (_ellipseMode == 'radius') {
|
|
6767
|
+
a = w;
|
|
6768
|
+
b = h;
|
|
6769
|
+
} else if (_ellipseMode == 'corner') {
|
|
6770
|
+
x += w / 2;
|
|
6771
|
+
y += h / 2;
|
|
6772
|
+
a = w / 2;
|
|
6773
|
+
b = h / 2;
|
|
6774
|
+
} else if (_ellipseMode == 'corners') {
|
|
6775
|
+
x = (x + w) / 2;
|
|
6776
|
+
y = (y + h) / 2;
|
|
6777
|
+
a = (w - x) / 2;
|
|
6778
|
+
b = (h - y) / 2;
|
|
6779
|
+
}
|
|
6780
|
+
return [x, y, a, b];
|
|
6781
|
+
}
|
|
6782
|
+
|
|
6783
|
+
$.ellipse = (x, y, w, h) => {
|
|
6784
|
+
let a, b;
|
|
6785
|
+
[x, y, a, b] = applyEllipseMode(x, y, w, h);
|
|
6786
|
+
|
|
6787
|
+
if (matrixDirty) saveMatrix();
|
|
6788
|
+
|
|
6789
|
+
addEllipse(x, y, a, b, 0, TAU, sw, doFill);
|
|
6790
|
+
};
|
|
6791
|
+
|
|
6792
|
+
$.circle = (x, y, d) => $.ellipse(x, y, d, d);
|
|
6793
|
+
|
|
6794
|
+
$.arc = (x, y, w, h, start, stop) => {
|
|
6795
|
+
if (start === stop) return $.ellipse(x, y, w, h);
|
|
6796
|
+
|
|
6797
|
+
// Convert angles if needed
|
|
6798
|
+
if ($._angleMode) {
|
|
6799
|
+
start = $.radians(start);
|
|
6800
|
+
stop = $.radians(stop);
|
|
6801
|
+
}
|
|
6802
|
+
|
|
6803
|
+
// Normalize angles
|
|
6804
|
+
start %= TAU;
|
|
6805
|
+
stop %= TAU;
|
|
6806
|
+
if (start < 0) start += TAU;
|
|
6807
|
+
if (stop < 0) stop += TAU;
|
|
6808
|
+
if (start > stop) stop += TAU;
|
|
6809
|
+
if (start == stop) return $.ellipse(x, y, w, h);
|
|
6810
|
+
|
|
6811
|
+
// Calculate position based on ellipseMode
|
|
6812
|
+
let a, b;
|
|
6813
|
+
[x, y, a, b] = applyEllipseMode(x, y, w, h);
|
|
6814
|
+
|
|
6815
|
+
if (matrixDirty) saveMatrix();
|
|
6816
|
+
|
|
6817
|
+
addEllipse(x, y, a, b, start, stop, sw, doFill);
|
|
6818
|
+
};
|
|
6819
|
+
|
|
6820
|
+
$.point = (x, y) => {
|
|
6821
|
+
if (matrixDirty) saveMatrix();
|
|
6822
|
+
|
|
6823
|
+
if (scaledHSW < 1) {
|
|
6824
|
+
addRect(x, y, hsw, hsw, 0, sw, 0);
|
|
6825
|
+
} else {
|
|
6826
|
+
// dimensions of the point needs to be set to half the stroke weight
|
|
6827
|
+
addEllipse(x, y, hsw, hsw, 0, TAU, sw, 0);
|
|
6828
|
+
}
|
|
6829
|
+
};
|
|
6830
|
+
|
|
6504
6831
|
/* IMAGE */
|
|
6505
6832
|
|
|
6506
6833
|
let imagePL = 2,
|
|
@@ -7636,6 +7963,9 @@ Q5.DILATE = 6;
|
|
|
7636
7963
|
Q5.ERODE = 7;
|
|
7637
7964
|
Q5.BLUR = 8;
|
|
7638
7965
|
|
|
7966
|
+
Q5.MAX_RECTS = 200200;
|
|
7967
|
+
Q5.MAX_ELLIPSES = 200200;
|
|
7968
|
+
|
|
7639
7969
|
Q5.initWebGPU = async () => {
|
|
7640
7970
|
if (!navigator.gpu) {
|
|
7641
7971
|
console.warn('q5 WebGPU not supported on this browser! Use Google Chrome or Edge.');
|