q5 2.21.4 → 2.22.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/q5.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * q5.js
3
- * @version 2.21
3
+ * @version 2.22
4
4
  * @author quinton-ashley, Tezumie, and LingDong-
5
5
  * @license LGPL-3.0
6
6
  * @class Q5
@@ -41,7 +41,8 @@ function Q5(scope, parent, renderer) {
41
41
 
42
42
  $.canvas = $.ctx = $.drawingContext = null;
43
43
  $.pixels = [];
44
- let looper = null;
44
+ let looper = null,
45
+ useRAF = true;
45
46
 
46
47
  $.frameCount = 0;
47
48
  $.deltaTime = 16;
@@ -83,13 +84,22 @@ function Q5(scope, parent, renderer) {
83
84
  $._didResize = false;
84
85
  }
85
86
 
86
- if ($._loop) looper = raf($._draw);
87
- else if ($.frameCount && !$._redraw) return;
87
+ if ($._loop) {
88
+ if (useRAF) looper = raf($._draw);
89
+ else {
90
+ let nextTS = ts + $._targetFrameDuration;
91
+ let frameDelay = nextTS - performance.now();
92
+ while (frameDelay < 0) frameDelay += $._targetFrameDuration;
93
+ log(frameDelay);
94
+ looper = setTimeout(() => $._draw(nextTS), frameDelay);
95
+ }
96
+ } else if ($.frameCount && !$._redraw) return;
88
97
 
89
- if (looper && $.frameCount) {
90
- let time_since_last = ts - $._lastFrameTime;
91
- if (time_since_last < $._targetFrameDuration - 4) return;
98
+ if ($.frameCount && useRAF) {
99
+ let timeSinceLast = ts - $._lastFrameTime;
100
+ if (timeSinceLast < $._targetFrameDuration - 4) return;
92
101
  }
102
+
93
103
  q.deltaTime = ts - $._lastFrameTime;
94
104
  $._frameRate = 1000 / $.deltaTime;
95
105
  q.frameCount++;
@@ -100,7 +110,6 @@ function Q5(scope, parent, renderer) {
100
110
  try {
101
111
  $.draw();
102
112
  } catch (e) {
103
- if (!Q5.disableFriendlyErrors && $._askAI) $._askAI(e);
104
113
  if (!Q5.errorTolerant) $.noLoop();
105
114
  throw e;
106
115
  }
@@ -117,6 +126,10 @@ function Q5(scope, parent, renderer) {
117
126
  };
118
127
  $.noLoop = () => {
119
128
  $._loop = false;
129
+ if (looper) {
130
+ if (useRAF) cancelAnimationFrame(looper);
131
+ else clearTimeout(looper);
132
+ }
120
133
  looper = null;
121
134
  };
122
135
  $.loop = () => {
@@ -140,6 +153,14 @@ function Q5(scope, parent, renderer) {
140
153
  if (hz) {
141
154
  $._targetFrameRate = hz;
142
155
  $._targetFrameDuration = 1000 / hz;
156
+
157
+ if ($._loop && $._setupDone && looper != null) {
158
+ if (useRAF) cancelAnimationFrame(looper);
159
+ else clearTimeout(looper);
160
+ looper = null;
161
+ }
162
+ useRAF = hz <= 60;
163
+ setTimeout(() => $._draw(), $._targetFrameDuration);
143
164
  }
144
165
  return $._frameRate;
145
166
  };
@@ -243,14 +264,7 @@ function Q5(scope, parent, renderer) {
243
264
  for (let k of userFns) {
244
265
  if (!t[k]) $[k] = () => {};
245
266
  else if ($._isGlobal) {
246
- $[k] = (event) => {
247
- try {
248
- return t[k](event);
249
- } catch (e) {
250
- if ($._askAI) $._askAI(e);
251
- throw e;
252
- }
253
- };
267
+ $[k] = (event) => t[k](event);
254
268
  }
255
269
  }
256
270
 
@@ -314,7 +328,7 @@ function createCanvas(w, h, opt) {
314
328
  }
315
329
  }
316
330
 
317
- Q5.version = Q5.VERSION = '2.21';
331
+ Q5.version = Q5.VERSION = '2.22';
318
332
 
319
333
  if (typeof document == 'object') {
320
334
  document.addEventListener('DOMContentLoaded', () => {
@@ -407,7 +421,6 @@ Q5.modules.canvas = ($, q) => {
407
421
  if ($._hooks) {
408
422
  for (let m of $._hooks.postCanvas) m();
409
423
  }
410
- if ($._beginRender) $._beginRender();
411
424
 
412
425
  if ($._addEventMethods) $._addEventMethods(c);
413
426
 
@@ -514,7 +527,7 @@ Q5.modules.canvas = ($, q) => {
514
527
  $.pixelDensity = (v) => {
515
528
  if (!v || v == $._pixelDensity) return $._pixelDensity;
516
529
  $._pixelDensity = v;
517
- $._setCanvasSize(c.w, c.h);
530
+ $._resizeCanvas(c.w, c.h);
518
531
  return v;
519
532
  };
520
533
 
@@ -688,6 +701,22 @@ Q5.renderers.c2d.canvas = ($, q) => {
688
701
 
689
702
  if ($._scope == 'image') return;
690
703
 
704
+ $.background = function (c) {
705
+ $.ctx.save();
706
+ $.ctx.resetTransform();
707
+ $.ctx.globalAlpha = 1;
708
+ if (c.canvas) $.image(c, 0, 0, $.canvas.width, $.canvas.height);
709
+ else {
710
+ if (Q5.Color && !c._q5Color) {
711
+ if (typeof c != 'string') c = $.color(...arguments);
712
+ else if ($._namedColors[c]) c = $.color(...$._namedColors[c]);
713
+ }
714
+ $.ctx.fillStyle = c.toString();
715
+ $.ctx.fillRect(0, 0, $.canvas.width, $.canvas.height);
716
+ }
717
+ $.ctx.restore();
718
+ };
719
+
691
720
  $._resizeCanvas = (w, h) => {
692
721
  let t = {};
693
722
  for (let prop in $.ctx) {
@@ -868,22 +897,6 @@ Q5.renderers.c2d.shapes = ($) => {
868
897
 
869
898
  // DRAWING
870
899
 
871
- $.background = function (c) {
872
- $.ctx.save();
873
- $.ctx.resetTransform();
874
- $.ctx.globalAlpha = 1;
875
- if (c.canvas) $.image(c, 0, 0, $.canvas.width, $.canvas.height);
876
- else {
877
- if (Q5.Color && !c._q5Color) {
878
- if (typeof c != 'string') c = $.color(...arguments);
879
- else if ($._namedColors[c]) c = $.color(...$._namedColors[c]);
880
- }
881
- $.ctx.fillStyle = c.toString();
882
- $.ctx.fillRect(0, 0, $.canvas.width, $.canvas.height);
883
- }
884
- $.ctx.restore();
885
- };
886
-
887
900
  $.line = (x0, y0, x1, y1) => {
888
901
  if ($._doStroke) {
889
902
  $._da && ((x0 *= $._da), (y0 *= $._da), (x1 *= $._da), (y1 *= $._da));
@@ -1217,8 +1230,8 @@ Q5.renderers.c2d.shapes = ($) => {
1217
1230
  $.erase = function (fillAlpha = 255, strokeAlpha = 255) {
1218
1231
  $.ctx.save();
1219
1232
  $.ctx.globalCompositeOperation = 'destination-out';
1220
- $.ctx.fillStyle = `rgba(0, 0, 0, ${fillAlpha / 255})`;
1221
- $.ctx.strokeStyle = `rgba(0, 0, 0, ${strokeAlpha / 255})`;
1233
+ $.ctx.fillStyle = `rgb(0 0 0 / ${fillAlpha / 255})`;
1234
+ $.ctx.strokeStyle = `rgb(0 0 0 / ${strokeAlpha / 255})`;
1222
1235
  };
1223
1236
 
1224
1237
  $.noErase = function () {
@@ -2095,21 +2108,29 @@ Q5.modules.ai = ($) => {
2095
2108
  };
2096
2109
  Q5.modules.color = ($, q) => {
2097
2110
  $.RGB = $.RGBA = $._colorMode = 'rgb';
2098
- $.SRGB = 'srgb';
2111
+ $.HSL = 'hsl';
2112
+ $.HSB = 'hsb';
2099
2113
  $.OKLCH = 'oklch';
2100
2114
 
2101
- $.colorMode = (mode, format) => {
2115
+ $.SRGB = 'srgb';
2116
+ $.DISPLAY_P3 = 'display-p3';
2117
+
2118
+ $.colorMode = (mode, format, gamut) => {
2102
2119
  $._colorMode = mode;
2103
- let srgb = $.canvas.colorSpace == 'srgb' || mode == 'srgb';
2120
+ let srgb = $.canvas.colorSpace == 'srgb' || gamut == 'srgb';
2104
2121
  format ??= srgb ? 'integer' : 'float';
2105
2122
  $._colorFormat = format == 'float' || format == 1 ? 1 : 255;
2106
2123
  if (mode == 'oklch') {
2107
2124
  q.Color = Q5.ColorOKLCH;
2125
+ } else if (mode == 'hsl') {
2126
+ q.Color = srgb ? Q5.ColorHSL : Q5.ColorHSL_P3;
2127
+ } else if (mode == 'hsb') {
2128
+ q.Color = srgb ? Q5.ColorHSB : Q5.ColorHSB_P3;
2108
2129
  } else {
2109
2130
  if ($._colorFormat == 255) {
2110
- q.Color = srgb ? Q5.ColorRGBA_8 : Q5.ColorRGBA_P3_8;
2131
+ q.Color = srgb ? Q5.ColorRGB_8 : Q5.ColorRGB_P3_8;
2111
2132
  } else {
2112
- q.Color = srgb ? Q5.ColorRGBA : Q5.ColorRGBA_P3;
2133
+ q.Color = srgb ? Q5.ColorRGB : Q5.ColorRGB_P3;
2113
2134
  }
2114
2135
  $._colorMode = 'rgb';
2115
2136
  }
@@ -2203,7 +2224,8 @@ Q5.modules.color = ($, q) => {
2203
2224
 
2204
2225
  $.lightness = (c) => {
2205
2226
  if (c.l) return c.l;
2206
- return ((0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b) * 100) / 255;
2227
+ let l = (0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b) * 100;
2228
+ return $._colorFormat == 255 ? l / 255 : l;
2207
2229
  };
2208
2230
  $.hue = (c) => {
2209
2231
  if (c.h) return c.h;
@@ -2248,6 +2270,12 @@ Q5.Color = class {
2248
2270
  constructor() {
2249
2271
  this._q5Color = true;
2250
2272
  }
2273
+ get alpha() {
2274
+ return this.a;
2275
+ }
2276
+ set alpha(v) {
2277
+ this.a = v;
2278
+ }
2251
2279
  };
2252
2280
 
2253
2281
  Q5.ColorOKLCH = class extends Q5.Color {
@@ -2289,15 +2317,9 @@ Q5.ColorOKLCH = class extends Q5.Color {
2289
2317
  set hue(v) {
2290
2318
  this.h = v;
2291
2319
  }
2292
- get alpha() {
2293
- return this.a;
2294
- }
2295
- set alpha(v) {
2296
- this.a = v;
2297
- }
2298
2320
  };
2299
2321
 
2300
- Q5.ColorRGBA = class extends Q5.Color {
2322
+ Q5.ColorRGB = class extends Q5.Color {
2301
2323
  constructor(r, g, b, a) {
2302
2324
  super();
2303
2325
  this.r = r;
@@ -2317,6 +2339,7 @@ Q5.ColorRGBA = class extends Q5.Color {
2317
2339
  toString() {
2318
2340
  return `color(srgb ${this.r} ${this.g} ${this.b} / ${this.a})`;
2319
2341
  }
2342
+
2320
2343
  get red() {
2321
2344
  return this.r;
2322
2345
  }
@@ -2335,22 +2358,16 @@ Q5.ColorRGBA = class extends Q5.Color {
2335
2358
  set blue(v) {
2336
2359
  this.b = v;
2337
2360
  }
2338
- get alpha() {
2339
- return this.a;
2340
- }
2341
- set alpha(v) {
2342
- this.a = v;
2343
- }
2344
2361
  };
2345
2362
 
2346
- Q5.ColorRGBA_P3 = class extends Q5.ColorRGBA {
2363
+ Q5.ColorRGB_P3 = class extends Q5.ColorRGB {
2347
2364
  toString() {
2348
2365
  return `color(display-p3 ${this.r} ${this.g} ${this.b} / ${this.a})`;
2349
2366
  }
2350
2367
  };
2351
2368
 
2352
- // legacy 8-bit (0-255) integer color format
2353
- Q5.ColorRGBA_8 = class extends Q5.ColorRGBA {
2369
+ // legacy 8-bit (0-255) integer color format, srgb color space
2370
+ Q5.ColorRGB_8 = class extends Q5.ColorRGB {
2354
2371
  constructor(r, g, b, a) {
2355
2372
  super(r, g, b, a ?? 255);
2356
2373
  }
@@ -2373,7 +2390,7 @@ Q5.ColorRGBA_8 = class extends Q5.ColorRGBA {
2373
2390
  };
2374
2391
 
2375
2392
  // p3 10-bit color in integer color format, for backwards compatibility
2376
- Q5.ColorRGBA_P3_8 = class extends Q5.ColorRGBA {
2393
+ Q5.ColorRGB_P3_8 = class extends Q5.ColorRGB_8 {
2377
2394
  constructor(r, g, b, a) {
2378
2395
  super(r, g, b, a ?? 255);
2379
2396
  this._edited = true;
@@ -2418,6 +2435,159 @@ Q5.ColorRGBA_P3_8 = class extends Q5.ColorRGBA {
2418
2435
  return this._css;
2419
2436
  }
2420
2437
  };
2438
+
2439
+ Q5.ColorHSL = class extends Q5.Color {
2440
+ constructor(h, s, l, a) {
2441
+ super();
2442
+ this.h = h;
2443
+ this.s = s;
2444
+ this.l = l;
2445
+ this.a = a ?? 1;
2446
+ }
2447
+ get levels() {
2448
+ return [this.h, this.s, this.l, this.a];
2449
+ }
2450
+ equals(c) {
2451
+ return c && this.h == c.h && this.s == c.s && this.l == c.l && this.a == c.a;
2452
+ }
2453
+ isSameColor(c) {
2454
+ return c && this.h == c.h && this.s == c.s && this.l == c.l;
2455
+ }
2456
+ toString() {
2457
+ return `hsl(${this.h} ${this.s} ${this.l} / ${this.a})`;
2458
+ }
2459
+
2460
+ get hue() {
2461
+ return this.h;
2462
+ }
2463
+ set hue(v) {
2464
+ this.h = v;
2465
+ }
2466
+ get saturation() {
2467
+ return this.s;
2468
+ }
2469
+ set saturation(v) {
2470
+ this.s = v;
2471
+ }
2472
+ get lightness() {
2473
+ return this.l;
2474
+ }
2475
+ set lightness(v) {
2476
+ this.l = v;
2477
+ }
2478
+ };
2479
+
2480
+ Q5.ColorHSL_P3 = class extends Q5.ColorHSL {
2481
+ toString() {
2482
+ let o = Q5.HSLtoRGB(this.h, this.s, this.l);
2483
+ return `color(display-p3 ${o.join(' ')} / ${this.a})`;
2484
+ }
2485
+ };
2486
+
2487
+ Q5.ColorHSB = class extends Q5.ColorHSL {
2488
+ constructor(h, s, b, a) {
2489
+ super(h, s, b, a);
2490
+ delete this.l;
2491
+ this.b = b;
2492
+ }
2493
+ get levels() {
2494
+ return [this.h, this.s, this.b, this.a];
2495
+ }
2496
+ equals(c) {
2497
+ return c && this.h == c.h && this.s == c.s && this.b == c.b && this.a == c.a;
2498
+ }
2499
+ isSameColor(c) {
2500
+ return c && this.h == c.h && this.s == c.s && this.b == c.b;
2501
+ }
2502
+ toString() {
2503
+ let o = Q5.HSBtoHSL(this.h, this.s, this.b);
2504
+ return `hsl(${o.join(' ')} / ${this.a})`;
2505
+ }
2506
+
2507
+ get v() {
2508
+ return this.b;
2509
+ }
2510
+ set v(v) {
2511
+ this.b = v;
2512
+ }
2513
+ get brightness() {
2514
+ return this.b;
2515
+ }
2516
+ set brightness(v) {
2517
+ this.b = v;
2518
+ }
2519
+ get value() {
2520
+ return this.b;
2521
+ }
2522
+ set value(v) {
2523
+ this.b = v;
2524
+ }
2525
+ };
2526
+
2527
+ Q5.ColorHSB_P3 = class extends Q5.ColorHSB {
2528
+ toString() {
2529
+ let o = Q5.HSLtoRGB(...Q5.HSBtoHSL(this.h, this.s, this.b));
2530
+ return `color(display-p3 ${o.join(' ')} / ${this.a})`;
2531
+ }
2532
+ };
2533
+
2534
+ Q5.HSLtoRGB = (h, s, l) => {
2535
+ l /= 100;
2536
+ let m = (s / 100) * Math.min(l, 1 - l);
2537
+ let f = (n, k = (n + h / 30) % 12) => l - m * Math.max(Math.min(k - 3, 9 - k, 1), -1);
2538
+ return [f(0), f(8), f(4)];
2539
+ };
2540
+
2541
+ Q5.HSBtoHSL = (h, s, v, l = v * (1 - s / 200)) => [h, !l || l == 100 ? 0 : ((v - l) / Math.min(l, 100 - l)) * 100, l];
2542
+
2543
+ {
2544
+ const multiplyMatrices = (A, B) => [
2545
+ A[0] * B[0] + A[1] * B[1] + A[2] * B[2],
2546
+ A[3] * B[0] + A[4] * B[1] + A[5] * B[2],
2547
+ A[6] * B[0] + A[7] * B[1] + A[8] * B[2]
2548
+ ];
2549
+
2550
+ const oklch2oklab = (l, c, h) => [
2551
+ l,
2552
+ isNaN(h) ? 0 : c * Math.cos((h * Math.PI) / 180),
2553
+ isNaN(h) ? 0 : c * Math.sin((h * Math.PI) / 180)
2554
+ ];
2555
+
2556
+ const srgbLinear2rgb = (rgb) =>
2557
+ rgb.map((c) =>
2558
+ Math.max(
2559
+ 0,
2560
+ Math.min(1, Math.abs(c) > 0.0031308 ? (c < 0 ? -1 : 1) * (1.055 * Math.abs(c) ** (1 / 2.4) - 0.055) : 12.92 * c)
2561
+ )
2562
+ );
2563
+
2564
+ const oklab2xyz = (lab) => {
2565
+ const LMSg = multiplyMatrices(
2566
+ [
2567
+ 1, 0.3963377773761749, 0.2158037573099136, 1, -0.1055613458156586, -0.0638541728258133, 1, -0.0894841775298119,
2568
+ -1.2914855480194092
2569
+ ],
2570
+ lab
2571
+ );
2572
+ return multiplyMatrices(
2573
+ [
2574
+ 1.2268798758459243, -0.5578149944602171, 0.2813910456659647, -0.0405757452148008, 1.112286803280317,
2575
+ -0.0717110580655164, -0.0763729366746601, -0.4214933324022432, 1.5869240198367816
2576
+ ],
2577
+ LMSg.map((val) => val ** 3)
2578
+ );
2579
+ };
2580
+ const xyz2rgbLinear = (xyz) =>
2581
+ multiplyMatrices(
2582
+ [
2583
+ 3.2409699419045226, -1.537383177570094, -0.4986107602930034, -0.9692436362808796, 1.8759675015077202,
2584
+ 0.04155505740717559, 0.05563007969699366, -0.20397695888897652, 1.0569715142428786
2585
+ ],
2586
+ xyz
2587
+ );
2588
+
2589
+ Q5.OKLCHtoRGB = (l, c, h) => srgbLinear2rgb(xyz2rgbLinear(oklab2xyz(oklch2oklab(l, c, h))));
2590
+ }
2421
2591
  Q5.modules.display = ($) => {
2422
2592
  if (!$.canvas || $._scope == 'graphics') return;
2423
2593
 
@@ -3552,13 +3722,13 @@ Q5.modules.record = ($, q) => {
3552
3722
  .rec button,
3553
3723
  .rec select,
3554
3724
  .rec input,
3555
- .rec .record-timer {
3725
+ .rec span {
3556
3726
  font-family: sans-serif;
3557
3727
  font-size: 14px;
3558
3728
  padding: 2px 10px;
3559
3729
  border-radius: 18px;
3560
3730
  outline: none;
3561
- background-color: #232529;
3731
+ background: #232529;
3562
3732
  color: #d4dae6;
3563
3733
  box-shadow: #0000001a 0px 4px 12px;
3564
3734
  border: 1px solid #46494e;
@@ -3567,13 +3737,28 @@ Q5.modules.record = ($, q) => {
3567
3737
  transition: all 0.3s;
3568
3738
  }
3569
3739
 
3740
+ .rec .bitrate input {
3741
+ border-radius: 18px 0 0 18px;
3742
+ border-right: 0;
3743
+ width: 40px;
3744
+ padding: 2px 5px 2px 10px;
3745
+ text-align: right;
3746
+ }
3747
+
3748
+ .rec .bitrate span {
3749
+ border-radius: 0 18px 18px 0;
3750
+ border-left: 0;
3751
+ padding: 2px 10px 2px 5px;
3752
+ background: #333;
3753
+ }
3754
+
3570
3755
  .rec .record-button {
3571
3756
  color: #cc3e44;
3572
3757
  font-size: 18px;
3573
3758
  }
3574
3759
 
3575
3760
  .rec select:hover,
3576
- .rec button:hover { background-color: #292b30; }
3761
+ .rec button:hover { background: #292b30; }
3577
3762
 
3578
3763
  .rec button:disabled {
3579
3764
  opacity: 0.5;
@@ -3617,10 +3802,17 @@ Q5.modules.record = ($, q) => {
3617
3802
  formatSelect.title = 'Video Format';
3618
3803
  rec.append(formatSelect);
3619
3804
 
3805
+ let div = $.createEl('div');
3806
+ div.className = 'bitrate';
3807
+ div.style.display = 'flex';
3808
+ rec.append(div);
3809
+
3620
3810
  bitrateInput = $.createInput();
3621
- bitrateInput.title = 'Video Bitrate';
3622
- bitrateInput.style.width = '48px';
3623
- rec.append(bitrateInput);
3811
+ let span = document.createElement('span');
3812
+ span.textContent = 'mbps';
3813
+ bitrateInput.title = span.title = 'Video Bitrate';
3814
+ div.append(bitrateInput);
3815
+ div.append(span);
3624
3816
 
3625
3817
  rec.encoderSettings = {};
3626
3818
 
@@ -4534,9 +4726,11 @@ struct Q5 {
4534
4726
  $._pipelineConfigs = [];
4535
4727
  $._pipelines = [];
4536
4728
  $._buffers = [];
4729
+ $._prevFramePL = 0;
4537
4730
  $._framePL = 0;
4538
4731
 
4539
4732
  // local variables used for slightly better performance
4733
+
4540
4734
  // stores pipeline shifts and vertex counts/image indices
4541
4735
  let drawStack = ($.drawStack = []);
4542
4736
 
@@ -4573,7 +4767,7 @@ struct Q5 {
4573
4767
  $.bindGroupLayouts = [mainLayout];
4574
4768
 
4575
4769
  let uniformBuffer = Q5.device.createBuffer({
4576
- size: 64, // Size of four floats
4770
+ size: 64,
4577
4771
  usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
4578
4772
  });
4579
4773
 
@@ -4673,7 +4867,7 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
4673
4867
  fragment: {
4674
4868
  module: frameShader,
4675
4869
  entryPoint: 'fragMain',
4676
- targets: [{ format, blend: $.blendConfigs.normal }]
4870
+ targets: [{ format }]
4677
4871
  },
4678
4872
  primitive: { topology: 'triangle-strip' },
4679
4873
  multisample: { count: 4 }
@@ -4701,27 +4895,23 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
4701
4895
  createMainView();
4702
4896
  };
4703
4897
 
4704
- $.pixelDensity = (v) => {
4705
- if (!v || v == $._pixelDensity) return $._pixelDensity;
4706
- $._pixelDensity = v;
4707
- $._setCanvasSize(c.w, c.h);
4708
- createMainView();
4709
- return v;
4710
- };
4711
-
4712
- // current color index, used to associate a vertex with a color
4713
4898
  let addColor = (r, g, b, a = 1) => {
4714
- if (typeof r == 'string') r = $.color(r);
4715
- else if (b == undefined) {
4899
+ if (typeof r == 'string' || $._colorMode != 'rgb') {
4900
+ r = $.color(r, g, b, a);
4901
+ } else if (b == undefined) {
4716
4902
  // grayscale mode `fill(1, 0.5)`
4717
4903
  a = g ?? 1;
4718
4904
  g = b = r;
4719
4905
  }
4720
4906
  if (r._q5Color) {
4721
- a = r.a;
4722
- b = r.b;
4723
- g = r.g;
4724
- r = r.r;
4907
+ let c = r;
4908
+ if (c.r) ({ r, g, b, a } = c);
4909
+ else {
4910
+ if (c.c) c = Q5.OKLCHtoRGB(c.l, c.c, c.h);
4911
+ else if (c.l) c = Q5.HSLtoRGB(c.h, c.s, c.l);
4912
+ else c = Q5.HSLtoRGB(...Q5.HSBtoHSL(c.h, c.s, c.b));
4913
+ [r, g, b, a] = c;
4914
+ }
4725
4915
  }
4726
4916
 
4727
4917
  let cs = colorStack,
@@ -4754,7 +4944,6 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
4754
4944
  $._tint = colorIndex;
4755
4945
  };
4756
4946
  $.opacity = (a) => ($._globalAlpha = a);
4757
-
4758
4947
  $.noFill = () => ($._doFill = false);
4759
4948
  $.noStroke = () => ($._doStroke = false);
4760
4949
  $.noTint = () => ($._tint = 1);
@@ -4762,6 +4951,7 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
4762
4951
  $._strokeWeight = 1;
4763
4952
  $._hsw = 0.5;
4764
4953
  $._scaledSW = 1;
4954
+
4765
4955
  $.strokeWeight = (v) => {
4766
4956
  v = Math.abs(v);
4767
4957
  $._strokeWeight = v;
@@ -4912,7 +5102,7 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
4912
5102
  $._matrixDirty = true;
4913
5103
  };
4914
5104
 
4915
- // function to save the current matrix state if dirty
5105
+ // saves the current matrix state
4916
5106
  $._saveMatrix = () => {
4917
5107
  transforms.set(matrix, matrices.length * MATRIX_SIZE);
4918
5108
  $._matrixIndex = matrices.length;
@@ -5044,13 +5234,28 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
5044
5234
  };
5045
5235
 
5046
5236
  let shouldClear;
5237
+
5047
5238
  $.clear = () => {
5048
5239
  shouldClear = true;
5049
5240
  };
5050
5241
 
5051
- $._beginRender = () => {
5052
- if (encoder) return;
5242
+ $.background = (r, g, b, a) => {
5243
+ $.push();
5244
+ $.resetMatrix();
5245
+ if (r.canvas) {
5246
+ let img = r;
5247
+ $._imageMode = 'corner';
5248
+ $.image(img, -c.hw, -c.hh, c.w, c.h);
5249
+ } else {
5250
+ $._rectMode = 'corner';
5251
+ $.fill(r, g, b, a);
5252
+ $._doStroke = false;
5253
+ $.rect(-c.hw, -c.hh, c.w, c.h);
5254
+ }
5255
+ $.pop();
5256
+ };
5053
5257
 
5258
+ $._beginRender = () => {
5054
5259
  // swap the frame textures
5055
5260
  const temp = frameA;
5056
5261
  frameA = frameB;
@@ -5081,7 +5286,7 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
5081
5286
  });
5082
5287
 
5083
5288
  if (!shouldClear) {
5084
- pass.setPipeline($._pipelines[0]);
5289
+ pass.setPipeline($._pipelines[$._prevFramePL]);
5085
5290
  pass.setBindGroup(0, frameBindGroup);
5086
5291
  pass.draw(4);
5087
5292
  }
@@ -5098,6 +5303,8 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
5098
5303
  new Float32Array(transformBuffer.getMappedRange()).set(transforms.slice(0, matrices.length * MATRIX_SIZE));
5099
5304
  transformBuffer.unmap();
5100
5305
 
5306
+ $._buffers.push(transformBuffer);
5307
+
5101
5308
  let colorsBuffer = Q5.device.createBuffer({
5102
5309
  size: colorStackIndex * 4,
5103
5310
  usage: GPUBufferUsage.STORAGE,
@@ -5107,6 +5314,8 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
5107
5314
  new Float32Array(colorsBuffer.getMappedRange()).set(colorStack.slice(0, colorStackIndex));
5108
5315
  colorsBuffer.unmap();
5109
5316
 
5317
+ $._buffers.push(colorsBuffer);
5318
+
5110
5319
  $._uniforms = [
5111
5320
  $.width,
5112
5321
  $.height,
@@ -5167,7 +5376,7 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
5167
5376
  pass.setBindGroup(1, $._textureBindGroups[v]);
5168
5377
  pass.draw(4, 1, imageVertOffset);
5169
5378
  imageVertOffset += 4;
5170
- } else if (curPipelineIndex == 1 || curPipelineIndex >= 1000) {
5379
+ } else {
5171
5380
  // draw a shape
5172
5381
  // v is the number of vertices
5173
5382
  pass.draw(v, 1, drawVertOffset);
@@ -5176,7 +5385,7 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
5176
5385
  }
5177
5386
  };
5178
5387
 
5179
- $._finishRender = () => {
5388
+ $._finishRender = async () => {
5180
5389
  pass.end();
5181
5390
 
5182
5391
  pass = encoder.beginRenderPass({
@@ -5206,6 +5415,7 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
5206
5415
  pass.end();
5207
5416
 
5208
5417
  Q5.device.queue.submit([encoder.finish()]);
5418
+ $._pass = pass = encoder = null;
5209
5419
 
5210
5420
  // destroy buffers
5211
5421
  Q5.device.queue.onSubmittedWorkDone().then(() => {
@@ -5213,8 +5423,6 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
5213
5423
  $._buffers = [];
5214
5424
  });
5215
5425
 
5216
- $._pass = pass = encoder = null;
5217
-
5218
5426
  // clear the stacks for the next frame
5219
5427
  drawStack.splice(0, drawStack.length);
5220
5428
  colorIndex = 1;
@@ -5864,22 +6072,6 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
5864
6072
  $.endShape(true);
5865
6073
  };
5866
6074
 
5867
- $.background = (r, g, b, a) => {
5868
- $.push();
5869
- $.resetMatrix();
5870
- if (r.canvas) {
5871
- let img = r;
5872
- $._imageMode = 'corner';
5873
- $.image(img, -c.hw, -c.hh, c.w, c.h);
5874
- } else {
5875
- $._rectMode = 'corner';
5876
- $.fill(r, g, b, a);
5877
- $._doStroke = false;
5878
- $.rect(-c.hw, -c.hh, c.w, c.h);
5879
- }
5880
- $.pop();
5881
- };
5882
-
5883
6075
  $._hooks.preRender.push(() => {
5884
6076
  $._pass.setPipeline($._pipelines[1]);
5885
6077
 
@@ -7027,6 +7219,7 @@ Q5.renderers.webgpu.shaders = ($) => {
7027
7219
 
7028
7220
  let pl = plCounters[type];
7029
7221
  $._pipelines[pl] = Q5.device.createRenderPipeline(config);
7222
+ $._pipelines[pl].shader = shader;
7030
7223
  shader.pipelineIndex = pl;
7031
7224
 
7032
7225
  plCounters[type]++;
@@ -7041,15 +7234,17 @@ Q5.renderers.webgpu.shaders = ($) => {
7041
7234
  $.createTextShader = (code) => $._createShader(code, 'text');
7042
7235
 
7043
7236
  $.shader = (shader) => {
7044
- $['_' + shader.type + 'PL'] = shader.pipelineIndex;
7237
+ if (shader.applyBeforeDraw) $._prevFramePL = shader.pipelineIndex;
7238
+ else $['_' + shader.type + 'PL'] = shader.pipelineIndex;
7045
7239
  };
7046
7240
 
7047
7241
  $.resetShader = (type = 'shapes') => {
7242
+ if (type == 'frame') $._prevFramePL = 0;
7048
7243
  $['_' + type + 'PL'] = pipelineTypes.indexOf(type);
7049
7244
  };
7050
7245
 
7051
7246
  $.resetShaders = () => {
7052
- $._framePL = 0;
7247
+ $._prevFramePL = $._framePL = 0;
7053
7248
  $._shapesPL = 1;
7054
7249
  $._imagePL = 2;
7055
7250
  $._videoPL = 3;