q5 2.2.0 → 2.2.2
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/README.md +3 -3
- package/package.json +6 -6
- package/{q5-webgpu.js → q5-q2d.js} +213 -689
- package/q5-q2d.min.js +8 -0
- package/q5.js +702 -204
- package/q5.min.js +1 -1
- package/src/q5-color.js +21 -0
- package/src/q5-core.js +1 -1
- package/src/q5-math.js +15 -27
- package/src/q5-webgpu-canvas.js +37 -15
- package/src/q5-webgpu-drawing.js +18 -36
- package/src/readme.md +24 -16
- package/q5-webgpu.min.js +0 -8
package/q5.js
CHANGED
|
@@ -239,7 +239,7 @@ function Q5(scope, parent, renderer) {
|
|
|
239
239
|
raf($._draw);
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
-
if ((arguments.length && scope != 'namespace') || preloadDefined) {
|
|
242
|
+
if ((arguments.length && scope != 'namespace' && renderer != 'webgpu') || preloadDefined) {
|
|
243
243
|
$.preload();
|
|
244
244
|
_start();
|
|
245
245
|
} else {
|
|
@@ -1376,182 +1376,6 @@ Q5.POSTERIZE = 5;
|
|
|
1376
1376
|
Q5.DILATE = 6;
|
|
1377
1377
|
Q5.ERODE = 7;
|
|
1378
1378
|
Q5.BLUR = 8;
|
|
1379
|
-
/* software implementation of image filters */
|
|
1380
|
-
Q5.renderers.q2d.soft_filters = ($) => {
|
|
1381
|
-
let tmpBuf = null;
|
|
1382
|
-
|
|
1383
|
-
function ensureTmpBuf() {
|
|
1384
|
-
let l = $.canvas.width * $.canvas.height * 4;
|
|
1385
|
-
if (!tmpBuf || tmpBuf.length != l) {
|
|
1386
|
-
tmpBuf = new Uint8ClampedArray(l);
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
|
|
1390
|
-
function initSoftFilters() {
|
|
1391
|
-
$._filters = [];
|
|
1392
|
-
$._filters[Q5.THRESHOLD] = (data, thresh) => {
|
|
1393
|
-
if (thresh === undefined) thresh = 127.5;
|
|
1394
|
-
else thresh *= 255;
|
|
1395
|
-
for (let i = 0; i < data.length; i += 4) {
|
|
1396
|
-
const gray = 0.2126 * data[i] + 0.7152 * data[i + 1] + 0.0722 * data[i + 2];
|
|
1397
|
-
data[i] = data[i + 1] = data[i + 2] = gray >= thresh ? 255 : 0;
|
|
1398
|
-
}
|
|
1399
|
-
};
|
|
1400
|
-
$._filters[Q5.GRAY] = (data) => {
|
|
1401
|
-
for (let i = 0; i < data.length; i += 4) {
|
|
1402
|
-
const gray = 0.2126 * data[i] + 0.7152 * data[i + 1] + 0.0722 * data[i + 2];
|
|
1403
|
-
data[i] = data[i + 1] = data[i + 2] = gray;
|
|
1404
|
-
}
|
|
1405
|
-
};
|
|
1406
|
-
$._filters[Q5.OPAQUE] = (data) => {
|
|
1407
|
-
for (let i = 0; i < data.length; i += 4) {
|
|
1408
|
-
data[i + 3] = 255;
|
|
1409
|
-
}
|
|
1410
|
-
};
|
|
1411
|
-
$._filters[Q5.INVERT] = (data) => {
|
|
1412
|
-
for (let i = 0; i < data.length; i += 4) {
|
|
1413
|
-
data[i] = 255 - data[i];
|
|
1414
|
-
data[i + 1] = 255 - data[i + 1];
|
|
1415
|
-
data[i + 2] = 255 - data[i + 2];
|
|
1416
|
-
}
|
|
1417
|
-
};
|
|
1418
|
-
$._filters[Q5.POSTERIZE] = (data, lvl = 4) => {
|
|
1419
|
-
let lvl1 = lvl - 1;
|
|
1420
|
-
for (let i = 0; i < data.length; i += 4) {
|
|
1421
|
-
data[i] = (((data[i] * lvl) >> 8) * 255) / lvl1;
|
|
1422
|
-
data[i + 1] = (((data[i + 1] * lvl) >> 8) * 255) / lvl1;
|
|
1423
|
-
data[i + 2] = (((data[i + 2] * lvl) >> 8) * 255) / lvl1;
|
|
1424
|
-
}
|
|
1425
|
-
};
|
|
1426
|
-
$._filters[Q5.DILATE] = (data) => {
|
|
1427
|
-
ensureTmpBuf();
|
|
1428
|
-
tmpBuf.set(data);
|
|
1429
|
-
let [w, h] = [$.canvas.width, $.canvas.height];
|
|
1430
|
-
for (let i = 0; i < h; i++) {
|
|
1431
|
-
for (let j = 0; j < w; j++) {
|
|
1432
|
-
let l = 4 * Math.max(j - 1, 0);
|
|
1433
|
-
let r = 4 * Math.min(j + 1, w - 1);
|
|
1434
|
-
let t = 4 * Math.max(i - 1, 0) * w;
|
|
1435
|
-
let b = 4 * Math.min(i + 1, h - 1) * w;
|
|
1436
|
-
let oi = 4 * i * w;
|
|
1437
|
-
let oj = 4 * j;
|
|
1438
|
-
for (let k = 0; k < 4; k++) {
|
|
1439
|
-
let kt = k + t;
|
|
1440
|
-
let kb = k + b;
|
|
1441
|
-
let ko = k + oi;
|
|
1442
|
-
data[oi + oj + k] = Math.max(
|
|
1443
|
-
/*tmpBuf[kt+l],*/ tmpBuf[kt + oj] /*tmpBuf[kt+r],*/,
|
|
1444
|
-
tmpBuf[ko + l],
|
|
1445
|
-
tmpBuf[ko + oj],
|
|
1446
|
-
tmpBuf[ko + r],
|
|
1447
|
-
/*tmpBuf[kb+l],*/ tmpBuf[kb + oj] /*tmpBuf[kb+r],*/
|
|
1448
|
-
);
|
|
1449
|
-
}
|
|
1450
|
-
}
|
|
1451
|
-
}
|
|
1452
|
-
};
|
|
1453
|
-
$._filters[Q5.ERODE] = (data) => {
|
|
1454
|
-
ensureTmpBuf();
|
|
1455
|
-
tmpBuf.set(data);
|
|
1456
|
-
let [w, h] = [$.canvas.width, $.canvas.height];
|
|
1457
|
-
for (let i = 0; i < h; i++) {
|
|
1458
|
-
for (let j = 0; j < w; j++) {
|
|
1459
|
-
let l = 4 * Math.max(j - 1, 0);
|
|
1460
|
-
let r = 4 * Math.min(j + 1, w - 1);
|
|
1461
|
-
let t = 4 * Math.max(i - 1, 0) * w;
|
|
1462
|
-
let b = 4 * Math.min(i + 1, h - 1) * w;
|
|
1463
|
-
let oi = 4 * i * w;
|
|
1464
|
-
let oj = 4 * j;
|
|
1465
|
-
for (let k = 0; k < 4; k++) {
|
|
1466
|
-
let kt = k + t;
|
|
1467
|
-
let kb = k + b;
|
|
1468
|
-
let ko = k + oi;
|
|
1469
|
-
data[oi + oj + k] = Math.min(
|
|
1470
|
-
/*tmpBuf[kt+l],*/ tmpBuf[kt + oj] /*tmpBuf[kt+r],*/,
|
|
1471
|
-
tmpBuf[ko + l],
|
|
1472
|
-
tmpBuf[ko + oj],
|
|
1473
|
-
tmpBuf[ko + r],
|
|
1474
|
-
/*tmpBuf[kb+l],*/ tmpBuf[kb + oj] /*tmpBuf[kb+r],*/
|
|
1475
|
-
);
|
|
1476
|
-
}
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
};
|
|
1480
|
-
$._filters[Q5.BLUR] = (data, rad) => {
|
|
1481
|
-
rad = rad || 1;
|
|
1482
|
-
rad = Math.floor(rad * $._pixelDensity);
|
|
1483
|
-
ensureTmpBuf();
|
|
1484
|
-
tmpBuf.set(data);
|
|
1485
|
-
|
|
1486
|
-
let ksize = rad * 2 + 1;
|
|
1487
|
-
|
|
1488
|
-
function gauss1d(ksize) {
|
|
1489
|
-
let im = new Float32Array(ksize);
|
|
1490
|
-
let sigma = 0.3 * rad + 0.8;
|
|
1491
|
-
let ss2 = sigma * sigma * 2;
|
|
1492
|
-
for (let i = 0; i < ksize; i++) {
|
|
1493
|
-
let x = i - ksize / 2;
|
|
1494
|
-
let z = Math.exp(-(x * x) / ss2) / (2.5066282746 * sigma);
|
|
1495
|
-
im[i] = z;
|
|
1496
|
-
}
|
|
1497
|
-
return im;
|
|
1498
|
-
}
|
|
1499
|
-
|
|
1500
|
-
let kern = gauss1d(ksize);
|
|
1501
|
-
let [w, h] = [$.canvas.width, $.canvas.height];
|
|
1502
|
-
for (let i = 0; i < h; i++) {
|
|
1503
|
-
for (let j = 0; j < w; j++) {
|
|
1504
|
-
let s0 = 0,
|
|
1505
|
-
s1 = 0,
|
|
1506
|
-
s2 = 0,
|
|
1507
|
-
s3 = 0;
|
|
1508
|
-
for (let k = 0; k < ksize; k++) {
|
|
1509
|
-
let jk = Math.min(Math.max(j - rad + k, 0), w - 1);
|
|
1510
|
-
let idx = 4 * (i * w + jk);
|
|
1511
|
-
s0 += tmpBuf[idx] * kern[k];
|
|
1512
|
-
s1 += tmpBuf[idx + 1] * kern[k];
|
|
1513
|
-
s2 += tmpBuf[idx + 2] * kern[k];
|
|
1514
|
-
s3 += tmpBuf[idx + 3] * kern[k];
|
|
1515
|
-
}
|
|
1516
|
-
let idx = 4 * (i * w + j);
|
|
1517
|
-
data[idx] = s0;
|
|
1518
|
-
data[idx + 1] = s1;
|
|
1519
|
-
data[idx + 2] = s2;
|
|
1520
|
-
data[idx + 3] = s3;
|
|
1521
|
-
}
|
|
1522
|
-
}
|
|
1523
|
-
tmpBuf.set(data);
|
|
1524
|
-
for (let i = 0; i < h; i++) {
|
|
1525
|
-
for (let j = 0; j < w; j++) {
|
|
1526
|
-
let s0 = 0,
|
|
1527
|
-
s1 = 0,
|
|
1528
|
-
s2 = 0,
|
|
1529
|
-
s3 = 0;
|
|
1530
|
-
for (let k = 0; k < ksize; k++) {
|
|
1531
|
-
let ik = Math.min(Math.max(i - rad + k, 0), h - 1);
|
|
1532
|
-
let idx = 4 * (ik * w + j);
|
|
1533
|
-
s0 += tmpBuf[idx] * kern[k];
|
|
1534
|
-
s1 += tmpBuf[idx + 1] * kern[k];
|
|
1535
|
-
s2 += tmpBuf[idx + 2] * kern[k];
|
|
1536
|
-
s3 += tmpBuf[idx + 3] * kern[k];
|
|
1537
|
-
}
|
|
1538
|
-
let idx = 4 * (i * w + j);
|
|
1539
|
-
data[idx] = s0;
|
|
1540
|
-
data[idx + 1] = s1;
|
|
1541
|
-
data[idx + 2] = s2;
|
|
1542
|
-
data[idx + 3] = s3;
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
};
|
|
1546
|
-
}
|
|
1547
|
-
|
|
1548
|
-
$._softFilter = (typ, x) => {
|
|
1549
|
-
if (!$._filters) initSoftFilters();
|
|
1550
|
-
let imgData = $.ctx.getImageData(0, 0, $.canvas.width, $.canvas.height);
|
|
1551
|
-
$._filters[typ](imgData.data, x);
|
|
1552
|
-
$.ctx.putImageData(imgData, 0, 0);
|
|
1553
|
-
};
|
|
1554
|
-
};
|
|
1555
1379
|
Q5.renderers.q2d.text = ($, q) => {
|
|
1556
1380
|
$.NORMAL = 'normal';
|
|
1557
1381
|
$.ITALIC = 'italic';
|
|
@@ -1936,8 +1760,29 @@ Q5.modules.color = ($, q) => {
|
|
|
1936
1760
|
$.blue = (c) => c.b;
|
|
1937
1761
|
$.alpha = (c) => c.a;
|
|
1938
1762
|
$.lightness = (c) => {
|
|
1763
|
+
if ($._colorMode == 'oklch') return c.l;
|
|
1939
1764
|
return ((0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b) * 100) / 255;
|
|
1940
1765
|
};
|
|
1766
|
+
$.hue = (c) => {
|
|
1767
|
+
if ($._colorMode == 'oklch') return c.h;
|
|
1768
|
+
let r = c.r;
|
|
1769
|
+
let g = c.g;
|
|
1770
|
+
let b = c.b;
|
|
1771
|
+
if ($._colorFormat == 255) {
|
|
1772
|
+
r /= 255;
|
|
1773
|
+
g /= 255;
|
|
1774
|
+
b /= 255;
|
|
1775
|
+
}
|
|
1776
|
+
let max = Math.max(r, g, b);
|
|
1777
|
+
let min = Math.min(r, g, b);
|
|
1778
|
+
let h;
|
|
1779
|
+
if (max == min) h = 0;
|
|
1780
|
+
else if (max == r) h = (60 * (g - b)) / (max - min);
|
|
1781
|
+
else if (max == g) h = (60 * (b - r)) / (max - min) + 120;
|
|
1782
|
+
else h = (60 * (r - g)) / (max - min) + 240;
|
|
1783
|
+
if (h < 0) h += 360;
|
|
1784
|
+
return h;
|
|
1785
|
+
};
|
|
1941
1786
|
|
|
1942
1787
|
$.lerpColor = (a, b, t) => {
|
|
1943
1788
|
if ($._colorMode == 'rgb') {
|
|
@@ -2392,33 +2237,21 @@ Q5.modules.math = ($, q) => {
|
|
|
2392
2237
|
$.norm = (value, start, stop) => $.map(value, start, stop, 0, 1);
|
|
2393
2238
|
$.sq = (x) => x * x;
|
|
2394
2239
|
$.fract = (x) => x - Math.floor(x);
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
return a;
|
|
2411
|
-
};
|
|
2412
|
-
$.acos = (x) => {
|
|
2413
|
-
let a = Math.acos(x);
|
|
2414
|
-
if ($._angleMode == 'degrees') a = $.degrees(a);
|
|
2415
|
-
return a;
|
|
2416
|
-
};
|
|
2417
|
-
$.atan = (x) => {
|
|
2418
|
-
let a = Math.atan(x);
|
|
2419
|
-
if ($._angleMode == 'degrees') a = $.degrees(a);
|
|
2420
|
-
return a;
|
|
2421
|
-
};
|
|
2240
|
+
|
|
2241
|
+
for (let fn of ['sin', 'cos', 'tan']) {
|
|
2242
|
+
$[fn] = (a) => {
|
|
2243
|
+
if ($._angleMode == 'degrees') a = $.radians(a);
|
|
2244
|
+
return Math[fn](a);
|
|
2245
|
+
};
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
for (let fn of ['asin', 'acos', 'atan']) {
|
|
2249
|
+
$[fn] = (x) => {
|
|
2250
|
+
let a = Math[fn](x);
|
|
2251
|
+
if ($._angleMode == 'degrees') a = $.degrees(a);
|
|
2252
|
+
return a;
|
|
2253
|
+
};
|
|
2254
|
+
}
|
|
2422
2255
|
$.atan2 = (y, x) => {
|
|
2423
2256
|
let a = Math.atan2(y, x);
|
|
2424
2257
|
if ($._angleMode == 'degrees') a = $.degrees(a);
|
|
@@ -3067,3 +2900,668 @@ Q5.Vector.sub = (v, u) => v.copy().sub(u);
|
|
|
3067
2900
|
for (let k of ['fromAngle', 'fromAngles', 'random2D', 'random3D']) {
|
|
3068
2901
|
Q5.Vector[k] = (u, v, t) => new Q5.Vector()[k](u, v, t);
|
|
3069
2902
|
}
|
|
2903
|
+
/**
|
|
2904
|
+
* q5-webgpu
|
|
2905
|
+
*
|
|
2906
|
+
* EXPERIMENTAL, for developer testing only!
|
|
2907
|
+
*/
|
|
2908
|
+
Q5.renderers.webgpu = {};
|
|
2909
|
+
|
|
2910
|
+
Q5.renderers.webgpu.canvas = ($, q) => {
|
|
2911
|
+
let c = $.canvas;
|
|
2912
|
+
|
|
2913
|
+
c.width = $.width = 500;
|
|
2914
|
+
c.height = $.height = 500;
|
|
2915
|
+
|
|
2916
|
+
if ($.colorMode) $.colorMode('rgb', 'float');
|
|
2917
|
+
|
|
2918
|
+
let pass, colorsStack, envBindGroup, transformBindGroup;
|
|
2919
|
+
|
|
2920
|
+
$._createCanvas = (w, h, opt) => {
|
|
2921
|
+
q.ctx = q.drawingContext = c.getContext('webgpu');
|
|
2922
|
+
|
|
2923
|
+
opt.format = navigator.gpu.getPreferredCanvasFormat();
|
|
2924
|
+
opt.device = Q5.device;
|
|
2925
|
+
|
|
2926
|
+
$.ctx.configure(opt);
|
|
2927
|
+
|
|
2928
|
+
$.pipelines = [];
|
|
2929
|
+
|
|
2930
|
+
// pipeline changes for each draw call
|
|
2931
|
+
$.pipelinesStack = [];
|
|
2932
|
+
|
|
2933
|
+
// vertices for each draw call
|
|
2934
|
+
$.verticesStack = [];
|
|
2935
|
+
|
|
2936
|
+
// number of vertices for each draw call
|
|
2937
|
+
$.drawStack = [];
|
|
2938
|
+
|
|
2939
|
+
// colors used for each draw call
|
|
2940
|
+
colorsStack = $.colorsStack = [1, 1, 1, 1];
|
|
2941
|
+
|
|
2942
|
+
// current color index, used to associate a vertex with a color
|
|
2943
|
+
$._colorIndex = 0;
|
|
2944
|
+
|
|
2945
|
+
let envLayout = Q5.device.createBindGroupLayout({
|
|
2946
|
+
entries: [
|
|
2947
|
+
{
|
|
2948
|
+
binding: 0,
|
|
2949
|
+
visibility: GPUShaderStage.VERTEX,
|
|
2950
|
+
buffer: {
|
|
2951
|
+
type: 'uniform',
|
|
2952
|
+
hasDynamicOffset: false
|
|
2953
|
+
}
|
|
2954
|
+
}
|
|
2955
|
+
]
|
|
2956
|
+
});
|
|
2957
|
+
|
|
2958
|
+
let transformLayout = Q5.device.createBindGroupLayout({
|
|
2959
|
+
entries: [
|
|
2960
|
+
{
|
|
2961
|
+
binding: 0,
|
|
2962
|
+
visibility: GPUShaderStage.VERTEX,
|
|
2963
|
+
buffer: {
|
|
2964
|
+
type: 'read-only-storage',
|
|
2965
|
+
hasDynamicOffset: false
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
]
|
|
2969
|
+
});
|
|
2970
|
+
|
|
2971
|
+
$.bindGroupLayouts = [envLayout, transformLayout];
|
|
2972
|
+
|
|
2973
|
+
const uniformBuffer = Q5.device.createBuffer({
|
|
2974
|
+
size: 8, // Size of two floats
|
|
2975
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
2976
|
+
});
|
|
2977
|
+
|
|
2978
|
+
Q5.device.queue.writeBuffer(uniformBuffer, 0, new Float32Array([$.canvas.hw, $.canvas.hh]));
|
|
2979
|
+
|
|
2980
|
+
envBindGroup = Q5.device.createBindGroup({
|
|
2981
|
+
layout: envLayout,
|
|
2982
|
+
entries: [
|
|
2983
|
+
{
|
|
2984
|
+
binding: 0,
|
|
2985
|
+
resource: {
|
|
2986
|
+
buffer: uniformBuffer
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
]
|
|
2990
|
+
});
|
|
2991
|
+
};
|
|
2992
|
+
|
|
2993
|
+
$._resizeCanvas = (w, h) => {
|
|
2994
|
+
$._setCanvasSize(w, h);
|
|
2995
|
+
};
|
|
2996
|
+
|
|
2997
|
+
$.resetMatrix = () => {
|
|
2998
|
+
// Initialize the transformation matrix as 4x4 identity matrix
|
|
2999
|
+
|
|
3000
|
+
// prettier-ignore
|
|
3001
|
+
$._matrix = [
|
|
3002
|
+
1, 0, 0, 0,
|
|
3003
|
+
0, 1, 0, 0,
|
|
3004
|
+
0, 0, 1, 0,
|
|
3005
|
+
0, 0, 0, 1
|
|
3006
|
+
];
|
|
3007
|
+
$._transformIndex = 0;
|
|
3008
|
+
};
|
|
3009
|
+
$.resetMatrix();
|
|
3010
|
+
|
|
3011
|
+
// Boolean to track if the matrix has been modified
|
|
3012
|
+
$._matrixDirty = false;
|
|
3013
|
+
|
|
3014
|
+
// Array to store transformation matrices for the render pass
|
|
3015
|
+
$.transformStates = [$._matrix.slice()];
|
|
3016
|
+
|
|
3017
|
+
// Stack to keep track of transformation matrix indexes
|
|
3018
|
+
$._transformIndexStack = [];
|
|
3019
|
+
|
|
3020
|
+
$.push = () => {
|
|
3021
|
+
// Push the current matrix index onto the stack
|
|
3022
|
+
$._transformIndexStack.push($._transformIndex);
|
|
3023
|
+
};
|
|
3024
|
+
|
|
3025
|
+
$.pop = () => {
|
|
3026
|
+
if ($._transformIndexStack.length > 0) {
|
|
3027
|
+
// Pop the last matrix index from the stack and set it as the current matrix index
|
|
3028
|
+
let idx = $._transformIndexStack.pop();
|
|
3029
|
+
$._matrix = $.transformStates[idx].slice();
|
|
3030
|
+
$._transformIndex = idx;
|
|
3031
|
+
} else {
|
|
3032
|
+
console.warn('Matrix index stack is empty!');
|
|
3033
|
+
}
|
|
3034
|
+
};
|
|
3035
|
+
|
|
3036
|
+
$.translate = (x, y, z) => {
|
|
3037
|
+
if (!x && !y && !z) return;
|
|
3038
|
+
// Update the translation values
|
|
3039
|
+
$._matrix[3] += x;
|
|
3040
|
+
$._matrix[7] += y;
|
|
3041
|
+
$._matrix[11] += z || 0;
|
|
3042
|
+
$._matrixDirty = true;
|
|
3043
|
+
};
|
|
3044
|
+
|
|
3045
|
+
$.rotate = (r) => {
|
|
3046
|
+
if (!r) return;
|
|
3047
|
+
if ($._angleMode == 'degrees') r = $.radians(r);
|
|
3048
|
+
|
|
3049
|
+
let cosR = Math.cos(r);
|
|
3050
|
+
let sinR = Math.sin(r);
|
|
3051
|
+
|
|
3052
|
+
let m0 = $._matrix[0],
|
|
3053
|
+
m1 = $._matrix[1],
|
|
3054
|
+
m4 = $._matrix[4],
|
|
3055
|
+
m5 = $._matrix[5];
|
|
3056
|
+
if (!m0 && !m1 && !m4 && !m5) {
|
|
3057
|
+
$._matrix[0] = cosR;
|
|
3058
|
+
$._matrix[1] = sinR;
|
|
3059
|
+
$._matrix[4] = -sinR;
|
|
3060
|
+
$._matrix[5] = cosR;
|
|
3061
|
+
} else {
|
|
3062
|
+
$._matrix[0] = m0 * cosR + m4 * sinR;
|
|
3063
|
+
$._matrix[1] = m1 * cosR + m5 * sinR;
|
|
3064
|
+
$._matrix[4] = m0 * -sinR + m4 * cosR;
|
|
3065
|
+
$._matrix[5] = m1 * -sinR + m5 * cosR;
|
|
3066
|
+
}
|
|
3067
|
+
|
|
3068
|
+
$._matrixDirty = true;
|
|
3069
|
+
};
|
|
3070
|
+
|
|
3071
|
+
$.scale = (sx = 1, sy, sz = 1) => {
|
|
3072
|
+
sy ??= sx;
|
|
3073
|
+
|
|
3074
|
+
$._matrix[0] *= sx;
|
|
3075
|
+
$._matrix[5] *= sy;
|
|
3076
|
+
$._matrix[10] *= sz;
|
|
3077
|
+
|
|
3078
|
+
$._matrixDirty = true;
|
|
3079
|
+
};
|
|
3080
|
+
|
|
3081
|
+
// Function to save the current matrix state if dirty
|
|
3082
|
+
$._saveMatrix = () => {
|
|
3083
|
+
$.transformStates.push($._matrix.slice());
|
|
3084
|
+
$._transformIndex = $.transformStates.length - 1;
|
|
3085
|
+
$._matrixDirty = false;
|
|
3086
|
+
};
|
|
3087
|
+
|
|
3088
|
+
$._beginRender = () => {
|
|
3089
|
+
$.encoder = Q5.device.createCommandEncoder();
|
|
3090
|
+
|
|
3091
|
+
pass = q.pass = $.encoder.beginRenderPass({
|
|
3092
|
+
colorAttachments: [
|
|
3093
|
+
{
|
|
3094
|
+
view: ctx.getCurrentTexture().createView(),
|
|
3095
|
+
loadOp: 'clear',
|
|
3096
|
+
storeOp: 'store'
|
|
3097
|
+
}
|
|
3098
|
+
]
|
|
3099
|
+
});
|
|
3100
|
+
};
|
|
3101
|
+
|
|
3102
|
+
$._render = () => {
|
|
3103
|
+
pass.setBindGroup(0, envBindGroup);
|
|
3104
|
+
|
|
3105
|
+
if (transformStates.length > 1 || !transformBindGroup) {
|
|
3106
|
+
const transformBuffer = Q5.device.createBuffer({
|
|
3107
|
+
size: transformStates.length * 64, // Size of 16 floats
|
|
3108
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
3109
|
+
});
|
|
3110
|
+
|
|
3111
|
+
Q5.device.queue.writeBuffer(transformBuffer, 0, new Float32Array(transformStates.flat()));
|
|
3112
|
+
|
|
3113
|
+
transformBindGroup = Q5.device.createBindGroup({
|
|
3114
|
+
layout: $.bindGroupLayouts[1],
|
|
3115
|
+
entries: [
|
|
3116
|
+
{
|
|
3117
|
+
binding: 0,
|
|
3118
|
+
resource: {
|
|
3119
|
+
buffer: transformBuffer
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
3122
|
+
]
|
|
3123
|
+
});
|
|
3124
|
+
}
|
|
3125
|
+
|
|
3126
|
+
pass.setBindGroup(1, transformBindGroup);
|
|
3127
|
+
|
|
3128
|
+
// run pre-render methods
|
|
3129
|
+
for (let m of $._hooks.preRender) m();
|
|
3130
|
+
|
|
3131
|
+
// local variables used for performance
|
|
3132
|
+
let drawStack = $.drawStack;
|
|
3133
|
+
|
|
3134
|
+
let drawVertOffset = 0;
|
|
3135
|
+
let curPipelineIndex = -1;
|
|
3136
|
+
|
|
3137
|
+
for (let i = 0; i < drawStack.length; i += 2) {
|
|
3138
|
+
if (curPipelineIndex != drawStack[i]) {
|
|
3139
|
+
curPipelineIndex = drawStack[i];
|
|
3140
|
+
pass.setPipeline($.pipelines[curPipelineIndex]);
|
|
3141
|
+
}
|
|
3142
|
+
|
|
3143
|
+
let vertCount = drawStack[i + 1];
|
|
3144
|
+
pass.draw(vertCount, 1, drawVertOffset, 0);
|
|
3145
|
+
drawVertOffset += vertCount;
|
|
3146
|
+
}
|
|
3147
|
+
};
|
|
3148
|
+
|
|
3149
|
+
$._finishRender = () => {
|
|
3150
|
+
pass.end();
|
|
3151
|
+
const commandBuffer = $.encoder.finish();
|
|
3152
|
+
Q5.device.queue.submit([commandBuffer]);
|
|
3153
|
+
q.pass = $.encoder = null;
|
|
3154
|
+
|
|
3155
|
+
// clear the stacks for the next frame
|
|
3156
|
+
$.verticesStack.length = 0;
|
|
3157
|
+
$.drawStack.length = 0;
|
|
3158
|
+
$.colorsStack.length = 4;
|
|
3159
|
+
$.pipelinesStack.length = 0;
|
|
3160
|
+
$._colorIndex = 0;
|
|
3161
|
+
rotation = 0;
|
|
3162
|
+
$.resetMatrix();
|
|
3163
|
+
$._matrixDirty = false;
|
|
3164
|
+
$.transformStates.length = 1;
|
|
3165
|
+
$._transformIndexStack.length = 0;
|
|
3166
|
+
};
|
|
3167
|
+
|
|
3168
|
+
function addColor(r, g, b, a = 1) {
|
|
3169
|
+
if (typeof r == 'string') r = Q5.color(r);
|
|
3170
|
+
// grayscale mode `fill(1, 0.5)`
|
|
3171
|
+
if (b == undefined) {
|
|
3172
|
+
a = g;
|
|
3173
|
+
g = b = r;
|
|
3174
|
+
}
|
|
3175
|
+
if (r._q5Color) colorsStack.push(...r.levels);
|
|
3176
|
+
else colorsStack.push(r, g, b, a);
|
|
3177
|
+
$._colorIndex++;
|
|
3178
|
+
}
|
|
3179
|
+
|
|
3180
|
+
$.fill = function () {
|
|
3181
|
+
addColor(...arguments);
|
|
3182
|
+
$._doFill = true;
|
|
3183
|
+
$._fillIndex = $._colorIndex;
|
|
3184
|
+
};
|
|
3185
|
+
$.stroke = function () {
|
|
3186
|
+
addColor(...arguments);
|
|
3187
|
+
$._doStroke = true;
|
|
3188
|
+
$._fillIndex = $._colorIndex;
|
|
3189
|
+
};
|
|
3190
|
+
|
|
3191
|
+
$.noFill = () => ($._doFill = false);
|
|
3192
|
+
$.noStroke = () => ($._doStroke = false);
|
|
3193
|
+
|
|
3194
|
+
$._strokeWeight = 1;
|
|
3195
|
+
$.strokeWeight = (v) => ($._strokeWeight = v);
|
|
3196
|
+
|
|
3197
|
+
$.clear = () => {};
|
|
3198
|
+
};
|
|
3199
|
+
|
|
3200
|
+
Q5.webgpu = async function (scope, parent) {
|
|
3201
|
+
if (!scope || scope == 'global') Q5._hasGlobal = true;
|
|
3202
|
+
if (!navigator.gpu) {
|
|
3203
|
+
console.error('q5 WebGPU not supported on this browser!');
|
|
3204
|
+
let q = new Q5(scope, parent);
|
|
3205
|
+
q.colorMode('rgb', 1);
|
|
3206
|
+
q._beginRender = () => q.translate(q.canvas.hw, q.canvas.hh);
|
|
3207
|
+
return q;
|
|
3208
|
+
}
|
|
3209
|
+
let adapter = await navigator.gpu.requestAdapter();
|
|
3210
|
+
if (!adapter) throw new Error('No appropriate GPUAdapter found.');
|
|
3211
|
+
Q5.device = await adapter.requestDevice();
|
|
3212
|
+
|
|
3213
|
+
return new Q5(scope, parent, 'webgpu');
|
|
3214
|
+
};
|
|
3215
|
+
Q5.renderers.webgpu.drawing = ($, q) => {
|
|
3216
|
+
$.CLOSE = 1;
|
|
3217
|
+
|
|
3218
|
+
let verticesStack, drawStack, colorsStack;
|
|
3219
|
+
|
|
3220
|
+
$._hooks.postCanvas.push(() => {
|
|
3221
|
+
let colorsLayout = Q5.device.createBindGroupLayout({
|
|
3222
|
+
entries: [
|
|
3223
|
+
{
|
|
3224
|
+
binding: 0,
|
|
3225
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
3226
|
+
buffer: {
|
|
3227
|
+
type: 'read-only-storage',
|
|
3228
|
+
hasDynamicOffset: false
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
]
|
|
3232
|
+
});
|
|
3233
|
+
|
|
3234
|
+
$.bindGroupLayouts.push(colorsLayout);
|
|
3235
|
+
|
|
3236
|
+
verticesStack = $.verticesStack;
|
|
3237
|
+
drawStack = $.drawStack;
|
|
3238
|
+
colorsStack = $.colorsStack;
|
|
3239
|
+
|
|
3240
|
+
let vertexBufferLayout = {
|
|
3241
|
+
arrayStride: 16, // 2 coordinates + 1 color index + 1 transform index * 4 bytes each
|
|
3242
|
+
attributes: [
|
|
3243
|
+
{ format: 'float32x2', offset: 0, shaderLocation: 0 }, // position
|
|
3244
|
+
{ format: 'float32', offset: 8, shaderLocation: 1 }, // colorIndex
|
|
3245
|
+
{ format: 'float32', offset: 12, shaderLocation: 2 } // transformIndex
|
|
3246
|
+
]
|
|
3247
|
+
};
|
|
3248
|
+
|
|
3249
|
+
let vertexShader = Q5.device.createShaderModule({
|
|
3250
|
+
code: `
|
|
3251
|
+
struct VertexOutput {
|
|
3252
|
+
@builtin(position) position: vec4<f32>,
|
|
3253
|
+
@location(1) colorIndex: f32
|
|
3254
|
+
};
|
|
3255
|
+
|
|
3256
|
+
struct Uniforms {
|
|
3257
|
+
halfWidth: f32,
|
|
3258
|
+
halfHeight: f32
|
|
3259
|
+
};
|
|
3260
|
+
|
|
3261
|
+
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
|
3262
|
+
@group(1) @binding(0) var<storage, read> transforms: array<mat4x4<f32>>;
|
|
3263
|
+
|
|
3264
|
+
@vertex
|
|
3265
|
+
fn vertexMain(@location(0) pos: vec2<f32>, @location(1) colorIndex: f32, @location(2) transformIndex: f32) -> VertexOutput {
|
|
3266
|
+
var vert = vec4<f32>(pos, 0.0, 1.0);
|
|
3267
|
+
vert *= transforms[i32(transformIndex)];
|
|
3268
|
+
vert.x /= uniforms.halfWidth;
|
|
3269
|
+
vert.y /= uniforms.halfHeight;
|
|
3270
|
+
|
|
3271
|
+
var output: VertexOutput;
|
|
3272
|
+
output.position = vert;
|
|
3273
|
+
output.colorIndex = colorIndex;
|
|
3274
|
+
return output;
|
|
3275
|
+
}
|
|
3276
|
+
`
|
|
3277
|
+
});
|
|
3278
|
+
|
|
3279
|
+
let fragmentShader = Q5.device.createShaderModule({
|
|
3280
|
+
code: `
|
|
3281
|
+
@group(2) @binding(0) var<storage, read> uColors : array<vec4<f32>>;
|
|
3282
|
+
|
|
3283
|
+
@fragment
|
|
3284
|
+
fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
3285
|
+
let index = u32(colorIndex);
|
|
3286
|
+
return mix(uColors[index], uColors[index + 1u], fract(colorIndex));
|
|
3287
|
+
}
|
|
3288
|
+
`
|
|
3289
|
+
});
|
|
3290
|
+
|
|
3291
|
+
let pipelineLayout = Q5.device.createPipelineLayout({
|
|
3292
|
+
bindGroupLayouts: $.bindGroupLayouts
|
|
3293
|
+
});
|
|
3294
|
+
|
|
3295
|
+
$._createPipeline = (blendConfig) => {
|
|
3296
|
+
return Q5.device.createRenderPipeline({
|
|
3297
|
+
layout: pipelineLayout,
|
|
3298
|
+
vertex: {
|
|
3299
|
+
module: vertexShader,
|
|
3300
|
+
entryPoint: 'vertexMain',
|
|
3301
|
+
buffers: [vertexBufferLayout]
|
|
3302
|
+
},
|
|
3303
|
+
fragment: {
|
|
3304
|
+
module: fragmentShader,
|
|
3305
|
+
entryPoint: 'fragmentMain',
|
|
3306
|
+
targets: [{ format: 'bgra8unorm', blend: blendConfig }]
|
|
3307
|
+
},
|
|
3308
|
+
primitive: { topology: 'triangle-list' }
|
|
3309
|
+
});
|
|
3310
|
+
};
|
|
3311
|
+
|
|
3312
|
+
$.pipelines[0] = $._createPipeline(blendConfigs.normal);
|
|
3313
|
+
});
|
|
3314
|
+
|
|
3315
|
+
// prettier-ignore
|
|
3316
|
+
let blendFactors = [
|
|
3317
|
+
'zero', // 0
|
|
3318
|
+
'one', // 1
|
|
3319
|
+
'src-alpha', // 2
|
|
3320
|
+
'one-minus-src-alpha', // 3
|
|
3321
|
+
'dst', // 4
|
|
3322
|
+
'dst-alpha', // 5
|
|
3323
|
+
'one-minus-dst-alpha', // 6
|
|
3324
|
+
'one-minus-src' // 7
|
|
3325
|
+
];
|
|
3326
|
+
let blendOps = [
|
|
3327
|
+
'add', // 0
|
|
3328
|
+
'subtract', // 1
|
|
3329
|
+
'reverse-subtract', // 2
|
|
3330
|
+
'min', // 3
|
|
3331
|
+
'max' // 4
|
|
3332
|
+
];
|
|
3333
|
+
|
|
3334
|
+
const blendModes = {
|
|
3335
|
+
normal: [2, 3, 0, 2, 3, 0],
|
|
3336
|
+
lighter: [2, 1, 0, 2, 1, 0],
|
|
3337
|
+
subtract: [2, 1, 2, 2, 1, 2],
|
|
3338
|
+
multiply: [4, 0, 0, 5, 0, 0],
|
|
3339
|
+
screen: [1, 3, 0, 1, 3, 0],
|
|
3340
|
+
darken: [1, 3, 3, 1, 3, 3],
|
|
3341
|
+
lighten: [1, 3, 4, 1, 3, 4],
|
|
3342
|
+
overlay: [2, 3, 0, 2, 3, 0],
|
|
3343
|
+
hard_light: [2, 3, 0, 2, 3, 0],
|
|
3344
|
+
soft_light: [2, 3, 0, 2, 3, 0],
|
|
3345
|
+
difference: [2, 3, 2, 2, 3, 2],
|
|
3346
|
+
exclusion: [2, 3, 0, 2, 3, 0],
|
|
3347
|
+
color_dodge: [1, 7, 0, 1, 7, 0],
|
|
3348
|
+
color_burn: [6, 1, 0, 6, 1, 0],
|
|
3349
|
+
linear_dodge: [2, 1, 0, 2, 1, 0],
|
|
3350
|
+
linear_burn: [2, 7, 1, 2, 7, 1],
|
|
3351
|
+
vivid_light: [2, 7, 0, 2, 7, 0],
|
|
3352
|
+
pin_light: [2, 7, 0, 2, 7, 0],
|
|
3353
|
+
hard_mix: [2, 7, 0, 2, 7, 0]
|
|
3354
|
+
};
|
|
3355
|
+
|
|
3356
|
+
$.blendConfigs = {};
|
|
3357
|
+
|
|
3358
|
+
Object.entries(blendModes).forEach(([name, mode]) => {
|
|
3359
|
+
$.blendConfigs[name] = {
|
|
3360
|
+
color: {
|
|
3361
|
+
srcFactor: blendFactors[mode[0]],
|
|
3362
|
+
dstFactor: blendFactors[mode[1]],
|
|
3363
|
+
operation: blendOps[mode[2]]
|
|
3364
|
+
},
|
|
3365
|
+
alpha: {
|
|
3366
|
+
srcFactor: blendFactors[mode[3]],
|
|
3367
|
+
dstFactor: blendFactors[mode[4]],
|
|
3368
|
+
operation: blendOps[mode[5]]
|
|
3369
|
+
}
|
|
3370
|
+
};
|
|
3371
|
+
});
|
|
3372
|
+
|
|
3373
|
+
$._blendMode = 'normal';
|
|
3374
|
+
$.blendMode = (mode) => {
|
|
3375
|
+
if (mode == $._blendMode) return;
|
|
3376
|
+
if (mode == 'source-over') mode = 'normal';
|
|
3377
|
+
mode = mode.toLowerCase().replace(/[ -]/g, '_');
|
|
3378
|
+
$._blendMode = mode;
|
|
3379
|
+
$.pipelines[0] = $._createPipeline($.blendConfigs[mode]);
|
|
3380
|
+
};
|
|
3381
|
+
|
|
3382
|
+
let shapeVertices;
|
|
3383
|
+
|
|
3384
|
+
$.beginShape = () => {
|
|
3385
|
+
shapeVertices = [];
|
|
3386
|
+
};
|
|
3387
|
+
|
|
3388
|
+
$.vertex = (x, y) => {
|
|
3389
|
+
if ($._matrixDirty) $._saveMatrix();
|
|
3390
|
+
shapeVertices.push(x, -y, $._colorIndex, $._transformIndex);
|
|
3391
|
+
};
|
|
3392
|
+
|
|
3393
|
+
$.endShape = (close) => {
|
|
3394
|
+
let v = shapeVertices;
|
|
3395
|
+
if (v.length < 12) {
|
|
3396
|
+
throw new Error('A shape must have at least 3 vertices.');
|
|
3397
|
+
}
|
|
3398
|
+
if (close) {
|
|
3399
|
+
// Close the shape by adding the first vertex at the end
|
|
3400
|
+
v.push(v[0], v[1], v[2], v[3]);
|
|
3401
|
+
}
|
|
3402
|
+
// Convert the shape to triangles
|
|
3403
|
+
let triangles = [];
|
|
3404
|
+
for (let i = 4; i < v.length; i += 4) {
|
|
3405
|
+
triangles.push(
|
|
3406
|
+
v[0], // First vertex
|
|
3407
|
+
v[1],
|
|
3408
|
+
v[2],
|
|
3409
|
+
v[3],
|
|
3410
|
+
v[i - 4], // Previous vertex
|
|
3411
|
+
v[i - 3],
|
|
3412
|
+
v[i - 2],
|
|
3413
|
+
v[i - 1],
|
|
3414
|
+
v[i], // Current vertex
|
|
3415
|
+
v[i + 1],
|
|
3416
|
+
v[i + 2],
|
|
3417
|
+
v[i + 3]
|
|
3418
|
+
);
|
|
3419
|
+
}
|
|
3420
|
+
|
|
3421
|
+
verticesStack.push(...triangles);
|
|
3422
|
+
drawStack.push(0, triangles.length / 4);
|
|
3423
|
+
shapeVertices = [];
|
|
3424
|
+
};
|
|
3425
|
+
|
|
3426
|
+
$.triangle = (x1, y1, x2, y2, x3, y3) => {
|
|
3427
|
+
$.beginShape();
|
|
3428
|
+
$.vertex(x1, y1);
|
|
3429
|
+
$.vertex(x2, y2);
|
|
3430
|
+
$.vertex(x3, y3);
|
|
3431
|
+
$.endShape(1);
|
|
3432
|
+
};
|
|
3433
|
+
|
|
3434
|
+
$.rect = (x, y, w, h) => {
|
|
3435
|
+
let hw = w / 2;
|
|
3436
|
+
let hh = h / 2;
|
|
3437
|
+
|
|
3438
|
+
let left = x - hw;
|
|
3439
|
+
let right = x + hw;
|
|
3440
|
+
let top = -(y - hh); // y is inverted in WebGPU
|
|
3441
|
+
let bottom = -(y + hh);
|
|
3442
|
+
|
|
3443
|
+
let ci = $._colorIndex;
|
|
3444
|
+
if ($._matrixDirty) $._saveMatrix();
|
|
3445
|
+
let ti = $._transformIndex;
|
|
3446
|
+
// two triangles make a rectangle
|
|
3447
|
+
// prettier-ignore
|
|
3448
|
+
verticesStack.push(
|
|
3449
|
+
left, top, ci, ti,
|
|
3450
|
+
right, top, ci, ti,
|
|
3451
|
+
left, bottom, ci, ti,
|
|
3452
|
+
right, top, ci, ti,
|
|
3453
|
+
left, bottom, ci, ti,
|
|
3454
|
+
right, bottom, ci, ti
|
|
3455
|
+
);
|
|
3456
|
+
drawStack.push(0, 6);
|
|
3457
|
+
};
|
|
3458
|
+
|
|
3459
|
+
$.point = (x, y) => {
|
|
3460
|
+
let sw = $._strokeWeight;
|
|
3461
|
+
if (sw == 1) $.rect(x, y, 1, 1);
|
|
3462
|
+
else $.ellipse(x, y, sw, sw);
|
|
3463
|
+
};
|
|
3464
|
+
|
|
3465
|
+
$.background = () => {};
|
|
3466
|
+
|
|
3467
|
+
/**
|
|
3468
|
+
* Derived from: ceil(Math.log(d) * 7) * 2 - ceil(28)
|
|
3469
|
+
* This lookup table is used for better performance.
|
|
3470
|
+
* @param {Number} d diameter of the circle
|
|
3471
|
+
* @returns n number of segments
|
|
3472
|
+
*/
|
|
3473
|
+
// prettier-ignore
|
|
3474
|
+
const getArcSegments = (d) =>
|
|
3475
|
+
d < 14 ? 8 :
|
|
3476
|
+
d < 16 ? 10 :
|
|
3477
|
+
d < 18 ? 12 :
|
|
3478
|
+
d < 20 ? 14 :
|
|
3479
|
+
d < 22 ? 16 :
|
|
3480
|
+
d < 24 ? 18 :
|
|
3481
|
+
d < 28 ? 20 :
|
|
3482
|
+
d < 34 ? 22 :
|
|
3483
|
+
d < 42 ? 24 :
|
|
3484
|
+
d < 48 ? 26 :
|
|
3485
|
+
d < 56 ? 28 :
|
|
3486
|
+
d < 64 ? 30 :
|
|
3487
|
+
d < 72 ? 32 :
|
|
3488
|
+
d < 84 ? 34 :
|
|
3489
|
+
d < 96 ? 36 :
|
|
3490
|
+
d < 98 ? 38 :
|
|
3491
|
+
d < 113 ? 40 :
|
|
3492
|
+
d < 149 ? 44 :
|
|
3493
|
+
d < 199 ? 48 :
|
|
3494
|
+
d < 261 ? 52 :
|
|
3495
|
+
d < 353 ? 56 :
|
|
3496
|
+
d < 461 ? 60 :
|
|
3497
|
+
d < 585 ? 64 :
|
|
3498
|
+
d < 1200 ? 70 :
|
|
3499
|
+
d < 1800 ? 80 :
|
|
3500
|
+
d < 2400 ? 90 :
|
|
3501
|
+
100;
|
|
3502
|
+
|
|
3503
|
+
$.ellipse = (x, y, w, h) => {
|
|
3504
|
+
const n = getArcSegments(w == h ? w : Math.max(w, h));
|
|
3505
|
+
|
|
3506
|
+
let a = Math.max(w, 1) / 2;
|
|
3507
|
+
let b = w == h ? a : Math.max(h, 1) / 2;
|
|
3508
|
+
|
|
3509
|
+
let t = 0; // theta
|
|
3510
|
+
const angleIncrement = $.TAU / n;
|
|
3511
|
+
const ci = $._colorIndex;
|
|
3512
|
+
if ($._matrixDirty) $._saveMatrix();
|
|
3513
|
+
const ti = $._transformIndex;
|
|
3514
|
+
let vx1, vy1, vx2, vy2;
|
|
3515
|
+
for (let i = 0; i <= n; i++) {
|
|
3516
|
+
vx1 = vx2;
|
|
3517
|
+
vy1 = vy2;
|
|
3518
|
+
vx2 = x + a * Math.cos(t);
|
|
3519
|
+
vy2 = y + b * Math.sin(t);
|
|
3520
|
+
t += angleIncrement;
|
|
3521
|
+
|
|
3522
|
+
if (i == 0) continue;
|
|
3523
|
+
|
|
3524
|
+
verticesStack.push(x, y, ci, ti, vx1, vy1, ci, ti, vx2, vy2, ci, ti);
|
|
3525
|
+
}
|
|
3526
|
+
|
|
3527
|
+
drawStack.push(0, n * 3);
|
|
3528
|
+
};
|
|
3529
|
+
|
|
3530
|
+
$.circle = (x, y, d) => $.ellipse(x, y, d, d);
|
|
3531
|
+
|
|
3532
|
+
$._hooks.preRender.push(() => {
|
|
3533
|
+
const vertexBuffer = Q5.device.createBuffer({
|
|
3534
|
+
size: verticesStack.length * 6,
|
|
3535
|
+
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
|
|
3536
|
+
});
|
|
3537
|
+
|
|
3538
|
+
Q5.device.queue.writeBuffer(vertexBuffer, 0, new Float32Array(verticesStack));
|
|
3539
|
+
$.pass.setVertexBuffer(0, vertexBuffer);
|
|
3540
|
+
|
|
3541
|
+
const colorsBuffer = Q5.device.createBuffer({
|
|
3542
|
+
size: colorsStack.length * 4,
|
|
3543
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
3544
|
+
});
|
|
3545
|
+
|
|
3546
|
+
Q5.device.queue.writeBuffer(colorsBuffer, 0, new Float32Array(colorsStack));
|
|
3547
|
+
|
|
3548
|
+
const colorsBindGroup = Q5.device.createBindGroup({
|
|
3549
|
+
layout: $.bindGroupLayouts[2],
|
|
3550
|
+
entries: [
|
|
3551
|
+
{
|
|
3552
|
+
binding: 0,
|
|
3553
|
+
resource: {
|
|
3554
|
+
buffer: colorsBuffer,
|
|
3555
|
+
offset: 0,
|
|
3556
|
+
size: colorsStack.length * 4
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3559
|
+
]
|
|
3560
|
+
});
|
|
3561
|
+
|
|
3562
|
+
// set the bind group once before rendering
|
|
3563
|
+
$.pass.setBindGroup(2, colorsBindGroup);
|
|
3564
|
+
});
|
|
3565
|
+
};
|
|
3566
|
+
Q5.renderers.webgpu.image = ($, q) => {};
|
|
3567
|
+
Q5.renderers.webgpu.text = ($, q) => {};
|