q5 2.5.5 → 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.
@@ -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', 'float');
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
- $.pipelines = [];
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
- let colorsStack = ($.colorsStack = [1, 1, 1, 1]);
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
- $.bindGroupLayouts = [$._transformLayout];
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) colorsStack.push(r.r, r.g, r.b, r.a);
83
- else colorsStack.push(r, g, b, a);
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 = -1;
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
 
@@ -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: $.ctx.getCurrentTexture().createView(),
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, // Size of 16 floats
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
- Q5.device.queue.writeBuffer(transformBuffer, 0, new Float32Array(transformStates.flat()));
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
- binding: 0,
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
- let imageVertOffset = 0;
349
- let textCharOffset = 0;
350
- let curPipelineIndex = -1;
351
- let curTextureIndex = -1;
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 += 2) {
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($.pipelines[curPipelineIndex]);
494
+ pass.setPipeline($._pipelines[curPipelineIndex]);
364
495
  }
365
496
 
366
497
  if (curPipelineIndex == 0) {
367
498
  // v is the number of vertices
368
- pass.draw(v, 1, drawVertOffset);
369
- drawVertOffset += v;
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, $._font.bindGroup);
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,12 +522,13 @@ 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
- $.colorsStack.length = 4;
399
- colorIndex = 0;
530
+ colorIndex = 1;
531
+ colorStackIndex = 8;
400
532
  rotation = 0;
401
533
  transformStates.length = 1;
402
534
  $._transformIndexStack.length = 0;