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.
- package/deno.json +1 -1
- package/package.json +1 -1
- package/q5.d.ts +48 -2
- package/q5.js +693 -390
- 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', () => {
|
|
@@ -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 =
|
|
4930
|
-
colorStackIndex =
|
|
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 =
|
|
5170
|
-
fillIdx =
|
|
5171
|
-
tintIdx =
|
|
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 =
|
|
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
|
|
5258
|
-
|
|
5259
|
-
|
|
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
|
-
|
|
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 ==
|
|
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 =
|
|
5830
|
-
colorStackIndex =
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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.');
|