q5 2.0.17 → 2.1.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 +8 -5
- package/package.json +5 -4
- package/q5-webgpu.js +3308 -0
- package/q5-webgpu.min.js +8 -0
- package/q5.js +531 -565
- package/q5.min.js +2 -2
- package/src/q5-2d-canvas.js +50 -174
- package/src/q5-2d-drawing.js +8 -39
- 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 +56 -30
- 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 +119 -0
- package/src/q5-webgpu-drawing.js +281 -0
- package/src/q5-webgpu-image.js +1 -0
- package/src/q5-webgpu-text.js +1 -0
- package/src/readme.md +83 -6
package/src/q5-math.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Q5.modules.math = ($,
|
|
1
|
+
Q5.modules.math = ($, q) => {
|
|
2
2
|
$.DEGREES = 'degrees';
|
|
3
3
|
$.RADIANS = 'radians';
|
|
4
4
|
|
|
@@ -27,7 +27,7 @@ Q5.modules.math = ($, p) => {
|
|
|
27
27
|
$.degrees = (x) => x * $._RADTODEG;
|
|
28
28
|
$.radians = (x) => x * $._DEGTORAD;
|
|
29
29
|
|
|
30
|
-
$.map = (value, istart, istop, ostart, ostop, clamp) => {
|
|
30
|
+
$.map = Q5.prototype.map = (value, istart, istop, ostart, ostop, clamp) => {
|
|
31
31
|
let val = ostart + (ostop - ostart) * (((value - istart) * 1.0) / (istop - istart));
|
|
32
32
|
if (!clamp) {
|
|
33
33
|
return val;
|
|
@@ -285,7 +285,7 @@ Q5.modules.math = ($, p) => {
|
|
|
285
285
|
let _noise;
|
|
286
286
|
|
|
287
287
|
$.noiseMode = (mode) => {
|
|
288
|
-
|
|
288
|
+
q.Noise = Q5[mode[0].toUpperCase() + mode.slice(1) + 'Noise'];
|
|
289
289
|
_noise = null;
|
|
290
290
|
};
|
|
291
291
|
$.noiseSeed = (seed) => {
|
package/src/q5-sound.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
Q5.modules.sound = ($,
|
|
1
|
+
Q5.modules.sound = ($, q) => {
|
|
2
2
|
$.Sound = Q5.Sound;
|
|
3
3
|
$.loadSound = (path, cb) => {
|
|
4
|
-
|
|
4
|
+
q._preloadCount++;
|
|
5
5
|
Q5.aud ??= new window.AudioContext();
|
|
6
6
|
let a = new Q5.Sound(path, cb);
|
|
7
7
|
a.addEventListener('canplaythrough', () => {
|
|
8
|
-
|
|
8
|
+
q._preloadCount--;
|
|
9
|
+
a.loaded = true;
|
|
9
10
|
if (cb) cb(a);
|
|
10
11
|
});
|
|
11
12
|
return a;
|
|
@@ -37,4 +38,10 @@ Q5.Sound = class extends Audio {
|
|
|
37
38
|
setPan(value) {
|
|
38
39
|
this.pan = value;
|
|
39
40
|
}
|
|
41
|
+
isLoaded() {
|
|
42
|
+
return this.loaded;
|
|
43
|
+
}
|
|
44
|
+
isPlaying() {
|
|
45
|
+
return !this.paused;
|
|
46
|
+
}
|
|
40
47
|
};
|
package/src/q5-util.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Q5.modules.util = ($,
|
|
1
|
+
Q5.modules.util = ($, q) => {
|
|
2
2
|
$._loadFile = (path, cb, type) => {
|
|
3
|
-
|
|
3
|
+
q._preloadCount++;
|
|
4
4
|
let ret = {};
|
|
5
5
|
fetch(path)
|
|
6
6
|
.then((r) => {
|
|
@@ -8,7 +8,7 @@ Q5.modules.util = ($, p) => {
|
|
|
8
8
|
if (type == 'text') return r.text();
|
|
9
9
|
})
|
|
10
10
|
.then((r) => {
|
|
11
|
-
|
|
11
|
+
q._preloadCount--;
|
|
12
12
|
Object.assign(ret, r);
|
|
13
13
|
if (cb) cb(r);
|
|
14
14
|
});
|
package/src/q5-vector.js
CHANGED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* q5-webgpu
|
|
3
|
+
*
|
|
4
|
+
* EXPERIMENTAL, for developer testing only!
|
|
5
|
+
*/
|
|
6
|
+
Q5.renderers.webgpu = {};
|
|
7
|
+
|
|
8
|
+
Q5.renderers.webgpu.canvas = ($, q) => {
|
|
9
|
+
let c = $.canvas;
|
|
10
|
+
|
|
11
|
+
c.width = $.width = 500;
|
|
12
|
+
c.height = $.height = 500;
|
|
13
|
+
|
|
14
|
+
if ($.colorMode) $.colorMode('rgb', 'float');
|
|
15
|
+
|
|
16
|
+
let colorsStack;
|
|
17
|
+
|
|
18
|
+
$._createCanvas = (w, h, opt) => {
|
|
19
|
+
q.ctx = q.drawingContext = c.getContext('webgpu');
|
|
20
|
+
|
|
21
|
+
$._canvasFormat = navigator.gpu.getPreferredCanvasFormat();
|
|
22
|
+
opt.format = $._canvasFormat;
|
|
23
|
+
opt.device = Q5.device;
|
|
24
|
+
|
|
25
|
+
$.ctx.configure(opt);
|
|
26
|
+
|
|
27
|
+
$.pipelines = [];
|
|
28
|
+
|
|
29
|
+
// pipeline changes for each draw call
|
|
30
|
+
$.pipelinesStack = [];
|
|
31
|
+
|
|
32
|
+
// vertices for each draw call
|
|
33
|
+
$.verticesStack = [];
|
|
34
|
+
|
|
35
|
+
// number of vertices for each draw call
|
|
36
|
+
$.drawStack = [];
|
|
37
|
+
|
|
38
|
+
// colors used for each draw call
|
|
39
|
+
colorsStack = $.colorsStack = [];
|
|
40
|
+
|
|
41
|
+
// current color index, used to associate a vertex with a color
|
|
42
|
+
$._colorIndex = -1;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
$._resizeCanvas = (w, h) => {
|
|
46
|
+
$._setCanvasSize(w, h);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
$.resetMatrix = () => {};
|
|
50
|
+
$.translate = () => {};
|
|
51
|
+
|
|
52
|
+
$._beginRender = () => {
|
|
53
|
+
$.encoder = Q5.device.createCommandEncoder();
|
|
54
|
+
|
|
55
|
+
q.pass = $.encoder.beginRenderPass({
|
|
56
|
+
colorAttachments: [
|
|
57
|
+
{
|
|
58
|
+
view: ctx.getCurrentTexture().createView(),
|
|
59
|
+
loadOp: 'clear',
|
|
60
|
+
storeOp: 'store'
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
$._render = () => {
|
|
67
|
+
// run pre-render methods
|
|
68
|
+
for (let m of $._hooks.preRender) m();
|
|
69
|
+
|
|
70
|
+
$.pass.setPipeline($.pipelines[0]);
|
|
71
|
+
|
|
72
|
+
let drawStack = $.drawStack; // local variables used for performance
|
|
73
|
+
let o = 0; // vertex offset
|
|
74
|
+
for (let i = 0; i < drawStack.length; i++) {
|
|
75
|
+
$.pass.draw(drawStack[i], 1, o, 0);
|
|
76
|
+
o += drawStack[i];
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
$._finishRender = () => {
|
|
81
|
+
$.pass.end();
|
|
82
|
+
const commandBuffer = $.encoder.finish();
|
|
83
|
+
Q5.device.queue.submit([commandBuffer]);
|
|
84
|
+
q.pass = $.encoder = null;
|
|
85
|
+
|
|
86
|
+
// clear the stacks for the next frame
|
|
87
|
+
$.verticesStack.length = 0;
|
|
88
|
+
$.drawStack.length = 0;
|
|
89
|
+
$.colorsStack.length = 0;
|
|
90
|
+
$.pipelinesStack.length = 0;
|
|
91
|
+
$._colorIndex = -1;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
$.fill = (r, g, b, a = 1) => {
|
|
95
|
+
// grayscale mode `fill(1, 0.5)`
|
|
96
|
+
if (b == undefined) {
|
|
97
|
+
a = g;
|
|
98
|
+
g = b = r;
|
|
99
|
+
}
|
|
100
|
+
let levels;
|
|
101
|
+
if (r._q5Color) levels = r.levels;
|
|
102
|
+
else levels = [r, g, b, a];
|
|
103
|
+
|
|
104
|
+
colorsStack.push(...levels);
|
|
105
|
+
$._colorIndex++;
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
Q5.webgpu = async function (scope, parent) {
|
|
110
|
+
if (!navigator.gpu) {
|
|
111
|
+
console.error('q5 WebGPU not supported on this browser!');
|
|
112
|
+
return new Q5(scope, parent);
|
|
113
|
+
}
|
|
114
|
+
let adapter = await navigator.gpu.requestAdapter();
|
|
115
|
+
if (!adapter) throw new Error('No appropriate GPUAdapter found.');
|
|
116
|
+
Q5.device = await adapter.requestDevice();
|
|
117
|
+
|
|
118
|
+
return new Q5(scope, parent, 'webgpu');
|
|
119
|
+
};
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
Q5.renderers.webgpu.drawing = ($, q) => {
|
|
2
|
+
$.CLOSE = 1;
|
|
3
|
+
|
|
4
|
+
let verticesStack, drawStack, colorsStack;
|
|
5
|
+
|
|
6
|
+
$._hooks.postCanvas.push(() => {
|
|
7
|
+
verticesStack = $.verticesStack;
|
|
8
|
+
drawStack = $.drawStack;
|
|
9
|
+
colorsStack = $.colorsStack;
|
|
10
|
+
|
|
11
|
+
let vertexBufferLayout = {
|
|
12
|
+
arrayStride: 12, // 2 coordinates + 1 color index * 4 bytes each
|
|
13
|
+
attributes: [
|
|
14
|
+
{
|
|
15
|
+
format: 'float32x2',
|
|
16
|
+
offset: 0,
|
|
17
|
+
shaderLocation: 0 // position
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
format: 'float32',
|
|
21
|
+
offset: 8,
|
|
22
|
+
shaderLocation: 1 // colorIndex
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
let vertexShader = Q5.device.createShaderModule({
|
|
28
|
+
code: `
|
|
29
|
+
struct VertexOutput {
|
|
30
|
+
@builtin(position) position: vec4<f32>,
|
|
31
|
+
@location(1) colorIndex: f32
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
@vertex
|
|
35
|
+
fn vertexMain(@location(0) pos: vec2<f32>, @location(1) colorIndex: f32) -> VertexOutput {
|
|
36
|
+
var output: VertexOutput;
|
|
37
|
+
output.position = vec4<f32>(pos, 0.0, 1.0);
|
|
38
|
+
output.colorIndex = colorIndex;
|
|
39
|
+
return output;
|
|
40
|
+
}
|
|
41
|
+
`
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
let fragmentShader = Q5.device.createShaderModule({
|
|
45
|
+
code: `
|
|
46
|
+
@group(0) @binding(0) var<storage, read> uColors : array<vec4<f32>>;
|
|
47
|
+
|
|
48
|
+
@fragment
|
|
49
|
+
fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
50
|
+
let index = u32(colorIndex);
|
|
51
|
+
return mix(uColors[index], uColors[index + 1u], fract(colorIndex));
|
|
52
|
+
}
|
|
53
|
+
`
|
|
54
|
+
});
|
|
55
|
+
|
|
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
|
+
let pipelineLayout = Q5.device.createPipelineLayout({
|
|
70
|
+
bindGroupLayouts: [bindGroupLayout]
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
$.pipelines[0] = Q5.device.createRenderPipeline({
|
|
74
|
+
layout: pipelineLayout,
|
|
75
|
+
vertex: {
|
|
76
|
+
module: vertexShader,
|
|
77
|
+
entryPoint: 'vertexMain',
|
|
78
|
+
buffers: [vertexBufferLayout]
|
|
79
|
+
},
|
|
80
|
+
fragment: {
|
|
81
|
+
module: fragmentShader,
|
|
82
|
+
entryPoint: 'fragmentMain',
|
|
83
|
+
targets: [
|
|
84
|
+
{
|
|
85
|
+
format: $._canvasFormat
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
},
|
|
89
|
+
primitive: {
|
|
90
|
+
topology: 'triangle-list'
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
let shapeVertices;
|
|
96
|
+
|
|
97
|
+
$.beginShape = () => {
|
|
98
|
+
shapeVertices = [];
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
$.vertex = (x, y) => {
|
|
102
|
+
shapeVertices.push(x / $.canvas.hw, -y / $.canvas.hh, $._colorIndex);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
$.endShape = (close) => {
|
|
106
|
+
if (shapeVertices.length < 6) {
|
|
107
|
+
throw new Error('A shape must have at least 3 vertices.');
|
|
108
|
+
}
|
|
109
|
+
if (close) {
|
|
110
|
+
// Close the shape by adding the first vertex at the end
|
|
111
|
+
shapeVertices.push(shapeVertices[0], shapeVertices[1], shapeVertices[2]);
|
|
112
|
+
}
|
|
113
|
+
// Convert the shape to triangles
|
|
114
|
+
let triangles = [];
|
|
115
|
+
for (let i = 3; i < shapeVertices.length; i += 3) {
|
|
116
|
+
triangles.push(
|
|
117
|
+
shapeVertices[0],
|
|
118
|
+
shapeVertices[1],
|
|
119
|
+
shapeVertices[2], // First vertex
|
|
120
|
+
shapeVertices[i - 3],
|
|
121
|
+
shapeVertices[i - 2],
|
|
122
|
+
shapeVertices[i - 1], // Previous vertex
|
|
123
|
+
shapeVertices[i],
|
|
124
|
+
shapeVertices[i + 1],
|
|
125
|
+
shapeVertices[i + 2] // Current vertex
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
verticesStack.push(...triangles);
|
|
130
|
+
drawStack.push(triangles.length / 3);
|
|
131
|
+
shapeVertices = [];
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
$.triangle = (x1, y1, x2, y2, x3, y3) => {
|
|
135
|
+
$.beginShape();
|
|
136
|
+
$.vertex(x1, y1);
|
|
137
|
+
$.vertex(x2, y2);
|
|
138
|
+
$.vertex(x3, y3);
|
|
139
|
+
$.endShape(1);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
$.rect = (x, y, w, h) => {
|
|
143
|
+
let hw = w / 2;
|
|
144
|
+
let hh = h / 2;
|
|
145
|
+
// convert the coordinates from pixel space to NDC space
|
|
146
|
+
let left = (x - hw) / $.canvas.hw;
|
|
147
|
+
let right = (x + hw) / $.canvas.hw;
|
|
148
|
+
let top = -(y - hh) / $.canvas.hh; // y is inverted in WebGPU
|
|
149
|
+
let bottom = -(y + hh) / $.canvas.hh; // y is inverted in WebGPU
|
|
150
|
+
|
|
151
|
+
let ci = $._colorIndex;
|
|
152
|
+
// two triangles make a rectangle
|
|
153
|
+
verticesStack.push(
|
|
154
|
+
left,
|
|
155
|
+
top,
|
|
156
|
+
ci,
|
|
157
|
+
right,
|
|
158
|
+
top,
|
|
159
|
+
ci,
|
|
160
|
+
left,
|
|
161
|
+
bottom,
|
|
162
|
+
ci,
|
|
163
|
+
right,
|
|
164
|
+
top,
|
|
165
|
+
ci,
|
|
166
|
+
left,
|
|
167
|
+
bottom,
|
|
168
|
+
ci,
|
|
169
|
+
right,
|
|
170
|
+
bottom,
|
|
171
|
+
ci
|
|
172
|
+
);
|
|
173
|
+
drawStack.push(6);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Derived from: ceil(Math.log(d) * 7) * 2 - ceil(28)
|
|
178
|
+
* This lookup table is used for better performance.
|
|
179
|
+
* @param {Number} d diameter of the circle
|
|
180
|
+
* @returns n number of segments
|
|
181
|
+
*/
|
|
182
|
+
// prettier-ignore
|
|
183
|
+
const getArcSegments = (d) =>
|
|
184
|
+
d < 14 ? 8 :
|
|
185
|
+
d < 16 ? 10 :
|
|
186
|
+
d < 18 ? 12 :
|
|
187
|
+
d < 20 ? 14 :
|
|
188
|
+
d < 22 ? 16 :
|
|
189
|
+
d < 24 ? 18 :
|
|
190
|
+
d < 28 ? 20 :
|
|
191
|
+
d < 34 ? 22 :
|
|
192
|
+
d < 42 ? 24 :
|
|
193
|
+
d < 48 ? 26 :
|
|
194
|
+
d < 56 ? 28 :
|
|
195
|
+
d < 64 ? 30 :
|
|
196
|
+
d < 72 ? 32 :
|
|
197
|
+
d < 84 ? 34 :
|
|
198
|
+
d < 96 ? 36 :
|
|
199
|
+
d < 98 ? 38 :
|
|
200
|
+
d < 113 ? 40 :
|
|
201
|
+
d < 149 ? 44 :
|
|
202
|
+
d < 199 ? 48 :
|
|
203
|
+
d < 261 ? 52 :
|
|
204
|
+
d < 353 ? 56 :
|
|
205
|
+
d < 461 ? 60 :
|
|
206
|
+
d < 585 ? 64 :
|
|
207
|
+
d < 1200 ? 70 :
|
|
208
|
+
d < 1800 ? 80 :
|
|
209
|
+
d < 2400 ? 90 :
|
|
210
|
+
100;
|
|
211
|
+
|
|
212
|
+
$.ellipse = (x, y, w, h) => {
|
|
213
|
+
const n = getArcSegments(w == h ? w : Math.max(w, h));
|
|
214
|
+
|
|
215
|
+
let a = Math.max(w, 1) / 2;
|
|
216
|
+
let b = w == h ? a : Math.max(h, 1) / 2;
|
|
217
|
+
|
|
218
|
+
x /= $.canvas.hw;
|
|
219
|
+
y /= -$.canvas.hh;
|
|
220
|
+
a /= $.canvas.hw;
|
|
221
|
+
b /= -$.canvas.hh;
|
|
222
|
+
|
|
223
|
+
let t = 0; // theta
|
|
224
|
+
const angleIncrement = $.TAU / n;
|
|
225
|
+
const ci = $._colorIndex;
|
|
226
|
+
let vx1, vy1, vx2, vy2;
|
|
227
|
+
for (let i = 0; i <= n; i++) {
|
|
228
|
+
vx1 = vx2;
|
|
229
|
+
vy1 = vy2;
|
|
230
|
+
vx2 = x + a * Math.cos(t);
|
|
231
|
+
vy2 = y + b * Math.sin(t);
|
|
232
|
+
t += angleIncrement;
|
|
233
|
+
|
|
234
|
+
if (i == 0) continue;
|
|
235
|
+
|
|
236
|
+
verticesStack.push(x, y, ci, vx1, vy1, ci, vx2, vy2, ci);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
drawStack.push(n * 3);
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
$.circle = (x, y, d) => $.ellipse(x, y, d, d);
|
|
243
|
+
|
|
244
|
+
$.noStroke = () => {};
|
|
245
|
+
|
|
246
|
+
$.background = () => {};
|
|
247
|
+
|
|
248
|
+
$._hooks.preRender.push(() => {
|
|
249
|
+
const vertexBuffer = Q5.device.createBuffer({
|
|
250
|
+
size: verticesStack.length * 6,
|
|
251
|
+
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
Q5.device.queue.writeBuffer(vertexBuffer, 0, new Float32Array(verticesStack));
|
|
255
|
+
$.pass.setVertexBuffer(0, vertexBuffer);
|
|
256
|
+
|
|
257
|
+
const colorBuffer = Q5.device.createBuffer({
|
|
258
|
+
size: colorsStack.length * 4,
|
|
259
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
Q5.device.queue.writeBuffer(colorBuffer, 0, new Float32Array(colorsStack));
|
|
263
|
+
|
|
264
|
+
const bindGroup = Q5.device.createBindGroup({
|
|
265
|
+
layout: $.pipelines[0].getBindGroupLayout(0),
|
|
266
|
+
entries: [
|
|
267
|
+
{
|
|
268
|
+
binding: 0,
|
|
269
|
+
resource: {
|
|
270
|
+
buffer: colorBuffer,
|
|
271
|
+
offset: 0,
|
|
272
|
+
size: colorsStack.length * 4
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
]
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// set the bind group once before rendering
|
|
279
|
+
$.pass.setBindGroup(0, bindGroup);
|
|
280
|
+
});
|
|
281
|
+
};
|
|
@@ -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 rendering support to q5.
|
|
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
|
+
Implemented functions:
|
|
139
|
+
|
|
140
|
+
`createCanvas`, `resizeCanvas`
|
|
141
|
+
|
|
142
|
+
## webgpu-drawing
|
|
143
|
+
|
|
144
|
+
> Uses `colorMode('rgb', 'float')` by default. Changing it to 'oklch' is not supported yet for the webgpu renderer.
|
|
145
|
+
|
|
146
|
+
> All basic shapes are drawn from their center. Strokes are not implemented yet.
|
|
147
|
+
|
|
148
|
+
q5's WebGPU renderer drawing functions like `rect` don't actually draw anything to 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 addon 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 typically 2-3x faster than canvas 2d.
|
|
149
|
+
|
|
150
|
+
Hooks into the q5-core `draw` loop were added to support webgpu rendering: `_beginRender`, `_render`, `_finishRender`.
|
|
151
|
+
|
|
152
|
+
The current implementation is provided to give developers a taste of what q5 programming will be like with webgpu. Significant changes need to be made to support transformations such as rotation and scaling.
|
|
153
|
+
|
|
154
|
+
Implemented functions:
|
|
155
|
+
|
|
156
|
+
`fill`, `rect`, `circle`, `ellipse`, `triangle`, `beginShape`, `vertex`, `endShape`
|
|
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.
|