q5 2.0.17 → 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/.vscode/settings.json +3 -0
- package/README.md +10 -7
- package/package.json +6 -4
- package/q5-webgpu.js +3554 -0
- package/q5-webgpu.min.js +8 -0
- package/q5.js +543 -590
- package/q5.min.js +2 -2
- package/src/q5-2d-canvas.js +51 -174
- package/src/q5-2d-drawing.js +9 -55
- package/src/q5-2d-image.js +141 -279
- package/src/q5-2d-soft-filters.js +6 -6
- package/src/q5-2d-text.js +3 -3
- package/src/q5-ai.js +2 -2
- package/src/q5-canvas.js +200 -0
- package/src/q5-color.js +66 -39
- package/src/q5-core.js +41 -33
- package/src/q5-display.js +6 -0
- package/src/q5-input.js +46 -34
- package/src/q5-math.js +3 -3
- package/src/q5-sound.js +10 -3
- package/src/q5-util.js +3 -3
- package/src/q5-vector.js +1 -1
- package/src/q5-webgpu-canvas.js +290 -0
- package/src/q5-webgpu-drawing.js +369 -0
- package/src/q5-webgpu-image.js +1 -0
- package/src/q5-webgpu-text.js +1 -0
- package/src/readme.md +83 -6
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
Q5.renderers.webgpu.drawing = ($, q) => {
|
|
2
|
+
$.CLOSE = 1;
|
|
3
|
+
|
|
4
|
+
let verticesStack, drawStack, colorsStack;
|
|
5
|
+
|
|
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
|
+
|
|
22
|
+
verticesStack = $.verticesStack;
|
|
23
|
+
drawStack = $.drawStack;
|
|
24
|
+
colorsStack = $.colorsStack;
|
|
25
|
+
|
|
26
|
+
let vertexBufferLayout = {
|
|
27
|
+
arrayStride: 16, // 2 coordinates + 1 color index + 1 transform index * 4 bytes each
|
|
28
|
+
attributes: [
|
|
29
|
+
{ format: 'float32x2', offset: 0, shaderLocation: 0 }, // position
|
|
30
|
+
{ format: 'float32', offset: 8, shaderLocation: 1 }, // colorIndex
|
|
31
|
+
{ format: 'float32', offset: 12, shaderLocation: 2 } // transformIndex
|
|
32
|
+
]
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
let vertexShader = Q5.device.createShaderModule({
|
|
36
|
+
code: `
|
|
37
|
+
struct VertexOutput {
|
|
38
|
+
@builtin(position) position: vec4<f32>,
|
|
39
|
+
@location(1) colorIndex: f32
|
|
40
|
+
};
|
|
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
|
+
|
|
50
|
+
@vertex
|
|
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
|
+
|
|
57
|
+
var output: VertexOutput;
|
|
58
|
+
output.position = vert;
|
|
59
|
+
output.colorIndex = colorIndex;
|
|
60
|
+
return output;
|
|
61
|
+
}
|
|
62
|
+
`
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
let fragmentShader = Q5.device.createShaderModule({
|
|
66
|
+
code: `
|
|
67
|
+
@group(2) @binding(0) var<storage, read> uColors : array<vec4<f32>>;
|
|
68
|
+
|
|
69
|
+
@fragment
|
|
70
|
+
fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
71
|
+
let index = u32(colorIndex);
|
|
72
|
+
return mix(uColors[index], uColors[index + 1u], fract(colorIndex));
|
|
73
|
+
}
|
|
74
|
+
`
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
let pipelineLayout = Q5.device.createPipelineLayout({
|
|
78
|
+
bindGroupLayouts: $.bindGroupLayouts
|
|
79
|
+
});
|
|
80
|
+
|
|
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]]
|
|
157
|
+
},
|
|
158
|
+
alpha: {
|
|
159
|
+
srcFactor: blendFactors[mode[3]],
|
|
160
|
+
dstFactor: blendFactors[mode[4]],
|
|
161
|
+
operation: blendOps[mode[5]]
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
});
|
|
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
|
+
|
|
175
|
+
let shapeVertices;
|
|
176
|
+
|
|
177
|
+
$.beginShape = () => {
|
|
178
|
+
shapeVertices = [];
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
$.vertex = (x, y) => {
|
|
182
|
+
if ($._matrixDirty) $._saveMatrix();
|
|
183
|
+
shapeVertices.push(x, -y, $._colorIndex, $._transformIndex);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
$.endShape = (close) => {
|
|
187
|
+
let v = shapeVertices;
|
|
188
|
+
if (v.length < 12) {
|
|
189
|
+
throw new Error('A shape must have at least 3 vertices.');
|
|
190
|
+
}
|
|
191
|
+
if (close) {
|
|
192
|
+
// Close the shape by adding the first vertex at the end
|
|
193
|
+
v.push(v[0], v[1], v[2], v[3]);
|
|
194
|
+
}
|
|
195
|
+
// Convert the shape to triangles
|
|
196
|
+
let triangles = [];
|
|
197
|
+
for (let i = 4; i < v.length; i += 4) {
|
|
198
|
+
triangles.push(
|
|
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]
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
verticesStack.push(...triangles);
|
|
215
|
+
drawStack.push(triangles.length / 4);
|
|
216
|
+
shapeVertices = [];
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
$.triangle = (x1, y1, x2, y2, x3, y3) => {
|
|
220
|
+
$.beginShape();
|
|
221
|
+
$.vertex(x1, y1);
|
|
222
|
+
$.vertex(x2, y2);
|
|
223
|
+
$.vertex(x3, y3);
|
|
224
|
+
$.endShape(1);
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
$.rect = (x, y, w, h) => {
|
|
228
|
+
let hw = w / 2;
|
|
229
|
+
let hh = h / 2;
|
|
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);
|
|
235
|
+
|
|
236
|
+
let ci = $._colorIndex;
|
|
237
|
+
if ($._matrixDirty) $._saveMatrix();
|
|
238
|
+
let ti = $._transformIndex;
|
|
239
|
+
// two triangles make a rectangle
|
|
240
|
+
verticesStack.push(
|
|
241
|
+
left,
|
|
242
|
+
top,
|
|
243
|
+
ci,
|
|
244
|
+
ti,
|
|
245
|
+
right,
|
|
246
|
+
top,
|
|
247
|
+
ci,
|
|
248
|
+
ti,
|
|
249
|
+
left,
|
|
250
|
+
bottom,
|
|
251
|
+
ci,
|
|
252
|
+
ti,
|
|
253
|
+
right,
|
|
254
|
+
top,
|
|
255
|
+
ci,
|
|
256
|
+
ti,
|
|
257
|
+
left,
|
|
258
|
+
bottom,
|
|
259
|
+
ci,
|
|
260
|
+
ti,
|
|
261
|
+
right,
|
|
262
|
+
bottom,
|
|
263
|
+
ci,
|
|
264
|
+
ti
|
|
265
|
+
);
|
|
266
|
+
drawStack.push(6);
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
$.background = () => {};
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Derived from: ceil(Math.log(d) * 7) * 2 - ceil(28)
|
|
273
|
+
* This lookup table is used for better performance.
|
|
274
|
+
* @param {Number} d diameter of the circle
|
|
275
|
+
* @returns n number of segments
|
|
276
|
+
*/
|
|
277
|
+
// prettier-ignore
|
|
278
|
+
const getArcSegments = (d) =>
|
|
279
|
+
d < 14 ? 8 :
|
|
280
|
+
d < 16 ? 10 :
|
|
281
|
+
d < 18 ? 12 :
|
|
282
|
+
d < 20 ? 14 :
|
|
283
|
+
d < 22 ? 16 :
|
|
284
|
+
d < 24 ? 18 :
|
|
285
|
+
d < 28 ? 20 :
|
|
286
|
+
d < 34 ? 22 :
|
|
287
|
+
d < 42 ? 24 :
|
|
288
|
+
d < 48 ? 26 :
|
|
289
|
+
d < 56 ? 28 :
|
|
290
|
+
d < 64 ? 30 :
|
|
291
|
+
d < 72 ? 32 :
|
|
292
|
+
d < 84 ? 34 :
|
|
293
|
+
d < 96 ? 36 :
|
|
294
|
+
d < 98 ? 38 :
|
|
295
|
+
d < 113 ? 40 :
|
|
296
|
+
d < 149 ? 44 :
|
|
297
|
+
d < 199 ? 48 :
|
|
298
|
+
d < 261 ? 52 :
|
|
299
|
+
d < 353 ? 56 :
|
|
300
|
+
d < 461 ? 60 :
|
|
301
|
+
d < 585 ? 64 :
|
|
302
|
+
d < 1200 ? 70 :
|
|
303
|
+
d < 1800 ? 80 :
|
|
304
|
+
d < 2400 ? 90 :
|
|
305
|
+
100;
|
|
306
|
+
|
|
307
|
+
$.ellipse = (x, y, w, h) => {
|
|
308
|
+
const n = getArcSegments(w == h ? w : Math.max(w, h));
|
|
309
|
+
|
|
310
|
+
let a = Math.max(w, 1) / 2;
|
|
311
|
+
let b = w == h ? a : Math.max(h, 1) / 2;
|
|
312
|
+
|
|
313
|
+
let t = 0; // theta
|
|
314
|
+
const angleIncrement = $.TAU / n;
|
|
315
|
+
const ci = $._colorIndex;
|
|
316
|
+
if ($._matrixDirty) $._saveMatrix();
|
|
317
|
+
const ti = $._transformIndex;
|
|
318
|
+
let vx1, vy1, vx2, vy2;
|
|
319
|
+
for (let i = 0; i <= n; i++) {
|
|
320
|
+
vx1 = vx2;
|
|
321
|
+
vy1 = vy2;
|
|
322
|
+
vx2 = x + a * Math.cos(t);
|
|
323
|
+
vy2 = y + b * Math.sin(t);
|
|
324
|
+
t += angleIncrement;
|
|
325
|
+
|
|
326
|
+
if (i == 0) continue;
|
|
327
|
+
|
|
328
|
+
verticesStack.push(x, y, ci, ti, vx1, vy1, ci, ti, vx2, vy2, ci, ti);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
drawStack.push(n * 3);
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
$.circle = (x, y, d) => $.ellipse(x, y, d, d);
|
|
335
|
+
|
|
336
|
+
$._hooks.preRender.push(() => {
|
|
337
|
+
const vertexBuffer = Q5.device.createBuffer({
|
|
338
|
+
size: verticesStack.length * 6,
|
|
339
|
+
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
Q5.device.queue.writeBuffer(vertexBuffer, 0, new Float32Array(verticesStack));
|
|
343
|
+
$.pass.setVertexBuffer(0, vertexBuffer);
|
|
344
|
+
|
|
345
|
+
const colorsBuffer = Q5.device.createBuffer({
|
|
346
|
+
size: colorsStack.length * 4,
|
|
347
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
Q5.device.queue.writeBuffer(colorsBuffer, 0, new Float32Array(colorsStack));
|
|
351
|
+
|
|
352
|
+
const colorsBindGroup = Q5.device.createBindGroup({
|
|
353
|
+
layout: $.bindGroupLayouts[2],
|
|
354
|
+
entries: [
|
|
355
|
+
{
|
|
356
|
+
binding: 0,
|
|
357
|
+
resource: {
|
|
358
|
+
buffer: colorsBuffer,
|
|
359
|
+
offset: 0,
|
|
360
|
+
size: colorsStack.length * 4
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
]
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// set the bind group once before rendering
|
|
367
|
+
$.pass.setBindGroup(2, colorsBindGroup);
|
|
368
|
+
});
|
|
369
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Q5.renderers.webgpu.image = ($, q) => {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Q5.renderers.webgpu.text = ($, q) => {};
|
package/src/readme.md
CHANGED
|
@@ -32,12 +32,39 @@ Additional modules:
|
|
|
32
32
|
<script src="https://q5js.org/src/q5-sensors.js"></script>
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
+
WebGPU rendering modules are in development:
|
|
36
|
+
|
|
37
|
+
```html
|
|
38
|
+
<script src="https://q5js.org/src/q5-webgpu-canvas.js"></script>
|
|
39
|
+
<script src="https://q5js.org/src/q5-webgpu-drawing.js"></script>
|
|
40
|
+
```
|
|
41
|
+
|
|
35
42
|
# Module Info
|
|
36
43
|
|
|
44
|
+
- [Modular Use](#modular-use)
|
|
45
|
+
- [Module Info](#module-info)
|
|
46
|
+
- [core](#core)
|
|
47
|
+
- [canvas](#canvas)
|
|
48
|
+
- [q2d-canvas](#q2d-canvas)
|
|
49
|
+
- [q2d-drawing](#q2d-drawing)
|
|
50
|
+
- [q2d-image](#q2d-image)
|
|
51
|
+
- [q2d-soft-filters](#q2d-soft-filters)
|
|
52
|
+
- [q2d-text](#q2d-text)
|
|
53
|
+
- [webgpu-canvas](#webgpu-canvas)
|
|
54
|
+
- [webgpu-drawing](#webgpu-drawing)
|
|
55
|
+
- [math](#math)
|
|
56
|
+
- [noisier](#noisier)
|
|
57
|
+
|
|
37
58
|
## core
|
|
38
59
|
|
|
39
60
|
The core module provides the absolute basic functionality necessary to run q5.
|
|
40
61
|
|
|
62
|
+
It loads other modules by passing `$` (alias for `this`) and `q` (which in global mode is a proxy for `this` and `window` or `global`).
|
|
63
|
+
|
|
64
|
+
## canvas
|
|
65
|
+
|
|
66
|
+
The canvas module provides shared functionality for all canvas renderers, such as adding the canvas to the DOM, resizing the canvas, setting pixel density,
|
|
67
|
+
|
|
41
68
|
## q2d-canvas
|
|
42
69
|
|
|
43
70
|
Adds canvas 2D rendering support to q5.
|
|
@@ -60,27 +87,77 @@ The filters in q5-image use the [CanvasRenderingContext2D.filter](https://develo
|
|
|
60
87
|
|
|
61
88
|
Software implementation of image filters.
|
|
62
89
|
|
|
63
|
-
This module includes additional filters not implemented in q5-image and legacy filter support for Safari which lacks ctx.filter
|
|
90
|
+
This module includes additional filters not implemented in q5-image and legacy filter support for Safari which lacks `ctx.filter`.
|
|
64
91
|
|
|
65
|
-
These filters are slow
|
|
92
|
+
These filters are slow. Real-time use of them is not recommended.
|
|
66
93
|
|
|
67
|
-
As of April 2024, Safari Technology Preview supports ctx.filter under a flag. Hopefully in the near future
|
|
94
|
+
As of April 2024, Safari Technology Preview supports `ctx.filter` under a flag. Hopefully in the near future this module can be omitted from the default bundle.
|
|
68
95
|
|
|
69
96
|
## q2d-text
|
|
70
97
|
|
|
71
98
|
Adds canvas 2D text rendering support to q5.
|
|
72
99
|
|
|
100
|
+
Image based features in this module require the q5-2d-image module.
|
|
101
|
+
|
|
73
102
|
`createTextImage(str, w, h)` provides a simple way for users to create images from text.
|
|
74
103
|
|
|
75
104
|
`textImage(img, x, y)` displays text images, complying with the user's text position settings instead of their image position settings. The idea is that text will appear in the same place as it would if it were drawn with the `text` function.
|
|
76
105
|
|
|
77
106
|
`textCache(bool, maxSize)` enables or disables text caching. As of June 2024, drawing rotated text is super slow in all browsers, so q5 creates and stores images of text and rotates that instead. Can improve rendering performance 90x but uses more memory. `maxSize` param determines the maximum number of text images to cache, default is 500 since these images will typically be quite small. The text image cache (tic) is a timed cache, so the oldest images are removed first.
|
|
78
107
|
|
|
79
|
-
|
|
108
|
+
## webgpu-canvas
|
|
109
|
+
|
|
110
|
+
> ⚠️ Experimental features! ⚠️
|
|
111
|
+
|
|
112
|
+
This module adds WebGPU renderer support to q5. Note that images, text, and strokes can not be rendered yet.
|
|
113
|
+
|
|
114
|
+
Instead of `new Q5()`, run the async function `Q5.webgpu()`. Explicit use of `createCanvas` is required.
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
let q = await Q5.webgpu();
|
|
118
|
+
|
|
119
|
+
createCanvas(500, 500);
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Set the script type of your sketch to "module" to use `await` on the top level.
|
|
123
|
+
|
|
124
|
+
```html
|
|
125
|
+
<script type="module" src="sketch.js"></script>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Using q5 with the webgpu renderer requires a different approach to setting up sketches. That's because variables and functions declared in a module are not added to the global `window` object.
|
|
129
|
+
|
|
130
|
+
Add functions like `setup` and `draw` as properties of `q`, the instance of Q5.
|
|
131
|
+
|
|
132
|
+
```js
|
|
133
|
+
q.draw = function () {
|
|
134
|
+
// draw stuff
|
|
135
|
+
};
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The sketches you create with the q5-webgpu renderer will still display properly if WebGPU is not supported on a viewer's browser.
|
|
139
|
+
|
|
140
|
+
In that case, q5 will put a warning in the console and fall back to the q2d renderer. A compatibility layer is applied which sets the color mode to "rgba" in float format and translates the origin to the center of the canvas on every frame. For now, be sure to set `noStroke` in your setup code and `clear` the canvas at the start of your `draw` function to match current q5 webgpu limitations.
|
|
141
|
+
|
|
142
|
+
Implemented functions:
|
|
143
|
+
|
|
144
|
+
`createCanvas`, `resizeCanvas`, `fill`, `clear`, `push`, `pop`, `resetMatrix`, `translate`, `rotate`, `scale`
|
|
145
|
+
|
|
146
|
+
## webgpu-drawing
|
|
147
|
+
|
|
148
|
+
> Uses `colorMode('rgb', 'float')` by default. Changing it to 'oklch' is not supported yet for the webgpu renderer.
|
|
149
|
+
|
|
150
|
+
All basic shapes are drawn from their center. Strokes are not implemented yet.
|
|
151
|
+
|
|
152
|
+
q5's WebGPU renderer drawing functions like `rect` don't immediately draw on the canvas. Instead, they prepare vertex and color data to be sent to the GPU in bulk, which occurs after the user's `draw` function and any post-draw functions are run. This approach better utilizes the GPU, so it doesn't have to repeatedly wait for the CPU to send small chunks of data that describe each individual shape. It's why WebGPU is faster than Canvas2D.
|
|
153
|
+
|
|
154
|
+
Implemented functions:
|
|
155
|
+
|
|
156
|
+
`rect`, `circle`, `ellipse`, `triangle`, `beginShape`, `vertex`, `endShape`, `blendMode`
|
|
80
157
|
|
|
81
158
|
## math
|
|
82
159
|
|
|
83
|
-
`PerlinNoise` is q5's default noise algorithm. Kevin Perlin won an Academy Award for his work on the original algorithm for the 1982 movie Tron.
|
|
160
|
+
`PerlinNoise` is q5's default noise algorithm. Kevin Perlin won an Academy Award for his work on the original algorithm for the 1982 movie Tron. The JavaScript implementation of it in q5 was authored by Tezumie.
|
|
84
161
|
|
|
85
162
|
`noiseMode` enables users to switch between noise algorithms, although only "perlin" is included in q5-math.
|
|
86
163
|
|
|
@@ -90,4 +167,4 @@ Adds additional noise functions to q5.
|
|
|
90
167
|
|
|
91
168
|
`SimplexNoise` is a simplex noise implementation in JavaScript by Tezumie. Kevin Perlin's patent on simplex noise expired in 2022. Simplex noise is slightly faster but arguably less visually appealing than perlin noise.
|
|
92
169
|
|
|
93
|
-
`BlockyNoise` is similar to p5's default `noise` function, which is a bit notorious in the gen art community for not actually being perlin noise, despite its claims to be. It looks closer to value noise but is not a standard implementation of that either.
|
|
170
|
+
`BlockyNoise` is similar to p5's default `noise` function, which is a bit notorious in the gen art community for not actually being perlin noise, despite its claims to be. It looks closer to value noise but is not a standard implementation of that either. When visualized in 2d it's a bit blocky at 1 octave, hence the name.
|