q5 2.1.0 → 2.2.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/README.md +3 -3
- package/package.json +3 -2
- package/q5-webgpu.js +406 -160
- package/q5-webgpu.min.js +2 -2
- package/q5.js +49 -62
- package/q5.min.js +2 -2
- package/src/q5-2d-canvas.js +16 -15
- package/src/q5-2d-drawing.js +1 -16
- package/src/q5-color.js +30 -29
- package/src/q5-core.js +2 -2
- package/src/q5-webgpu-canvas.js +187 -16
- package/src/q5-webgpu-drawing.js +170 -82
- package/src/readme.md +20 -20
package/q5-webgpu.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* q5.js
|
|
3
|
-
* @version 2.
|
|
3
|
+
* @version 2.2
|
|
4
4
|
* @author quinton-ashley, Tezumie, and LingDong-
|
|
5
5
|
* @license LGPL-3.0
|
|
6
6
|
* @class Q5
|
|
@@ -85,8 +85,8 @@ function Q5(scope, parent, renderer) {
|
|
|
85
85
|
$._frameRate = 1000 / $.deltaTime;
|
|
86
86
|
q.frameCount++;
|
|
87
87
|
let pre = performance.now();
|
|
88
|
-
if ($._beginRender) $._beginRender();
|
|
89
88
|
if ($.ctx) $.resetMatrix();
|
|
89
|
+
if ($._beginRender) $._beginRender();
|
|
90
90
|
for (let m of Q5.methods.pre) m.call($);
|
|
91
91
|
$.draw();
|
|
92
92
|
if ($._render) $._render();
|
|
@@ -524,11 +524,17 @@ Q5.renderers.q2d.canvas = ($, q) => {
|
|
|
524
524
|
$.ctx.drawImage(o, 0, 0, o.w, o.h);
|
|
525
525
|
};
|
|
526
526
|
|
|
527
|
-
$.
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
527
|
+
$.fill = function (c) {
|
|
528
|
+
$._doFill = true;
|
|
529
|
+
$._fillSet = true;
|
|
530
|
+
if (Q5.Color) {
|
|
531
|
+
if (!c._q5Color && typeof c != 'string') c = $.color(...arguments);
|
|
532
|
+
else if ($._namedColors[c]) c = $.color(...$._namedColors[c]);
|
|
533
|
+
if (c.a <= 0) return ($._doFill = false);
|
|
534
|
+
}
|
|
535
|
+
$.ctx.fillStyle = c.toString();
|
|
531
536
|
};
|
|
537
|
+
$.noFill = () => ($._doFill = false);
|
|
532
538
|
$.stroke = function (c) {
|
|
533
539
|
$._doStroke = true;
|
|
534
540
|
$._strokeSet = true;
|
|
@@ -539,18 +545,13 @@ Q5.renderers.q2d.canvas = ($, q) => {
|
|
|
539
545
|
}
|
|
540
546
|
$.ctx.strokeStyle = c.toString();
|
|
541
547
|
};
|
|
542
|
-
$.
|
|
543
|
-
|
|
544
|
-
$.
|
|
545
|
-
$.
|
|
546
|
-
if (Q5.Color) {
|
|
547
|
-
if (!c._q5Color && typeof c != 'string') c = $.color(...arguments);
|
|
548
|
-
else if ($._namedColors[c]) c = $.color(...$._namedColors[c]);
|
|
549
|
-
if (c.a <= 0) return ($._doFill = false);
|
|
550
|
-
}
|
|
551
|
-
$.ctx.fillStyle = c.toString();
|
|
548
|
+
$.strokeWeight = (n) => {
|
|
549
|
+
if (!n) $._doStroke = false;
|
|
550
|
+
if ($._da) n *= $._da;
|
|
551
|
+
$.ctx.lineWidth = n || 0.0001;
|
|
552
552
|
};
|
|
553
|
-
$.
|
|
553
|
+
$.noStroke = () => ($._doStroke = false);
|
|
554
|
+
$.clear = () => $.ctx.clearRect(0, 0, $.canvas.width, $.canvas.height);
|
|
554
555
|
|
|
555
556
|
// DRAWING MATRIX
|
|
556
557
|
|
|
@@ -715,10 +716,6 @@ Q5.renderers.q2d.drawing = ($) => {
|
|
|
715
716
|
|
|
716
717
|
// DRAWING
|
|
717
718
|
|
|
718
|
-
$.clear = () => {
|
|
719
|
-
$.ctx.clearRect(0, 0, $.canvas.width, $.canvas.height);
|
|
720
|
-
};
|
|
721
|
-
|
|
722
719
|
$.background = function (c) {
|
|
723
720
|
if (c.canvas) return $.image(c, 0, 0, $.width, $.height);
|
|
724
721
|
$.ctx.save();
|
|
@@ -876,18 +873,7 @@ Q5.renderers.q2d.drawing = ($) => {
|
|
|
876
873
|
bl *= $._da;
|
|
877
874
|
br *= $._da;
|
|
878
875
|
}
|
|
879
|
-
|
|
880
|
-
tl = Math.min(hh, tl);
|
|
881
|
-
tr = Math.min(hh, tr);
|
|
882
|
-
bl = Math.min(hh, bl);
|
|
883
|
-
br = Math.min(hh, br);
|
|
884
|
-
$.ctx.beginPath();
|
|
885
|
-
$.ctx.moveTo(x + tl, y);
|
|
886
|
-
$.ctx.arcTo(x + w, y, x + w, y + h, tr);
|
|
887
|
-
$.ctx.arcTo(x + w, y + h, x, y + h, br);
|
|
888
|
-
$.ctx.arcTo(x, y + h, x, y, bl);
|
|
889
|
-
$.ctx.arcTo(x, y, x + w, y, tl);
|
|
890
|
-
$.ctx.closePath();
|
|
876
|
+
$.ctx.roundRect(x, y, w, h, [tl, tr, br, bl]);
|
|
891
877
|
ink();
|
|
892
878
|
}
|
|
893
879
|
|
|
@@ -1676,12 +1662,12 @@ Q5.modules.color = ($, q) => {
|
|
|
1676
1662
|
$._colorMode = mode;
|
|
1677
1663
|
let srgb = $.canvas.colorSpace == 'srgb' || mode == 'srgb';
|
|
1678
1664
|
format ??= srgb ? 'integer' : 'float';
|
|
1679
|
-
$._colorFormat = format;
|
|
1665
|
+
$._colorFormat = format == 'float' || format == 1 ? 1 : 255;
|
|
1680
1666
|
if (mode == 'oklch') {
|
|
1681
1667
|
q.Color = Q5.ColorOKLCH;
|
|
1682
1668
|
} else {
|
|
1683
1669
|
let srgb = $.canvas.colorSpace == 'srgb';
|
|
1684
|
-
if ($._colorFormat ==
|
|
1670
|
+
if ($._colorFormat == 255) {
|
|
1685
1671
|
q.Color = srgb ? Q5.ColorRGBA_8 : Q5.ColorRGBA_P3_8;
|
|
1686
1672
|
} else {
|
|
1687
1673
|
q.Color = srgb ? Q5.ColorRGBA : Q5.ColorRGBA_P3;
|
|
@@ -1724,48 +1710,49 @@ Q5.modules.color = ($, q) => {
|
|
|
1724
1710
|
yellow: [255, 255, 0]
|
|
1725
1711
|
};
|
|
1726
1712
|
|
|
1727
|
-
$.color =
|
|
1713
|
+
$.color = (c0, c1, c2, c3) => {
|
|
1728
1714
|
let C = $.Color;
|
|
1729
1715
|
if (c0._q5Color) return new C(...c0.levels);
|
|
1730
|
-
|
|
1731
|
-
if (args.length == 1) {
|
|
1716
|
+
if (c1 == undefined) {
|
|
1732
1717
|
if (typeof c0 == 'string') {
|
|
1733
|
-
let r, g, b, a;
|
|
1734
1718
|
if (c0[0] == '#') {
|
|
1735
1719
|
if (c0.length <= 5) {
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1720
|
+
if (c0.length > 4) c3 = parseInt(c0[4] + c0[4], 16);
|
|
1721
|
+
c2 = parseInt(c0[3] + c0[3], 16);
|
|
1722
|
+
c1 = parseInt(c0[2] + c0[2], 16);
|
|
1723
|
+
c0 = parseInt(c0[1] + c0[1], 16);
|
|
1740
1724
|
} else {
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1725
|
+
if (c0.length > 7) c3 = parseInt(c0.slice(7, 9), 16);
|
|
1726
|
+
c2 = parseInt(c0.slice(5, 7), 16);
|
|
1727
|
+
c1 = parseInt(c0.slice(3, 5), 16);
|
|
1728
|
+
c0 = parseInt(c0.slice(1, 3), 16);
|
|
1745
1729
|
}
|
|
1746
|
-
} else if ($._namedColors[c0])
|
|
1747
|
-
|
|
1730
|
+
} else if ($._namedColors[c0]) {
|
|
1731
|
+
c0 = $._namedColors[c0];
|
|
1732
|
+
} else {
|
|
1748
1733
|
console.error(
|
|
1749
1734
|
"q5 can't parse color: " + c0 + '\nOnly numeric input, hex, and common named colors are supported.'
|
|
1750
1735
|
);
|
|
1751
1736
|
return new C(0, 0, 0);
|
|
1752
1737
|
}
|
|
1753
|
-
if ($._colorFormat != 'integer') {
|
|
1754
|
-
r /= 255;
|
|
1755
|
-
g /= 255;
|
|
1756
|
-
b /= 255;
|
|
1757
|
-
if (a != null) a /= 255;
|
|
1758
|
-
}
|
|
1759
|
-
return new C(r, g, b, a);
|
|
1760
1738
|
}
|
|
1761
|
-
if (Array.isArray(c0))
|
|
1739
|
+
if (Array.isArray(c0)) {
|
|
1740
|
+
c1 = c0[1];
|
|
1741
|
+
c2 = c0[2];
|
|
1742
|
+
c3 = c0[3];
|
|
1743
|
+
c0 = c0[0];
|
|
1744
|
+
}
|
|
1762
1745
|
}
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1746
|
+
|
|
1747
|
+
if ($._colorFormat == 1) {
|
|
1748
|
+
c0 /= 255;
|
|
1749
|
+
if (c1) c1 /= 255;
|
|
1750
|
+
if (c2) c2 /= 255;
|
|
1751
|
+
if (c3) c3 /= 255;
|
|
1766
1752
|
}
|
|
1767
|
-
|
|
1768
|
-
if (
|
|
1753
|
+
|
|
1754
|
+
if (c2 == undefined) return new C(c0, c0, c0, c1);
|
|
1755
|
+
return new C(c0, c1, c2, c3);
|
|
1769
1756
|
};
|
|
1770
1757
|
|
|
1771
1758
|
$.red = (c) => c.r;
|
|
@@ -2919,13 +2906,12 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
2919
2906
|
|
|
2920
2907
|
if ($.colorMode) $.colorMode('rgb', 'float');
|
|
2921
2908
|
|
|
2922
|
-
let colorsStack;
|
|
2909
|
+
let colorsStack, envBindGroup, transformBindGroup;
|
|
2923
2910
|
|
|
2924
2911
|
$._createCanvas = (w, h, opt) => {
|
|
2925
2912
|
q.ctx = q.drawingContext = c.getContext('webgpu');
|
|
2926
2913
|
|
|
2927
|
-
|
|
2928
|
-
opt.format = $._canvasFormat;
|
|
2914
|
+
opt.format = navigator.gpu.getPreferredCanvasFormat();
|
|
2929
2915
|
opt.device = Q5.device;
|
|
2930
2916
|
|
|
2931
2917
|
$.ctx.configure(opt);
|
|
@@ -2942,18 +2928,153 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
2942
2928
|
$.drawStack = [];
|
|
2943
2929
|
|
|
2944
2930
|
// colors used for each draw call
|
|
2945
|
-
colorsStack = $.colorsStack = [];
|
|
2931
|
+
colorsStack = $.colorsStack = [1, 1, 1, 1];
|
|
2946
2932
|
|
|
2947
2933
|
// current color index, used to associate a vertex with a color
|
|
2948
|
-
$._colorIndex =
|
|
2934
|
+
$._colorIndex = 0;
|
|
2935
|
+
|
|
2936
|
+
let envLayout = Q5.device.createBindGroupLayout({
|
|
2937
|
+
entries: [
|
|
2938
|
+
{
|
|
2939
|
+
binding: 0,
|
|
2940
|
+
visibility: GPUShaderStage.VERTEX,
|
|
2941
|
+
buffer: {
|
|
2942
|
+
type: 'uniform',
|
|
2943
|
+
hasDynamicOffset: false
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2946
|
+
]
|
|
2947
|
+
});
|
|
2948
|
+
|
|
2949
|
+
let transformLayout = Q5.device.createBindGroupLayout({
|
|
2950
|
+
entries: [
|
|
2951
|
+
{
|
|
2952
|
+
binding: 0,
|
|
2953
|
+
visibility: GPUShaderStage.VERTEX,
|
|
2954
|
+
buffer: {
|
|
2955
|
+
type: 'read-only-storage',
|
|
2956
|
+
hasDynamicOffset: false
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
]
|
|
2960
|
+
});
|
|
2961
|
+
|
|
2962
|
+
$.bindGroupLayouts = [envLayout, transformLayout];
|
|
2963
|
+
|
|
2964
|
+
const uniformBuffer = Q5.device.createBuffer({
|
|
2965
|
+
size: 8, // Size of two floats
|
|
2966
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
2967
|
+
});
|
|
2968
|
+
|
|
2969
|
+
Q5.device.queue.writeBuffer(uniformBuffer, 0, new Float32Array([$.canvas.hw, $.canvas.hh]));
|
|
2970
|
+
|
|
2971
|
+
envBindGroup = Q5.device.createBindGroup({
|
|
2972
|
+
layout: envLayout,
|
|
2973
|
+
entries: [
|
|
2974
|
+
{
|
|
2975
|
+
binding: 0,
|
|
2976
|
+
resource: {
|
|
2977
|
+
buffer: uniformBuffer
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
]
|
|
2981
|
+
});
|
|
2949
2982
|
};
|
|
2950
2983
|
|
|
2951
2984
|
$._resizeCanvas = (w, h) => {
|
|
2952
2985
|
$._setCanvasSize(w, h);
|
|
2953
2986
|
};
|
|
2954
2987
|
|
|
2955
|
-
$.resetMatrix = () => {
|
|
2956
|
-
|
|
2988
|
+
$.resetMatrix = () => {
|
|
2989
|
+
// Initialize the transformation matrix as 4x4 identity matrix
|
|
2990
|
+
|
|
2991
|
+
// prettier-ignore
|
|
2992
|
+
$._matrix = [
|
|
2993
|
+
1, 0, 0, 0,
|
|
2994
|
+
0, 1, 0, 0,
|
|
2995
|
+
0, 0, 1, 0,
|
|
2996
|
+
0, 0, 0, 1
|
|
2997
|
+
];
|
|
2998
|
+
$._transformIndex = 0;
|
|
2999
|
+
};
|
|
3000
|
+
$.resetMatrix();
|
|
3001
|
+
|
|
3002
|
+
// Boolean to track if the matrix has been modified
|
|
3003
|
+
$._matrixDirty = false;
|
|
3004
|
+
|
|
3005
|
+
// Array to store transformation matrices for the render pass
|
|
3006
|
+
$.transformStates = [$._matrix.slice()];
|
|
3007
|
+
|
|
3008
|
+
// Stack to keep track of transformation matrix indexes
|
|
3009
|
+
$._transformIndexStack = [];
|
|
3010
|
+
|
|
3011
|
+
$.push = () => {
|
|
3012
|
+
// Push the current matrix index onto the stack
|
|
3013
|
+
$._transformIndexStack.push($._transformIndex);
|
|
3014
|
+
};
|
|
3015
|
+
|
|
3016
|
+
$.pop = () => {
|
|
3017
|
+
if ($._transformIndexStack.length > 0) {
|
|
3018
|
+
// Pop the last matrix index from the stack and set it as the current matrix index
|
|
3019
|
+
let idx = $._transformIndexStack.pop();
|
|
3020
|
+
$._matrix = $.transformStates[idx].slice();
|
|
3021
|
+
$._transformIndex = idx;
|
|
3022
|
+
} else {
|
|
3023
|
+
console.warn('Matrix index stack is empty!');
|
|
3024
|
+
}
|
|
3025
|
+
};
|
|
3026
|
+
|
|
3027
|
+
$.translate = (x, y, z) => {
|
|
3028
|
+
if (!x && !y && !z) return;
|
|
3029
|
+
// Update the translation values
|
|
3030
|
+
$._matrix[3] += x;
|
|
3031
|
+
$._matrix[7] += y;
|
|
3032
|
+
$._matrix[11] += z || 0;
|
|
3033
|
+
$._matrixDirty = true;
|
|
3034
|
+
};
|
|
3035
|
+
|
|
3036
|
+
$.rotate = (r) => {
|
|
3037
|
+
if (!r) return;
|
|
3038
|
+
if ($._angleMode == 'degrees') r = $.radians(r);
|
|
3039
|
+
|
|
3040
|
+
let cosR = Math.cos(r);
|
|
3041
|
+
let sinR = Math.sin(r);
|
|
3042
|
+
|
|
3043
|
+
let m0 = $._matrix[0],
|
|
3044
|
+
m1 = $._matrix[1],
|
|
3045
|
+
m4 = $._matrix[4],
|
|
3046
|
+
m5 = $._matrix[5];
|
|
3047
|
+
if (!m0 && !m1 && !m4 && !m5) {
|
|
3048
|
+
$._matrix[0] = cosR;
|
|
3049
|
+
$._matrix[1] = sinR;
|
|
3050
|
+
$._matrix[4] = -sinR;
|
|
3051
|
+
$._matrix[5] = cosR;
|
|
3052
|
+
} else {
|
|
3053
|
+
$._matrix[0] = m0 * cosR + m4 * sinR;
|
|
3054
|
+
$._matrix[1] = m1 * cosR + m5 * sinR;
|
|
3055
|
+
$._matrix[4] = m0 * -sinR + m4 * cosR;
|
|
3056
|
+
$._matrix[5] = m1 * -sinR + m5 * cosR;
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
$._matrixDirty = true;
|
|
3060
|
+
};
|
|
3061
|
+
|
|
3062
|
+
$.scale = (sx = 1, sy, sz = 1) => {
|
|
3063
|
+
sy ??= sx;
|
|
3064
|
+
|
|
3065
|
+
$._matrix[0] *= sx;
|
|
3066
|
+
$._matrix[5] *= sy;
|
|
3067
|
+
$._matrix[10] *= sz;
|
|
3068
|
+
|
|
3069
|
+
$._matrixDirty = true;
|
|
3070
|
+
};
|
|
3071
|
+
|
|
3072
|
+
// Function to save the current matrix state if dirty
|
|
3073
|
+
$._saveMatrix = () => {
|
|
3074
|
+
$.transformStates.push($._matrix.slice());
|
|
3075
|
+
$._transformIndex = $.transformStates.length - 1;
|
|
3076
|
+
$._matrixDirty = false;
|
|
3077
|
+
};
|
|
2957
3078
|
|
|
2958
3079
|
$._beginRender = () => {
|
|
2959
3080
|
$.encoder = Q5.device.createCommandEncoder();
|
|
@@ -2970,12 +3091,38 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
2970
3091
|
};
|
|
2971
3092
|
|
|
2972
3093
|
$._render = () => {
|
|
3094
|
+
$.pass.setBindGroup(0, envBindGroup);
|
|
3095
|
+
|
|
3096
|
+
if (transformStates.length > 1 || !transformBindGroup) {
|
|
3097
|
+
const transformBuffer = Q5.device.createBuffer({
|
|
3098
|
+
size: transformStates.length * 64, // Size of 16 floats
|
|
3099
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
3100
|
+
});
|
|
3101
|
+
|
|
3102
|
+
Q5.device.queue.writeBuffer(transformBuffer, 0, new Float32Array(transformStates.flat()));
|
|
3103
|
+
|
|
3104
|
+
transformBindGroup = Q5.device.createBindGroup({
|
|
3105
|
+
layout: $.bindGroupLayouts[1],
|
|
3106
|
+
entries: [
|
|
3107
|
+
{
|
|
3108
|
+
binding: 0,
|
|
3109
|
+
resource: {
|
|
3110
|
+
buffer: transformBuffer
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
]
|
|
3114
|
+
});
|
|
3115
|
+
}
|
|
3116
|
+
|
|
3117
|
+
$.pass.setBindGroup(1, transformBindGroup);
|
|
3118
|
+
|
|
2973
3119
|
// run pre-render methods
|
|
2974
3120
|
for (let m of $._hooks.preRender) m();
|
|
2975
3121
|
|
|
2976
3122
|
$.pass.setPipeline($.pipelines[0]);
|
|
2977
3123
|
|
|
2978
|
-
|
|
3124
|
+
// local variables used for performance
|
|
3125
|
+
let drawStack = $.drawStack;
|
|
2979
3126
|
let o = 0; // vertex offset
|
|
2980
3127
|
for (let i = 0; i < drawStack.length; i++) {
|
|
2981
3128
|
$.pass.draw(drawStack[i], 1, o, 0);
|
|
@@ -2992,30 +3139,41 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
2992
3139
|
// clear the stacks for the next frame
|
|
2993
3140
|
$.verticesStack.length = 0;
|
|
2994
3141
|
$.drawStack.length = 0;
|
|
2995
|
-
$.colorsStack.length =
|
|
3142
|
+
$.colorsStack.length = 4;
|
|
2996
3143
|
$.pipelinesStack.length = 0;
|
|
2997
|
-
$._colorIndex =
|
|
3144
|
+
$._colorIndex = 0;
|
|
3145
|
+
rotation = 0;
|
|
3146
|
+
$.resetMatrix();
|
|
3147
|
+
$._matrixDirty = false;
|
|
3148
|
+
$.transformStates.length = 1;
|
|
3149
|
+
$._transformIndexStack.length = 0;
|
|
2998
3150
|
};
|
|
2999
3151
|
|
|
3000
3152
|
$.fill = (r, g, b, a = 1) => {
|
|
3153
|
+
if (typeof r == 'string') r = Q5.color(r);
|
|
3001
3154
|
// grayscale mode `fill(1, 0.5)`
|
|
3002
3155
|
if (b == undefined) {
|
|
3003
3156
|
a = g;
|
|
3004
3157
|
g = b = r;
|
|
3005
3158
|
}
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
else levels = [r, g, b, a];
|
|
3009
|
-
|
|
3010
|
-
colorsStack.push(...levels);
|
|
3159
|
+
if (r._q5Color) colorsStack.push(...r.levels);
|
|
3160
|
+
else colorsStack.push(r, g, b, a);
|
|
3011
3161
|
$._colorIndex++;
|
|
3012
3162
|
};
|
|
3163
|
+
$.noFill = () => colorsStack.push(0, 0, 0, 0);
|
|
3164
|
+
$.stroke = () => {};
|
|
3165
|
+
$.noStroke = () => {};
|
|
3166
|
+
|
|
3167
|
+
$.clear = () => {};
|
|
3013
3168
|
};
|
|
3014
3169
|
|
|
3015
3170
|
Q5.webgpu = async function (scope, parent) {
|
|
3016
3171
|
if (!navigator.gpu) {
|
|
3017
3172
|
console.error('q5 WebGPU not supported on this browser!');
|
|
3018
|
-
|
|
3173
|
+
let q = new Q5(scope, parent);
|
|
3174
|
+
q.colorMode('rgb', 1);
|
|
3175
|
+
q._beginRender = () => q.translate(q.canvas.hw, q.canvas.hh);
|
|
3176
|
+
return q;
|
|
3019
3177
|
}
|
|
3020
3178
|
let adapter = await navigator.gpu.requestAdapter();
|
|
3021
3179
|
if (!adapter) throw new Error('No appropriate GPUAdapter found.');
|
|
@@ -3029,23 +3187,31 @@ Q5.renderers.webgpu.drawing = ($, q) => {
|
|
|
3029
3187
|
let verticesStack, drawStack, colorsStack;
|
|
3030
3188
|
|
|
3031
3189
|
$._hooks.postCanvas.push(() => {
|
|
3190
|
+
let colorsLayout = Q5.device.createBindGroupLayout({
|
|
3191
|
+
entries: [
|
|
3192
|
+
{
|
|
3193
|
+
binding: 0,
|
|
3194
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
3195
|
+
buffer: {
|
|
3196
|
+
type: 'read-only-storage',
|
|
3197
|
+
hasDynamicOffset: false
|
|
3198
|
+
}
|
|
3199
|
+
}
|
|
3200
|
+
]
|
|
3201
|
+
});
|
|
3202
|
+
|
|
3203
|
+
$.bindGroupLayouts.push(colorsLayout);
|
|
3204
|
+
|
|
3032
3205
|
verticesStack = $.verticesStack;
|
|
3033
3206
|
drawStack = $.drawStack;
|
|
3034
3207
|
colorsStack = $.colorsStack;
|
|
3035
3208
|
|
|
3036
3209
|
let vertexBufferLayout = {
|
|
3037
|
-
arrayStride:
|
|
3210
|
+
arrayStride: 16, // 2 coordinates + 1 color index + 1 transform index * 4 bytes each
|
|
3038
3211
|
attributes: [
|
|
3039
|
-
{
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
shaderLocation: 0 // position
|
|
3043
|
-
},
|
|
3044
|
-
{
|
|
3045
|
-
format: 'float32',
|
|
3046
|
-
offset: 8,
|
|
3047
|
-
shaderLocation: 1 // colorIndex
|
|
3048
|
-
}
|
|
3212
|
+
{ format: 'float32x2', offset: 0, shaderLocation: 0 }, // position
|
|
3213
|
+
{ format: 'float32', offset: 8, shaderLocation: 1 }, // colorIndex
|
|
3214
|
+
{ format: 'float32', offset: 12, shaderLocation: 2 } // transformIndex
|
|
3049
3215
|
]
|
|
3050
3216
|
};
|
|
3051
3217
|
|
|
@@ -3056,10 +3222,23 @@ struct VertexOutput {
|
|
|
3056
3222
|
@location(1) colorIndex: f32
|
|
3057
3223
|
};
|
|
3058
3224
|
|
|
3225
|
+
struct Uniforms {
|
|
3226
|
+
halfWidth: f32,
|
|
3227
|
+
halfHeight: f32
|
|
3228
|
+
};
|
|
3229
|
+
|
|
3230
|
+
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
|
3231
|
+
@group(1) @binding(0) var<storage, read> transforms: array<mat4x4<f32>>;
|
|
3232
|
+
|
|
3059
3233
|
@vertex
|
|
3060
|
-
fn vertexMain(@location(0) pos: vec2<f32>, @location(1) colorIndex: f32) -> VertexOutput {
|
|
3234
|
+
fn vertexMain(@location(0) pos: vec2<f32>, @location(1) colorIndex: f32, @location(2) transformIndex: f32) -> VertexOutput {
|
|
3235
|
+
var vert = vec4<f32>(pos, 0.0, 1.0);
|
|
3236
|
+
vert *= transforms[i32(transformIndex)];
|
|
3237
|
+
vert.x /= uniforms.halfWidth;
|
|
3238
|
+
vert.y /= uniforms.halfHeight;
|
|
3239
|
+
|
|
3061
3240
|
var output: VertexOutput;
|
|
3062
|
-
output.position =
|
|
3241
|
+
output.position = vert;
|
|
3063
3242
|
output.colorIndex = colorIndex;
|
|
3064
3243
|
return output;
|
|
3065
3244
|
}
|
|
@@ -3068,7 +3247,7 @@ fn vertexMain(@location(0) pos: vec2<f32>, @location(1) colorIndex: f32) -> Vert
|
|
|
3068
3247
|
|
|
3069
3248
|
let fragmentShader = Q5.device.createShaderModule({
|
|
3070
3249
|
code: `
|
|
3071
|
-
@group(
|
|
3250
|
+
@group(2) @binding(0) var<storage, read> uColors : array<vec4<f32>>;
|
|
3072
3251
|
|
|
3073
3252
|
@fragment
|
|
3074
3253
|
fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
@@ -3078,45 +3257,104 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
|
3078
3257
|
`
|
|
3079
3258
|
});
|
|
3080
3259
|
|
|
3081
|
-
let bindGroupLayout = Q5.device.createBindGroupLayout({
|
|
3082
|
-
entries: [
|
|
3083
|
-
{
|
|
3084
|
-
binding: 0,
|
|
3085
|
-
visibility: GPUShaderStage.FRAGMENT,
|
|
3086
|
-
buffer: {
|
|
3087
|
-
type: 'read-only-storage',
|
|
3088
|
-
hasDynamicOffset: false
|
|
3089
|
-
}
|
|
3090
|
-
}
|
|
3091
|
-
]
|
|
3092
|
-
});
|
|
3093
|
-
|
|
3094
3260
|
let pipelineLayout = Q5.device.createPipelineLayout({
|
|
3095
|
-
bindGroupLayouts:
|
|
3261
|
+
bindGroupLayouts: $.bindGroupLayouts
|
|
3096
3262
|
});
|
|
3097
3263
|
|
|
3098
|
-
$.
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3264
|
+
$._createPipeline = (blendConfig) => {
|
|
3265
|
+
return Q5.device.createRenderPipeline({
|
|
3266
|
+
layout: pipelineLayout,
|
|
3267
|
+
vertex: {
|
|
3268
|
+
module: vertexShader,
|
|
3269
|
+
entryPoint: 'vertexMain',
|
|
3270
|
+
buffers: [vertexBufferLayout]
|
|
3271
|
+
},
|
|
3272
|
+
fragment: {
|
|
3273
|
+
module: fragmentShader,
|
|
3274
|
+
entryPoint: 'fragmentMain',
|
|
3275
|
+
targets: [
|
|
3276
|
+
{
|
|
3277
|
+
format: 'bgra8unorm',
|
|
3278
|
+
blend: blendConfig
|
|
3279
|
+
}
|
|
3280
|
+
]
|
|
3281
|
+
},
|
|
3282
|
+
primitive: {
|
|
3283
|
+
topology: 'triangle-list'
|
|
3284
|
+
}
|
|
3285
|
+
});
|
|
3286
|
+
};
|
|
3287
|
+
|
|
3288
|
+
$.pipelines[0] = $._createPipeline(blendConfigs.normal);
|
|
3289
|
+
});
|
|
3290
|
+
|
|
3291
|
+
// prettier-ignore
|
|
3292
|
+
let blendFactors = [
|
|
3293
|
+
'zero', // 0
|
|
3294
|
+
'one', // 1
|
|
3295
|
+
'src-alpha', // 2
|
|
3296
|
+
'one-minus-src-alpha', // 3
|
|
3297
|
+
'dst', // 4
|
|
3298
|
+
'dst-alpha', // 5
|
|
3299
|
+
'one-minus-dst-alpha', // 6
|
|
3300
|
+
'one-minus-src' // 7
|
|
3301
|
+
];
|
|
3302
|
+
let blendOps = [
|
|
3303
|
+
'add', // 0
|
|
3304
|
+
'subtract', // 1
|
|
3305
|
+
'reverse-subtract', // 2
|
|
3306
|
+
'min', // 3
|
|
3307
|
+
'max' // 4
|
|
3308
|
+
];
|
|
3309
|
+
|
|
3310
|
+
const blendModes = {
|
|
3311
|
+
normal: [2, 3, 0, 2, 3, 0],
|
|
3312
|
+
lighter: [2, 1, 0, 2, 1, 0],
|
|
3313
|
+
subtract: [2, 1, 2, 2, 1, 2],
|
|
3314
|
+
multiply: [4, 0, 0, 5, 0, 0],
|
|
3315
|
+
screen: [1, 3, 0, 1, 3, 0],
|
|
3316
|
+
darken: [1, 3, 3, 1, 3, 3],
|
|
3317
|
+
lighten: [1, 3, 4, 1, 3, 4],
|
|
3318
|
+
overlay: [2, 3, 0, 2, 3, 0],
|
|
3319
|
+
hard_light: [2, 3, 0, 2, 3, 0],
|
|
3320
|
+
soft_light: [2, 3, 0, 2, 3, 0],
|
|
3321
|
+
difference: [2, 3, 2, 2, 3, 2],
|
|
3322
|
+
exclusion: [2, 3, 0, 2, 3, 0],
|
|
3323
|
+
color_dodge: [1, 7, 0, 1, 7, 0],
|
|
3324
|
+
color_burn: [6, 1, 0, 6, 1, 0],
|
|
3325
|
+
linear_dodge: [2, 1, 0, 2, 1, 0],
|
|
3326
|
+
linear_burn: [2, 7, 1, 2, 7, 1],
|
|
3327
|
+
vivid_light: [2, 7, 0, 2, 7, 0],
|
|
3328
|
+
pin_light: [2, 7, 0, 2, 7, 0],
|
|
3329
|
+
hard_mix: [2, 7, 0, 2, 7, 0]
|
|
3330
|
+
};
|
|
3331
|
+
|
|
3332
|
+
$.blendConfigs = {};
|
|
3333
|
+
|
|
3334
|
+
Object.entries(blendModes).forEach(([name, mode]) => {
|
|
3335
|
+
$.blendConfigs[name] = {
|
|
3336
|
+
color: {
|
|
3337
|
+
srcFactor: blendFactors[mode[0]],
|
|
3338
|
+
dstFactor: blendFactors[mode[1]],
|
|
3339
|
+
operation: blendOps[mode[2]]
|
|
3113
3340
|
},
|
|
3114
|
-
|
|
3115
|
-
|
|
3341
|
+
alpha: {
|
|
3342
|
+
srcFactor: blendFactors[mode[3]],
|
|
3343
|
+
dstFactor: blendFactors[mode[4]],
|
|
3344
|
+
operation: blendOps[mode[5]]
|
|
3116
3345
|
}
|
|
3117
|
-
}
|
|
3346
|
+
};
|
|
3118
3347
|
});
|
|
3119
3348
|
|
|
3349
|
+
$._blendMode = 'normal';
|
|
3350
|
+
$.blendMode = (mode) => {
|
|
3351
|
+
if (mode == $._blendMode) return;
|
|
3352
|
+
if (mode == 'source-over') mode = 'normal';
|
|
3353
|
+
mode = mode.toLowerCase().replace(/[ -]/g, '_');
|
|
3354
|
+
$._blendMode = mode;
|
|
3355
|
+
$.pipelines[0] = $._createPipeline($.blendConfigs[mode]);
|
|
3356
|
+
};
|
|
3357
|
+
|
|
3120
3358
|
let shapeVertices;
|
|
3121
3359
|
|
|
3122
3360
|
$.beginShape = () => {
|
|
@@ -3124,35 +3362,40 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
|
3124
3362
|
};
|
|
3125
3363
|
|
|
3126
3364
|
$.vertex = (x, y) => {
|
|
3127
|
-
|
|
3365
|
+
if ($._matrixDirty) $._saveMatrix();
|
|
3366
|
+
shapeVertices.push(x, -y, $._colorIndex, $._transformIndex);
|
|
3128
3367
|
};
|
|
3129
3368
|
|
|
3130
3369
|
$.endShape = (close) => {
|
|
3131
|
-
|
|
3370
|
+
let v = shapeVertices;
|
|
3371
|
+
if (v.length < 12) {
|
|
3132
3372
|
throw new Error('A shape must have at least 3 vertices.');
|
|
3133
3373
|
}
|
|
3134
3374
|
if (close) {
|
|
3135
3375
|
// Close the shape by adding the first vertex at the end
|
|
3136
|
-
|
|
3376
|
+
v.push(v[0], v[1], v[2], v[3]);
|
|
3137
3377
|
}
|
|
3138
3378
|
// Convert the shape to triangles
|
|
3139
3379
|
let triangles = [];
|
|
3140
|
-
for (let i =
|
|
3380
|
+
for (let i = 4; i < v.length; i += 4) {
|
|
3141
3381
|
triangles.push(
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3382
|
+
v[0], // First vertex
|
|
3383
|
+
v[1],
|
|
3384
|
+
v[2],
|
|
3385
|
+
v[3],
|
|
3386
|
+
v[i - 4], // Previous vertex
|
|
3387
|
+
v[i - 3],
|
|
3388
|
+
v[i - 2],
|
|
3389
|
+
v[i - 1],
|
|
3390
|
+
v[i], // Current vertex
|
|
3391
|
+
v[i + 1],
|
|
3392
|
+
v[i + 2],
|
|
3393
|
+
v[i + 3]
|
|
3151
3394
|
);
|
|
3152
3395
|
}
|
|
3153
3396
|
|
|
3154
3397
|
verticesStack.push(...triangles);
|
|
3155
|
-
drawStack.push(triangles.length /
|
|
3398
|
+
drawStack.push(triangles.length / 4);
|
|
3156
3399
|
shapeVertices = [];
|
|
3157
3400
|
};
|
|
3158
3401
|
|
|
@@ -3167,37 +3410,47 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
|
3167
3410
|
$.rect = (x, y, w, h) => {
|
|
3168
3411
|
let hw = w / 2;
|
|
3169
3412
|
let hh = h / 2;
|
|
3170
|
-
|
|
3171
|
-
let left =
|
|
3172
|
-
let right =
|
|
3173
|
-
let top = -(y - hh)
|
|
3174
|
-
let bottom = -(y + hh)
|
|
3413
|
+
|
|
3414
|
+
let left = x - hw;
|
|
3415
|
+
let right = x + hw;
|
|
3416
|
+
let top = -(y - hh); // y is inverted in WebGPU
|
|
3417
|
+
let bottom = -(y + hh);
|
|
3175
3418
|
|
|
3176
3419
|
let ci = $._colorIndex;
|
|
3420
|
+
if ($._matrixDirty) $._saveMatrix();
|
|
3421
|
+
let ti = $._transformIndex;
|
|
3177
3422
|
// two triangles make a rectangle
|
|
3178
3423
|
verticesStack.push(
|
|
3179
3424
|
left,
|
|
3180
3425
|
top,
|
|
3181
3426
|
ci,
|
|
3427
|
+
ti,
|
|
3182
3428
|
right,
|
|
3183
3429
|
top,
|
|
3184
3430
|
ci,
|
|
3431
|
+
ti,
|
|
3185
3432
|
left,
|
|
3186
3433
|
bottom,
|
|
3187
3434
|
ci,
|
|
3435
|
+
ti,
|
|
3188
3436
|
right,
|
|
3189
3437
|
top,
|
|
3190
3438
|
ci,
|
|
3439
|
+
ti,
|
|
3191
3440
|
left,
|
|
3192
3441
|
bottom,
|
|
3193
3442
|
ci,
|
|
3443
|
+
ti,
|
|
3194
3444
|
right,
|
|
3195
3445
|
bottom,
|
|
3196
|
-
ci
|
|
3446
|
+
ci,
|
|
3447
|
+
ti
|
|
3197
3448
|
);
|
|
3198
3449
|
drawStack.push(6);
|
|
3199
3450
|
};
|
|
3200
3451
|
|
|
3452
|
+
$.background = () => {};
|
|
3453
|
+
|
|
3201
3454
|
/**
|
|
3202
3455
|
* Derived from: ceil(Math.log(d) * 7) * 2 - ceil(28)
|
|
3203
3456
|
* This lookup table is used for better performance.
|
|
@@ -3240,14 +3493,11 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
|
3240
3493
|
let a = Math.max(w, 1) / 2;
|
|
3241
3494
|
let b = w == h ? a : Math.max(h, 1) / 2;
|
|
3242
3495
|
|
|
3243
|
-
x /= $.canvas.hw;
|
|
3244
|
-
y /= -$.canvas.hh;
|
|
3245
|
-
a /= $.canvas.hw;
|
|
3246
|
-
b /= -$.canvas.hh;
|
|
3247
|
-
|
|
3248
3496
|
let t = 0; // theta
|
|
3249
3497
|
const angleIncrement = $.TAU / n;
|
|
3250
3498
|
const ci = $._colorIndex;
|
|
3499
|
+
if ($._matrixDirty) $._saveMatrix();
|
|
3500
|
+
const ti = $._transformIndex;
|
|
3251
3501
|
let vx1, vy1, vx2, vy2;
|
|
3252
3502
|
for (let i = 0; i <= n; i++) {
|
|
3253
3503
|
vx1 = vx2;
|
|
@@ -3258,7 +3508,7 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
|
3258
3508
|
|
|
3259
3509
|
if (i == 0) continue;
|
|
3260
3510
|
|
|
3261
|
-
verticesStack.push(x, y, ci, vx1, vy1, ci, vx2, vy2, ci);
|
|
3511
|
+
verticesStack.push(x, y, ci, ti, vx1, vy1, ci, ti, vx2, vy2, ci, ti);
|
|
3262
3512
|
}
|
|
3263
3513
|
|
|
3264
3514
|
drawStack.push(n * 3);
|
|
@@ -3266,10 +3516,6 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
|
3266
3516
|
|
|
3267
3517
|
$.circle = (x, y, d) => $.ellipse(x, y, d, d);
|
|
3268
3518
|
|
|
3269
|
-
$.noStroke = () => {};
|
|
3270
|
-
|
|
3271
|
-
$.background = () => {};
|
|
3272
|
-
|
|
3273
3519
|
$._hooks.preRender.push(() => {
|
|
3274
3520
|
const vertexBuffer = Q5.device.createBuffer({
|
|
3275
3521
|
size: verticesStack.length * 6,
|
|
@@ -3279,20 +3525,20 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
|
3279
3525
|
Q5.device.queue.writeBuffer(vertexBuffer, 0, new Float32Array(verticesStack));
|
|
3280
3526
|
$.pass.setVertexBuffer(0, vertexBuffer);
|
|
3281
3527
|
|
|
3282
|
-
const
|
|
3528
|
+
const colorsBuffer = Q5.device.createBuffer({
|
|
3283
3529
|
size: colorsStack.length * 4,
|
|
3284
3530
|
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
3285
3531
|
});
|
|
3286
3532
|
|
|
3287
|
-
Q5.device.queue.writeBuffer(
|
|
3533
|
+
Q5.device.queue.writeBuffer(colorsBuffer, 0, new Float32Array(colorsStack));
|
|
3288
3534
|
|
|
3289
|
-
const
|
|
3290
|
-
layout: $.
|
|
3535
|
+
const colorsBindGroup = Q5.device.createBindGroup({
|
|
3536
|
+
layout: $.bindGroupLayouts[2],
|
|
3291
3537
|
entries: [
|
|
3292
3538
|
{
|
|
3293
3539
|
binding: 0,
|
|
3294
3540
|
resource: {
|
|
3295
|
-
buffer:
|
|
3541
|
+
buffer: colorsBuffer,
|
|
3296
3542
|
offset: 0,
|
|
3297
3543
|
size: colorsStack.length * 4
|
|
3298
3544
|
}
|
|
@@ -3301,7 +3547,7 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
|
3301
3547
|
});
|
|
3302
3548
|
|
|
3303
3549
|
// set the bind group once before rendering
|
|
3304
|
-
$.pass.setBindGroup(
|
|
3550
|
+
$.pass.setBindGroup(2, colorsBindGroup);
|
|
3305
3551
|
});
|
|
3306
3552
|
};
|
|
3307
3553
|
Q5.renderers.webgpu.image = ($, q) => {};
|