q5 2.5.4 → 2.6.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 +31 -337
- package/package.json +2 -2
- package/q5.d.ts +81 -20
- package/q5.js +620 -530
- package/q5.min.js +2 -2
- package/src/q5-2d-canvas.js +2 -4
- package/src/q5-core.js +2 -2
- package/src/q5-util.js +1 -1
- package/src/q5-webgpu-canvas.js +195 -55
- package/src/q5-webgpu-drawing.js +298 -291
- package/src/q5-webgpu-image.js +25 -38
- package/src/q5-webgpu-text.js +95 -137
- package/src/readme.md +27 -59
package/src/q5-webgpu-canvas.js
CHANGED
|
@@ -11,18 +11,30 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
11
11
|
c.width = $.width = 500;
|
|
12
12
|
c.height = $.height = 500;
|
|
13
13
|
|
|
14
|
-
if ($.colorMode) $.colorMode('rgb',
|
|
14
|
+
if ($.colorMode) $.colorMode('rgb', 1);
|
|
15
15
|
|
|
16
|
-
let pass
|
|
16
|
+
let pass,
|
|
17
|
+
mainView,
|
|
18
|
+
colorsLayout,
|
|
19
|
+
colorIndex = 1,
|
|
20
|
+
colorStackIndex = 8;
|
|
17
21
|
|
|
18
|
-
$.
|
|
22
|
+
$._pipelineConfigs = [];
|
|
23
|
+
$._pipelines = [];
|
|
19
24
|
|
|
20
25
|
// local variables used for slightly better performance
|
|
21
26
|
// stores pipeline shifts and vertex counts/image indices
|
|
22
27
|
let drawStack = ($.drawStack = []);
|
|
23
28
|
|
|
24
29
|
// colors used for each draw call
|
|
25
|
-
|
|
30
|
+
|
|
31
|
+
let colorStack = ($.colorStack = new Float32Array(1e6));
|
|
32
|
+
|
|
33
|
+
// prettier-ignore
|
|
34
|
+
colorStack.set([
|
|
35
|
+
0, 0, 0, 1, // black
|
|
36
|
+
1, 1, 1, 1 // white
|
|
37
|
+
]);
|
|
26
38
|
|
|
27
39
|
$._transformLayout = Q5.device.createBindGroupLayout({
|
|
28
40
|
label: 'transformLayout',
|
|
@@ -46,32 +58,70 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
46
58
|
]
|
|
47
59
|
});
|
|
48
60
|
|
|
49
|
-
|
|
61
|
+
colorsLayout = Q5.device.createBindGroupLayout({
|
|
62
|
+
label: 'colorsLayout',
|
|
63
|
+
entries: [
|
|
64
|
+
{
|
|
65
|
+
binding: 0,
|
|
66
|
+
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
|
|
67
|
+
buffer: {
|
|
68
|
+
type: 'read-only-storage',
|
|
69
|
+
hasDynamicOffset: false
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
$.bindGroupLayouts = [$._transformLayout, colorsLayout];
|
|
50
76
|
|
|
51
77
|
let uniformBuffer = Q5.device.createBuffer({
|
|
52
78
|
size: 8, // Size of two floats
|
|
53
79
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
54
80
|
});
|
|
55
81
|
|
|
82
|
+
let createMainView = () => {
|
|
83
|
+
mainView = Q5.device
|
|
84
|
+
.createTexture({
|
|
85
|
+
size: [$.canvas.width, $.canvas.height],
|
|
86
|
+
sampleCount: 4,
|
|
87
|
+
format: 'bgra8unorm',
|
|
88
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT
|
|
89
|
+
})
|
|
90
|
+
.createView();
|
|
91
|
+
};
|
|
92
|
+
|
|
56
93
|
$._createCanvas = (w, h, opt) => {
|
|
57
94
|
q.ctx = q.drawingContext = c.getContext('webgpu');
|
|
58
95
|
|
|
59
96
|
opt.format ??= navigator.gpu.getPreferredCanvasFormat();
|
|
60
97
|
opt.device ??= Q5.device;
|
|
61
98
|
|
|
99
|
+
// needed for other blend modes but couldn't get it working
|
|
100
|
+
// opt.alphaMode = 'premultiplied';
|
|
101
|
+
|
|
62
102
|
$.ctx.configure(opt);
|
|
63
103
|
|
|
64
104
|
Q5.device.queue.writeBuffer(uniformBuffer, 0, new Float32Array([$.canvas.hw, $.canvas.hh]));
|
|
65
105
|
|
|
106
|
+
createMainView();
|
|
107
|
+
|
|
66
108
|
return c;
|
|
67
109
|
};
|
|
68
110
|
|
|
69
111
|
$._resizeCanvas = (w, h) => {
|
|
70
112
|
$._setCanvasSize(w, h);
|
|
113
|
+
createMainView();
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
$.pixelDensity = (v) => {
|
|
117
|
+
if (!v || v == $._pixelDensity) return $._pixelDensity;
|
|
118
|
+
$._pixelDensity = v;
|
|
119
|
+
$._setCanvasSize(c.w, c.h);
|
|
120
|
+
createMainView();
|
|
121
|
+
return v;
|
|
71
122
|
};
|
|
72
123
|
|
|
73
124
|
// current color index, used to associate a vertex with a color
|
|
74
|
-
let colorIndex = 0;
|
|
75
125
|
let addColor = (r, g, b, a = 1) => {
|
|
76
126
|
if (typeof r == 'string') r = $.color(r);
|
|
77
127
|
else if (b == undefined) {
|
|
@@ -79,21 +129,35 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
79
129
|
a = g ?? 1;
|
|
80
130
|
g = b = r;
|
|
81
131
|
}
|
|
82
|
-
if (r._q5Color)
|
|
83
|
-
|
|
132
|
+
if (r._q5Color) {
|
|
133
|
+
a = r.a;
|
|
134
|
+
b = r.b;
|
|
135
|
+
g = r.g;
|
|
136
|
+
r = r.r;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let cs = colorStack,
|
|
140
|
+
i = colorStackIndex;
|
|
141
|
+
cs[i++] = r;
|
|
142
|
+
cs[i++] = g;
|
|
143
|
+
cs[i++] = b;
|
|
144
|
+
cs[i++] = a;
|
|
145
|
+
colorStackIndex = i;
|
|
146
|
+
|
|
84
147
|
colorIndex++;
|
|
85
148
|
};
|
|
86
149
|
|
|
87
|
-
$._fillIndex = $._strokeIndex =
|
|
150
|
+
$._fillIndex = $._strokeIndex = 0;
|
|
151
|
+
$._doFill = $._doStroke = true;
|
|
88
152
|
|
|
89
153
|
$.fill = (r, g, b, a) => {
|
|
90
154
|
addColor(r, g, b, a);
|
|
91
|
-
$._doFill = true;
|
|
155
|
+
$._doFill = $._fillSet = true;
|
|
92
156
|
$._fillIndex = colorIndex;
|
|
93
157
|
};
|
|
94
158
|
$.stroke = (r, g, b, a) => {
|
|
95
159
|
addColor(r, g, b, a);
|
|
96
|
-
$._doStroke = true;
|
|
160
|
+
$._doStroke = $._strokeSet = true;
|
|
97
161
|
$._strokeIndex = colorIndex;
|
|
98
162
|
};
|
|
99
163
|
|
|
@@ -121,7 +185,7 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
121
185
|
$._matrixDirty = false;
|
|
122
186
|
|
|
123
187
|
// array to store transformation matrices for the render pass
|
|
124
|
-
|
|
188
|
+
let transformStates = [$._matrix.slice()];
|
|
125
189
|
|
|
126
190
|
// stack to keep track of transformation matrix indexes
|
|
127
191
|
$._transformIndexStack = [];
|
|
@@ -238,8 +302,8 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
238
302
|
|
|
239
303
|
// Function to save the current matrix state if dirty
|
|
240
304
|
$._saveMatrix = () => {
|
|
241
|
-
|
|
242
|
-
$._transformIndex =
|
|
305
|
+
transformStates.push($._matrix.slice());
|
|
306
|
+
$._transformIndex = transformStates.length - 1;
|
|
243
307
|
$._matrixDirty = false;
|
|
244
308
|
};
|
|
245
309
|
|
|
@@ -254,7 +318,7 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
254
318
|
}
|
|
255
319
|
// Pop the last matrix index and set it as the current matrix index
|
|
256
320
|
let idx = $._transformIndexStack.pop();
|
|
257
|
-
$._matrix =
|
|
321
|
+
$._matrix = transformStates[idx].slice();
|
|
258
322
|
$._transformIndex = idx;
|
|
259
323
|
$._matrixDirty = false;
|
|
260
324
|
};
|
|
@@ -295,6 +359,68 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
295
359
|
return [l, r, t, b];
|
|
296
360
|
};
|
|
297
361
|
|
|
362
|
+
// prettier-ignore
|
|
363
|
+
let blendFactors = [
|
|
364
|
+
'zero', // 0
|
|
365
|
+
'one', // 1
|
|
366
|
+
'src-alpha', // 2
|
|
367
|
+
'one-minus-src-alpha', // 3
|
|
368
|
+
'dst', // 4
|
|
369
|
+
'dst-alpha', // 5
|
|
370
|
+
'one-minus-dst-alpha', // 6
|
|
371
|
+
'one-minus-src' // 7
|
|
372
|
+
];
|
|
373
|
+
let blendOps = [
|
|
374
|
+
'add', // 0
|
|
375
|
+
'subtract', // 1
|
|
376
|
+
'reverse-subtract', // 2
|
|
377
|
+
'min', // 3
|
|
378
|
+
'max' // 4
|
|
379
|
+
];
|
|
380
|
+
|
|
381
|
+
const blendModes = {
|
|
382
|
+
normal: [2, 3, 0, 2, 3, 0],
|
|
383
|
+
// destination_over: [6, 1, 0, 6, 1, 0],
|
|
384
|
+
additive: [1, 1, 0, 1, 1, 0]
|
|
385
|
+
// source_in: [5, 0, 0, 5, 0, 0],
|
|
386
|
+
// destination_in: [0, 2, 0, 0, 2, 0],
|
|
387
|
+
// source_out: [6, 0, 0, 6, 0, 0],
|
|
388
|
+
// destination_out: [0, 3, 0, 0, 3, 0],
|
|
389
|
+
// source_atop: [5, 3, 0, 5, 3, 0],
|
|
390
|
+
// destination_atop: [6, 2, 0, 6, 2, 0]
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
$.blendConfigs = {};
|
|
394
|
+
|
|
395
|
+
for (const [name, mode] of Object.entries(blendModes)) {
|
|
396
|
+
$.blendConfigs[name] = {
|
|
397
|
+
color: {
|
|
398
|
+
srcFactor: blendFactors[mode[0]],
|
|
399
|
+
dstFactor: blendFactors[mode[1]],
|
|
400
|
+
operation: blendOps[mode[2]]
|
|
401
|
+
},
|
|
402
|
+
alpha: {
|
|
403
|
+
srcFactor: blendFactors[mode[3]],
|
|
404
|
+
dstFactor: blendFactors[mode[4]],
|
|
405
|
+
operation: blendOps[mode[5]]
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
$._blendMode = 'normal';
|
|
411
|
+
$.blendMode = (mode) => {
|
|
412
|
+
if (mode == $._blendMode) return;
|
|
413
|
+
if (mode == 'source-over') mode = 'normal';
|
|
414
|
+
if (mode == 'lighter') mode = 'additive';
|
|
415
|
+
mode = mode.toLowerCase().replace(/[ -]/g, '_');
|
|
416
|
+
$._blendMode = mode;
|
|
417
|
+
|
|
418
|
+
for (let i = 0; i < $._pipelines.length; i++) {
|
|
419
|
+
$._pipelineConfigs[i].fragment.targets[0].blend = $.blendConfigs[mode];
|
|
420
|
+
$._pipelines[i] = Q5.device.createRenderPipeline($._pipelineConfigs[i]);
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
|
|
298
424
|
$.clear = () => {};
|
|
299
425
|
|
|
300
426
|
$._beginRender = () => {
|
|
@@ -304,7 +430,8 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
304
430
|
label: 'q5-webgpu',
|
|
305
431
|
colorAttachments: [
|
|
306
432
|
{
|
|
307
|
-
view:
|
|
433
|
+
view: mainView,
|
|
434
|
+
resolveTarget: $.ctx.getCurrentTexture().createView(),
|
|
308
435
|
loadOp: 'clear',
|
|
309
436
|
storeOp: 'store'
|
|
310
437
|
}
|
|
@@ -315,58 +442,62 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
315
442
|
$._render = () => {
|
|
316
443
|
if (transformStates.length > 1 || !$._transformBindGroup) {
|
|
317
444
|
let transformBuffer = Q5.device.createBuffer({
|
|
318
|
-
size: transformStates.length * 64, //
|
|
319
|
-
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
445
|
+
size: transformStates.length * 64, // 64 is the size of 16 floats
|
|
446
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
447
|
+
mappedAtCreation: true
|
|
320
448
|
});
|
|
321
449
|
|
|
322
|
-
|
|
450
|
+
new Float32Array(transformBuffer.getMappedRange()).set(transformStates.flat());
|
|
451
|
+
transformBuffer.unmap();
|
|
323
452
|
|
|
324
453
|
$._transformBindGroup = Q5.device.createBindGroup({
|
|
325
454
|
layout: $._transformLayout,
|
|
326
455
|
entries: [
|
|
327
|
-
{
|
|
328
|
-
|
|
329
|
-
resource: {
|
|
330
|
-
buffer: uniformBuffer
|
|
331
|
-
}
|
|
332
|
-
},
|
|
333
|
-
{
|
|
334
|
-
binding: 1,
|
|
335
|
-
resource: {
|
|
336
|
-
buffer: transformBuffer
|
|
337
|
-
}
|
|
338
|
-
}
|
|
456
|
+
{ binding: 0, resource: { buffer: uniformBuffer } },
|
|
457
|
+
{ binding: 1, resource: { buffer: transformBuffer } }
|
|
339
458
|
]
|
|
340
459
|
});
|
|
341
460
|
}
|
|
342
461
|
|
|
343
462
|
pass.setBindGroup(0, $._transformBindGroup);
|
|
344
463
|
|
|
464
|
+
let colorsBuffer = Q5.device.createBuffer({
|
|
465
|
+
size: colorStackIndex * 4,
|
|
466
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
467
|
+
mappedAtCreation: true
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
new Float32Array(colorsBuffer.getMappedRange()).set(colorStack.slice(0, colorStackIndex));
|
|
471
|
+
colorsBuffer.unmap();
|
|
472
|
+
|
|
473
|
+
$._colorsBindGroup = Q5.device.createBindGroup({
|
|
474
|
+
layout: colorsLayout,
|
|
475
|
+
entries: [{ binding: 0, resource: { buffer: colorsBuffer } }]
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
$.pass.setBindGroup(1, $._colorsBindGroup);
|
|
479
|
+
|
|
345
480
|
for (let m of $._hooks.preRender) m();
|
|
346
481
|
|
|
347
|
-
let drawVertOffset = 0
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
482
|
+
let drawVertOffset = 0,
|
|
483
|
+
imageVertOffset = 0,
|
|
484
|
+
textCharOffset = 0,
|
|
485
|
+
curPipelineIndex = -1,
|
|
486
|
+
curTextureIndex = -1;
|
|
352
487
|
|
|
353
|
-
for (let i = 0; i < drawStack.length; i +=
|
|
488
|
+
for (let i = 0; i < drawStack.length; i += 3) {
|
|
354
489
|
let v = drawStack[i + 1];
|
|
355
|
-
|
|
356
|
-
if (drawStack[i] == -1) {
|
|
357
|
-
v();
|
|
358
|
-
continue;
|
|
359
|
-
}
|
|
490
|
+
let o = drawStack[i + 2];
|
|
360
491
|
|
|
361
492
|
if (curPipelineIndex != drawStack[i]) {
|
|
362
493
|
curPipelineIndex = drawStack[i];
|
|
363
|
-
pass.setPipeline($.
|
|
494
|
+
pass.setPipeline($._pipelines[curPipelineIndex]);
|
|
364
495
|
}
|
|
365
496
|
|
|
366
497
|
if (curPipelineIndex == 0) {
|
|
367
498
|
// v is the number of vertices
|
|
368
|
-
pass.
|
|
369
|
-
drawVertOffset +=
|
|
499
|
+
pass.drawIndexed(v, 1, 0, drawVertOffset);
|
|
500
|
+
drawVertOffset += o;
|
|
370
501
|
} else if (curPipelineIndex == 1) {
|
|
371
502
|
if (curTextureIndex != v) {
|
|
372
503
|
// v is the texture index
|
|
@@ -375,7 +506,7 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
375
506
|
pass.draw(6, 1, imageVertOffset);
|
|
376
507
|
imageVertOffset += 6;
|
|
377
508
|
} else if (curPipelineIndex == 2) {
|
|
378
|
-
pass.setBindGroup(2, $.
|
|
509
|
+
pass.setBindGroup(2, $._fonts[o].bindGroup);
|
|
379
510
|
pass.setBindGroup(3, $._textBindGroup);
|
|
380
511
|
|
|
381
512
|
// v is the number of characters in the text
|
|
@@ -391,30 +522,39 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
391
522
|
pass.end();
|
|
392
523
|
let commandBuffer = $.encoder.finish();
|
|
393
524
|
Q5.device.queue.submit([commandBuffer]);
|
|
525
|
+
|
|
394
526
|
q.pass = $.encoder = null;
|
|
395
527
|
|
|
396
528
|
// clear the stacks for the next frame
|
|
397
529
|
$.drawStack.length = 0;
|
|
398
|
-
|
|
399
|
-
|
|
530
|
+
colorIndex = 1;
|
|
531
|
+
colorStackIndex = 8;
|
|
400
532
|
rotation = 0;
|
|
401
|
-
|
|
533
|
+
transformStates.length = 1;
|
|
402
534
|
$._transformIndexStack.length = 0;
|
|
403
535
|
};
|
|
404
536
|
};
|
|
405
537
|
|
|
406
|
-
Q5.
|
|
407
|
-
if (!scope || scope == 'global') Q5._hasGlobal = true;
|
|
538
|
+
Q5.initWebGPU = async () => {
|
|
408
539
|
if (!navigator.gpu) {
|
|
409
540
|
console.warn('q5 WebGPU not supported on this browser!');
|
|
541
|
+
return false;
|
|
542
|
+
}
|
|
543
|
+
if (!Q5.device) {
|
|
544
|
+
let adapter = await navigator.gpu.requestAdapter();
|
|
545
|
+
if (!adapter) throw new Error('No appropriate GPUAdapter found.');
|
|
546
|
+
Q5.device = await adapter.requestDevice();
|
|
547
|
+
}
|
|
548
|
+
return true;
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
Q5.webgpu = async function (scope, parent) {
|
|
552
|
+
if (!scope || scope == 'global') Q5._hasGlobal = true;
|
|
553
|
+
if (!(await Q5.initWebGPU())) {
|
|
410
554
|
let q = new Q5(scope, parent);
|
|
411
555
|
q.colorMode('rgb', 1);
|
|
412
556
|
q._beginRender = () => q.translate(q.canvas.hw, q.canvas.hh);
|
|
413
557
|
return q;
|
|
414
558
|
}
|
|
415
|
-
let adapter = await navigator.gpu.requestAdapter();
|
|
416
|
-
if (!adapter) throw new Error('No appropriate GPUAdapter found.');
|
|
417
|
-
Q5.device = await adapter.requestDevice();
|
|
418
|
-
|
|
419
559
|
return new Q5(scope, parent, 'webgpu');
|
|
420
560
|
};
|