q5 2.1.0 → 2.2.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/README.md +3 -3
- package/package.json +6 -5
- package/{q5-webgpu.js → q5-q2d.js} +225 -464
- package/q5-q2d.min.js +8 -0
- package/q5.js +710 -238
- 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.min.js +0 -8
package/src/q5-webgpu-canvas.js
CHANGED
|
@@ -13,13 +13,12 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
13
13
|
|
|
14
14
|
if ($.colorMode) $.colorMode('rgb', 'float');
|
|
15
15
|
|
|
16
|
-
let colorsStack;
|
|
16
|
+
let colorsStack, envBindGroup, transformBindGroup;
|
|
17
17
|
|
|
18
18
|
$._createCanvas = (w, h, opt) => {
|
|
19
19
|
q.ctx = q.drawingContext = c.getContext('webgpu');
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
opt.format = $._canvasFormat;
|
|
21
|
+
opt.format = navigator.gpu.getPreferredCanvasFormat();
|
|
23
22
|
opt.device = Q5.device;
|
|
24
23
|
|
|
25
24
|
$.ctx.configure(opt);
|
|
@@ -36,18 +35,153 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
36
35
|
$.drawStack = [];
|
|
37
36
|
|
|
38
37
|
// colors used for each draw call
|
|
39
|
-
colorsStack = $.colorsStack = [];
|
|
38
|
+
colorsStack = $.colorsStack = [1, 1, 1, 1];
|
|
40
39
|
|
|
41
40
|
// current color index, used to associate a vertex with a color
|
|
42
|
-
$._colorIndex =
|
|
41
|
+
$._colorIndex = 0;
|
|
42
|
+
|
|
43
|
+
let envLayout = Q5.device.createBindGroupLayout({
|
|
44
|
+
entries: [
|
|
45
|
+
{
|
|
46
|
+
binding: 0,
|
|
47
|
+
visibility: GPUShaderStage.VERTEX,
|
|
48
|
+
buffer: {
|
|
49
|
+
type: 'uniform',
|
|
50
|
+
hasDynamicOffset: false
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
let transformLayout = Q5.device.createBindGroupLayout({
|
|
57
|
+
entries: [
|
|
58
|
+
{
|
|
59
|
+
binding: 0,
|
|
60
|
+
visibility: GPUShaderStage.VERTEX,
|
|
61
|
+
buffer: {
|
|
62
|
+
type: 'read-only-storage',
|
|
63
|
+
hasDynamicOffset: false
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
$.bindGroupLayouts = [envLayout, transformLayout];
|
|
70
|
+
|
|
71
|
+
const uniformBuffer = Q5.device.createBuffer({
|
|
72
|
+
size: 8, // Size of two floats
|
|
73
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
Q5.device.queue.writeBuffer(uniformBuffer, 0, new Float32Array([$.canvas.hw, $.canvas.hh]));
|
|
77
|
+
|
|
78
|
+
envBindGroup = Q5.device.createBindGroup({
|
|
79
|
+
layout: envLayout,
|
|
80
|
+
entries: [
|
|
81
|
+
{
|
|
82
|
+
binding: 0,
|
|
83
|
+
resource: {
|
|
84
|
+
buffer: uniformBuffer
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
});
|
|
43
89
|
};
|
|
44
90
|
|
|
45
91
|
$._resizeCanvas = (w, h) => {
|
|
46
92
|
$._setCanvasSize(w, h);
|
|
47
93
|
};
|
|
48
94
|
|
|
49
|
-
$.resetMatrix = () => {
|
|
50
|
-
|
|
95
|
+
$.resetMatrix = () => {
|
|
96
|
+
// Initialize the transformation matrix as 4x4 identity matrix
|
|
97
|
+
|
|
98
|
+
// prettier-ignore
|
|
99
|
+
$._matrix = [
|
|
100
|
+
1, 0, 0, 0,
|
|
101
|
+
0, 1, 0, 0,
|
|
102
|
+
0, 0, 1, 0,
|
|
103
|
+
0, 0, 0, 1
|
|
104
|
+
];
|
|
105
|
+
$._transformIndex = 0;
|
|
106
|
+
};
|
|
107
|
+
$.resetMatrix();
|
|
108
|
+
|
|
109
|
+
// Boolean to track if the matrix has been modified
|
|
110
|
+
$._matrixDirty = false;
|
|
111
|
+
|
|
112
|
+
// Array to store transformation matrices for the render pass
|
|
113
|
+
$.transformStates = [$._matrix.slice()];
|
|
114
|
+
|
|
115
|
+
// Stack to keep track of transformation matrix indexes
|
|
116
|
+
$._transformIndexStack = [];
|
|
117
|
+
|
|
118
|
+
$.push = () => {
|
|
119
|
+
// Push the current matrix index onto the stack
|
|
120
|
+
$._transformIndexStack.push($._transformIndex);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
$.pop = () => {
|
|
124
|
+
if ($._transformIndexStack.length > 0) {
|
|
125
|
+
// Pop the last matrix index from the stack and set it as the current matrix index
|
|
126
|
+
let idx = $._transformIndexStack.pop();
|
|
127
|
+
$._matrix = $.transformStates[idx].slice();
|
|
128
|
+
$._transformIndex = idx;
|
|
129
|
+
} else {
|
|
130
|
+
console.warn('Matrix index stack is empty!');
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
$.translate = (x, y, z) => {
|
|
135
|
+
if (!x && !y && !z) return;
|
|
136
|
+
// Update the translation values
|
|
137
|
+
$._matrix[3] += x;
|
|
138
|
+
$._matrix[7] += y;
|
|
139
|
+
$._matrix[11] += z || 0;
|
|
140
|
+
$._matrixDirty = true;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
$.rotate = (r) => {
|
|
144
|
+
if (!r) return;
|
|
145
|
+
if ($._angleMode == 'degrees') r = $.radians(r);
|
|
146
|
+
|
|
147
|
+
let cosR = Math.cos(r);
|
|
148
|
+
let sinR = Math.sin(r);
|
|
149
|
+
|
|
150
|
+
let m0 = $._matrix[0],
|
|
151
|
+
m1 = $._matrix[1],
|
|
152
|
+
m4 = $._matrix[4],
|
|
153
|
+
m5 = $._matrix[5];
|
|
154
|
+
if (!m0 && !m1 && !m4 && !m5) {
|
|
155
|
+
$._matrix[0] = cosR;
|
|
156
|
+
$._matrix[1] = sinR;
|
|
157
|
+
$._matrix[4] = -sinR;
|
|
158
|
+
$._matrix[5] = cosR;
|
|
159
|
+
} else {
|
|
160
|
+
$._matrix[0] = m0 * cosR + m4 * sinR;
|
|
161
|
+
$._matrix[1] = m1 * cosR + m5 * sinR;
|
|
162
|
+
$._matrix[4] = m0 * -sinR + m4 * cosR;
|
|
163
|
+
$._matrix[5] = m1 * -sinR + m5 * cosR;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
$._matrixDirty = true;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
$.scale = (sx = 1, sy, sz = 1) => {
|
|
170
|
+
sy ??= sx;
|
|
171
|
+
|
|
172
|
+
$._matrix[0] *= sx;
|
|
173
|
+
$._matrix[5] *= sy;
|
|
174
|
+
$._matrix[10] *= sz;
|
|
175
|
+
|
|
176
|
+
$._matrixDirty = true;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// Function to save the current matrix state if dirty
|
|
180
|
+
$._saveMatrix = () => {
|
|
181
|
+
$.transformStates.push($._matrix.slice());
|
|
182
|
+
$._transformIndex = $.transformStates.length - 1;
|
|
183
|
+
$._matrixDirty = false;
|
|
184
|
+
};
|
|
51
185
|
|
|
52
186
|
$._beginRender = () => {
|
|
53
187
|
$.encoder = Q5.device.createCommandEncoder();
|
|
@@ -64,12 +198,38 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
64
198
|
};
|
|
65
199
|
|
|
66
200
|
$._render = () => {
|
|
201
|
+
$.pass.setBindGroup(0, envBindGroup);
|
|
202
|
+
|
|
203
|
+
if (transformStates.length > 1 || !transformBindGroup) {
|
|
204
|
+
const transformBuffer = Q5.device.createBuffer({
|
|
205
|
+
size: transformStates.length * 64, // Size of 16 floats
|
|
206
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
Q5.device.queue.writeBuffer(transformBuffer, 0, new Float32Array(transformStates.flat()));
|
|
210
|
+
|
|
211
|
+
transformBindGroup = Q5.device.createBindGroup({
|
|
212
|
+
layout: $.bindGroupLayouts[1],
|
|
213
|
+
entries: [
|
|
214
|
+
{
|
|
215
|
+
binding: 0,
|
|
216
|
+
resource: {
|
|
217
|
+
buffer: transformBuffer
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
]
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
$.pass.setBindGroup(1, transformBindGroup);
|
|
225
|
+
|
|
67
226
|
// run pre-render methods
|
|
68
227
|
for (let m of $._hooks.preRender) m();
|
|
69
228
|
|
|
70
229
|
$.pass.setPipeline($.pipelines[0]);
|
|
71
230
|
|
|
72
|
-
|
|
231
|
+
// local variables used for performance
|
|
232
|
+
let drawStack = $.drawStack;
|
|
73
233
|
let o = 0; // vertex offset
|
|
74
234
|
for (let i = 0; i < drawStack.length; i++) {
|
|
75
235
|
$.pass.draw(drawStack[i], 1, o, 0);
|
|
@@ -86,30 +246,41 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
86
246
|
// clear the stacks for the next frame
|
|
87
247
|
$.verticesStack.length = 0;
|
|
88
248
|
$.drawStack.length = 0;
|
|
89
|
-
$.colorsStack.length =
|
|
249
|
+
$.colorsStack.length = 4;
|
|
90
250
|
$.pipelinesStack.length = 0;
|
|
91
|
-
$._colorIndex =
|
|
251
|
+
$._colorIndex = 0;
|
|
252
|
+
rotation = 0;
|
|
253
|
+
$.resetMatrix();
|
|
254
|
+
$._matrixDirty = false;
|
|
255
|
+
$.transformStates.length = 1;
|
|
256
|
+
$._transformIndexStack.length = 0;
|
|
92
257
|
};
|
|
93
258
|
|
|
94
259
|
$.fill = (r, g, b, a = 1) => {
|
|
260
|
+
if (typeof r == 'string') r = Q5.color(r);
|
|
95
261
|
// grayscale mode `fill(1, 0.5)`
|
|
96
262
|
if (b == undefined) {
|
|
97
263
|
a = g;
|
|
98
264
|
g = b = r;
|
|
99
265
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
else levels = [r, g, b, a];
|
|
103
|
-
|
|
104
|
-
colorsStack.push(...levels);
|
|
266
|
+
if (r._q5Color) colorsStack.push(...r.levels);
|
|
267
|
+
else colorsStack.push(r, g, b, a);
|
|
105
268
|
$._colorIndex++;
|
|
106
269
|
};
|
|
270
|
+
$.noFill = () => colorsStack.push(0, 0, 0, 0);
|
|
271
|
+
$.stroke = () => {};
|
|
272
|
+
$.noStroke = () => {};
|
|
273
|
+
|
|
274
|
+
$.clear = () => {};
|
|
107
275
|
};
|
|
108
276
|
|
|
109
277
|
Q5.webgpu = async function (scope, parent) {
|
|
110
278
|
if (!navigator.gpu) {
|
|
111
279
|
console.error('q5 WebGPU not supported on this browser!');
|
|
112
|
-
|
|
280
|
+
let q = new Q5(scope, parent);
|
|
281
|
+
q.colorMode('rgb', 1);
|
|
282
|
+
q._beginRender = () => q.translate(q.canvas.hw, q.canvas.hh);
|
|
283
|
+
return q;
|
|
113
284
|
}
|
|
114
285
|
let adapter = await navigator.gpu.requestAdapter();
|
|
115
286
|
if (!adapter) throw new Error('No appropriate GPUAdapter found.');
|
package/src/q5-webgpu-drawing.js
CHANGED
|
@@ -4,23 +4,31 @@ Q5.renderers.webgpu.drawing = ($, q) => {
|
|
|
4
4
|
let verticesStack, drawStack, colorsStack;
|
|
5
5
|
|
|
6
6
|
$._hooks.postCanvas.push(() => {
|
|
7
|
+
let colorsLayout = Q5.device.createBindGroupLayout({
|
|
8
|
+
entries: [
|
|
9
|
+
{
|
|
10
|
+
binding: 0,
|
|
11
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
12
|
+
buffer: {
|
|
13
|
+
type: 'read-only-storage',
|
|
14
|
+
hasDynamicOffset: false
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
$.bindGroupLayouts.push(colorsLayout);
|
|
21
|
+
|
|
7
22
|
verticesStack = $.verticesStack;
|
|
8
23
|
drawStack = $.drawStack;
|
|
9
24
|
colorsStack = $.colorsStack;
|
|
10
25
|
|
|
11
26
|
let vertexBufferLayout = {
|
|
12
|
-
arrayStride:
|
|
27
|
+
arrayStride: 16, // 2 coordinates + 1 color index + 1 transform index * 4 bytes each
|
|
13
28
|
attributes: [
|
|
14
|
-
{
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
shaderLocation: 0 // position
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
format: 'float32',
|
|
21
|
-
offset: 8,
|
|
22
|
-
shaderLocation: 1 // colorIndex
|
|
23
|
-
}
|
|
29
|
+
{ format: 'float32x2', offset: 0, shaderLocation: 0 }, // position
|
|
30
|
+
{ format: 'float32', offset: 8, shaderLocation: 1 }, // colorIndex
|
|
31
|
+
{ format: 'float32', offset: 12, shaderLocation: 2 } // transformIndex
|
|
24
32
|
]
|
|
25
33
|
};
|
|
26
34
|
|
|
@@ -31,10 +39,23 @@ struct VertexOutput {
|
|
|
31
39
|
@location(1) colorIndex: f32
|
|
32
40
|
};
|
|
33
41
|
|
|
42
|
+
struct Uniforms {
|
|
43
|
+
halfWidth: f32,
|
|
44
|
+
halfHeight: f32
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
|
48
|
+
@group(1) @binding(0) var<storage, read> transforms: array<mat4x4<f32>>;
|
|
49
|
+
|
|
34
50
|
@vertex
|
|
35
|
-
fn vertexMain(@location(0) pos: vec2<f32>, @location(1) colorIndex: f32) -> VertexOutput {
|
|
51
|
+
fn vertexMain(@location(0) pos: vec2<f32>, @location(1) colorIndex: f32, @location(2) transformIndex: f32) -> VertexOutput {
|
|
52
|
+
var vert = vec4<f32>(pos, 0.0, 1.0);
|
|
53
|
+
vert *= transforms[i32(transformIndex)];
|
|
54
|
+
vert.x /= uniforms.halfWidth;
|
|
55
|
+
vert.y /= uniforms.halfHeight;
|
|
56
|
+
|
|
36
57
|
var output: VertexOutput;
|
|
37
|
-
output.position =
|
|
58
|
+
output.position = vert;
|
|
38
59
|
output.colorIndex = colorIndex;
|
|
39
60
|
return output;
|
|
40
61
|
}
|
|
@@ -43,7 +64,7 @@ fn vertexMain(@location(0) pos: vec2<f32>, @location(1) colorIndex: f32) -> Vert
|
|
|
43
64
|
|
|
44
65
|
let fragmentShader = Q5.device.createShaderModule({
|
|
45
66
|
code: `
|
|
46
|
-
@group(
|
|
67
|
+
@group(2) @binding(0) var<storage, read> uColors : array<vec4<f32>>;
|
|
47
68
|
|
|
48
69
|
@fragment
|
|
49
70
|
fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
@@ -53,45 +74,104 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
|
53
74
|
`
|
|
54
75
|
});
|
|
55
76
|
|
|
56
|
-
let bindGroupLayout = Q5.device.createBindGroupLayout({
|
|
57
|
-
entries: [
|
|
58
|
-
{
|
|
59
|
-
binding: 0,
|
|
60
|
-
visibility: GPUShaderStage.FRAGMENT,
|
|
61
|
-
buffer: {
|
|
62
|
-
type: 'read-only-storage',
|
|
63
|
-
hasDynamicOffset: false
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
]
|
|
67
|
-
});
|
|
68
|
-
|
|
69
77
|
let pipelineLayout = Q5.device.createPipelineLayout({
|
|
70
|
-
bindGroupLayouts:
|
|
78
|
+
bindGroupLayouts: $.bindGroupLayouts
|
|
71
79
|
});
|
|
72
80
|
|
|
73
|
-
$.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
81
|
+
$._createPipeline = (blendConfig) => {
|
|
82
|
+
return Q5.device.createRenderPipeline({
|
|
83
|
+
layout: pipelineLayout,
|
|
84
|
+
vertex: {
|
|
85
|
+
module: vertexShader,
|
|
86
|
+
entryPoint: 'vertexMain',
|
|
87
|
+
buffers: [vertexBufferLayout]
|
|
88
|
+
},
|
|
89
|
+
fragment: {
|
|
90
|
+
module: fragmentShader,
|
|
91
|
+
entryPoint: 'fragmentMain',
|
|
92
|
+
targets: [
|
|
93
|
+
{
|
|
94
|
+
format: 'bgra8unorm',
|
|
95
|
+
blend: blendConfig
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
primitive: {
|
|
100
|
+
topology: 'triangle-list'
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
$.pipelines[0] = $._createPipeline(blendConfigs.normal);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// prettier-ignore
|
|
109
|
+
let blendFactors = [
|
|
110
|
+
'zero', // 0
|
|
111
|
+
'one', // 1
|
|
112
|
+
'src-alpha', // 2
|
|
113
|
+
'one-minus-src-alpha', // 3
|
|
114
|
+
'dst', // 4
|
|
115
|
+
'dst-alpha', // 5
|
|
116
|
+
'one-minus-dst-alpha', // 6
|
|
117
|
+
'one-minus-src' // 7
|
|
118
|
+
];
|
|
119
|
+
let blendOps = [
|
|
120
|
+
'add', // 0
|
|
121
|
+
'subtract', // 1
|
|
122
|
+
'reverse-subtract', // 2
|
|
123
|
+
'min', // 3
|
|
124
|
+
'max' // 4
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
const blendModes = {
|
|
128
|
+
normal: [2, 3, 0, 2, 3, 0],
|
|
129
|
+
lighter: [2, 1, 0, 2, 1, 0],
|
|
130
|
+
subtract: [2, 1, 2, 2, 1, 2],
|
|
131
|
+
multiply: [4, 0, 0, 5, 0, 0],
|
|
132
|
+
screen: [1, 3, 0, 1, 3, 0],
|
|
133
|
+
darken: [1, 3, 3, 1, 3, 3],
|
|
134
|
+
lighten: [1, 3, 4, 1, 3, 4],
|
|
135
|
+
overlay: [2, 3, 0, 2, 3, 0],
|
|
136
|
+
hard_light: [2, 3, 0, 2, 3, 0],
|
|
137
|
+
soft_light: [2, 3, 0, 2, 3, 0],
|
|
138
|
+
difference: [2, 3, 2, 2, 3, 2],
|
|
139
|
+
exclusion: [2, 3, 0, 2, 3, 0],
|
|
140
|
+
color_dodge: [1, 7, 0, 1, 7, 0],
|
|
141
|
+
color_burn: [6, 1, 0, 6, 1, 0],
|
|
142
|
+
linear_dodge: [2, 1, 0, 2, 1, 0],
|
|
143
|
+
linear_burn: [2, 7, 1, 2, 7, 1],
|
|
144
|
+
vivid_light: [2, 7, 0, 2, 7, 0],
|
|
145
|
+
pin_light: [2, 7, 0, 2, 7, 0],
|
|
146
|
+
hard_mix: [2, 7, 0, 2, 7, 0]
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
$.blendConfigs = {};
|
|
150
|
+
|
|
151
|
+
Object.entries(blendModes).forEach(([name, mode]) => {
|
|
152
|
+
$.blendConfigs[name] = {
|
|
153
|
+
color: {
|
|
154
|
+
srcFactor: blendFactors[mode[0]],
|
|
155
|
+
dstFactor: blendFactors[mode[1]],
|
|
156
|
+
operation: blendOps[mode[2]]
|
|
88
157
|
},
|
|
89
|
-
|
|
90
|
-
|
|
158
|
+
alpha: {
|
|
159
|
+
srcFactor: blendFactors[mode[3]],
|
|
160
|
+
dstFactor: blendFactors[mode[4]],
|
|
161
|
+
operation: blendOps[mode[5]]
|
|
91
162
|
}
|
|
92
|
-
}
|
|
163
|
+
};
|
|
93
164
|
});
|
|
94
165
|
|
|
166
|
+
$._blendMode = 'normal';
|
|
167
|
+
$.blendMode = (mode) => {
|
|
168
|
+
if (mode == $._blendMode) return;
|
|
169
|
+
if (mode == 'source-over') mode = 'normal';
|
|
170
|
+
mode = mode.toLowerCase().replace(/[ -]/g, '_');
|
|
171
|
+
$._blendMode = mode;
|
|
172
|
+
$.pipelines[0] = $._createPipeline($.blendConfigs[mode]);
|
|
173
|
+
};
|
|
174
|
+
|
|
95
175
|
let shapeVertices;
|
|
96
176
|
|
|
97
177
|
$.beginShape = () => {
|
|
@@ -99,35 +179,40 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
|
99
179
|
};
|
|
100
180
|
|
|
101
181
|
$.vertex = (x, y) => {
|
|
102
|
-
|
|
182
|
+
if ($._matrixDirty) $._saveMatrix();
|
|
183
|
+
shapeVertices.push(x, -y, $._colorIndex, $._transformIndex);
|
|
103
184
|
};
|
|
104
185
|
|
|
105
186
|
$.endShape = (close) => {
|
|
106
|
-
|
|
187
|
+
let v = shapeVertices;
|
|
188
|
+
if (v.length < 12) {
|
|
107
189
|
throw new Error('A shape must have at least 3 vertices.');
|
|
108
190
|
}
|
|
109
191
|
if (close) {
|
|
110
192
|
// Close the shape by adding the first vertex at the end
|
|
111
|
-
|
|
193
|
+
v.push(v[0], v[1], v[2], v[3]);
|
|
112
194
|
}
|
|
113
195
|
// Convert the shape to triangles
|
|
114
196
|
let triangles = [];
|
|
115
|
-
for (let i =
|
|
197
|
+
for (let i = 4; i < v.length; i += 4) {
|
|
116
198
|
triangles.push(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
199
|
+
v[0], // First vertex
|
|
200
|
+
v[1],
|
|
201
|
+
v[2],
|
|
202
|
+
v[3],
|
|
203
|
+
v[i - 4], // Previous vertex
|
|
204
|
+
v[i - 3],
|
|
205
|
+
v[i - 2],
|
|
206
|
+
v[i - 1],
|
|
207
|
+
v[i], // Current vertex
|
|
208
|
+
v[i + 1],
|
|
209
|
+
v[i + 2],
|
|
210
|
+
v[i + 3]
|
|
126
211
|
);
|
|
127
212
|
}
|
|
128
213
|
|
|
129
214
|
verticesStack.push(...triangles);
|
|
130
|
-
drawStack.push(triangles.length /
|
|
215
|
+
drawStack.push(triangles.length / 4);
|
|
131
216
|
shapeVertices = [];
|
|
132
217
|
};
|
|
133
218
|
|
|
@@ -142,37 +227,47 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
|
142
227
|
$.rect = (x, y, w, h) => {
|
|
143
228
|
let hw = w / 2;
|
|
144
229
|
let hh = h / 2;
|
|
145
|
-
|
|
146
|
-
let left =
|
|
147
|
-
let right =
|
|
148
|
-
let top = -(y - hh)
|
|
149
|
-
let bottom = -(y + hh)
|
|
230
|
+
|
|
231
|
+
let left = x - hw;
|
|
232
|
+
let right = x + hw;
|
|
233
|
+
let top = -(y - hh); // y is inverted in WebGPU
|
|
234
|
+
let bottom = -(y + hh);
|
|
150
235
|
|
|
151
236
|
let ci = $._colorIndex;
|
|
237
|
+
if ($._matrixDirty) $._saveMatrix();
|
|
238
|
+
let ti = $._transformIndex;
|
|
152
239
|
// two triangles make a rectangle
|
|
153
240
|
verticesStack.push(
|
|
154
241
|
left,
|
|
155
242
|
top,
|
|
156
243
|
ci,
|
|
244
|
+
ti,
|
|
157
245
|
right,
|
|
158
246
|
top,
|
|
159
247
|
ci,
|
|
248
|
+
ti,
|
|
160
249
|
left,
|
|
161
250
|
bottom,
|
|
162
251
|
ci,
|
|
252
|
+
ti,
|
|
163
253
|
right,
|
|
164
254
|
top,
|
|
165
255
|
ci,
|
|
256
|
+
ti,
|
|
166
257
|
left,
|
|
167
258
|
bottom,
|
|
168
259
|
ci,
|
|
260
|
+
ti,
|
|
169
261
|
right,
|
|
170
262
|
bottom,
|
|
171
|
-
ci
|
|
263
|
+
ci,
|
|
264
|
+
ti
|
|
172
265
|
);
|
|
173
266
|
drawStack.push(6);
|
|
174
267
|
};
|
|
175
268
|
|
|
269
|
+
$.background = () => {};
|
|
270
|
+
|
|
176
271
|
/**
|
|
177
272
|
* Derived from: ceil(Math.log(d) * 7) * 2 - ceil(28)
|
|
178
273
|
* This lookup table is used for better performance.
|
|
@@ -215,14 +310,11 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
|
215
310
|
let a = Math.max(w, 1) / 2;
|
|
216
311
|
let b = w == h ? a : Math.max(h, 1) / 2;
|
|
217
312
|
|
|
218
|
-
x /= $.canvas.hw;
|
|
219
|
-
y /= -$.canvas.hh;
|
|
220
|
-
a /= $.canvas.hw;
|
|
221
|
-
b /= -$.canvas.hh;
|
|
222
|
-
|
|
223
313
|
let t = 0; // theta
|
|
224
314
|
const angleIncrement = $.TAU / n;
|
|
225
315
|
const ci = $._colorIndex;
|
|
316
|
+
if ($._matrixDirty) $._saveMatrix();
|
|
317
|
+
const ti = $._transformIndex;
|
|
226
318
|
let vx1, vy1, vx2, vy2;
|
|
227
319
|
for (let i = 0; i <= n; i++) {
|
|
228
320
|
vx1 = vx2;
|
|
@@ -233,7 +325,7 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
|
233
325
|
|
|
234
326
|
if (i == 0) continue;
|
|
235
327
|
|
|
236
|
-
verticesStack.push(x, y, ci, vx1, vy1, ci, vx2, vy2, ci);
|
|
328
|
+
verticesStack.push(x, y, ci, ti, vx1, vy1, ci, ti, vx2, vy2, ci, ti);
|
|
237
329
|
}
|
|
238
330
|
|
|
239
331
|
drawStack.push(n * 3);
|
|
@@ -241,10 +333,6 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
|
241
333
|
|
|
242
334
|
$.circle = (x, y, d) => $.ellipse(x, y, d, d);
|
|
243
335
|
|
|
244
|
-
$.noStroke = () => {};
|
|
245
|
-
|
|
246
|
-
$.background = () => {};
|
|
247
|
-
|
|
248
336
|
$._hooks.preRender.push(() => {
|
|
249
337
|
const vertexBuffer = Q5.device.createBuffer({
|
|
250
338
|
size: verticesStack.length * 6,
|
|
@@ -254,20 +342,20 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
|
254
342
|
Q5.device.queue.writeBuffer(vertexBuffer, 0, new Float32Array(verticesStack));
|
|
255
343
|
$.pass.setVertexBuffer(0, vertexBuffer);
|
|
256
344
|
|
|
257
|
-
const
|
|
345
|
+
const colorsBuffer = Q5.device.createBuffer({
|
|
258
346
|
size: colorsStack.length * 4,
|
|
259
347
|
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
260
348
|
});
|
|
261
349
|
|
|
262
|
-
Q5.device.queue.writeBuffer(
|
|
350
|
+
Q5.device.queue.writeBuffer(colorsBuffer, 0, new Float32Array(colorsStack));
|
|
263
351
|
|
|
264
|
-
const
|
|
265
|
-
layout: $.
|
|
352
|
+
const colorsBindGroup = Q5.device.createBindGroup({
|
|
353
|
+
layout: $.bindGroupLayouts[2],
|
|
266
354
|
entries: [
|
|
267
355
|
{
|
|
268
356
|
binding: 0,
|
|
269
357
|
resource: {
|
|
270
|
-
buffer:
|
|
358
|
+
buffer: colorsBuffer,
|
|
271
359
|
offset: 0,
|
|
272
360
|
size: colorsStack.length * 4
|
|
273
361
|
}
|
|
@@ -276,6 +364,6 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
|
276
364
|
});
|
|
277
365
|
|
|
278
366
|
// set the bind group once before rendering
|
|
279
|
-
$.pass.setBindGroup(
|
|
367
|
+
$.pass.setBindGroup(2, colorsBindGroup);
|
|
280
368
|
});
|
|
281
369
|
};
|