q5 2.12.10 → 2.13.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/q5.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * q5.js
3
- * @version 2.12
3
+ * @version 2.13
4
4
  * @author quinton-ashley, Tezumie, and LingDong-
5
5
  * @license LGPL-3.0
6
6
  * @class Q5
@@ -575,6 +575,7 @@ Q5.modules.canvas = ($, q) => {
575
575
  '_strokeSet',
576
576
  '_fillSet',
577
577
  '_shadow',
578
+ '_doShadow',
578
579
  '_shadowOffsetX',
579
580
  '_shadowOffsetY',
580
581
  '_shadowBlur',
@@ -781,6 +782,7 @@ Q5.renderers.q2d.canvas = ($, q) => {
781
782
  $.noStroke = () => ($._doStroke = false);
782
783
  $.opacity = (a) => ($.ctx.globalAlpha = a);
783
784
 
785
+ $._doShadow = false;
784
786
  $._shadowOffsetX = $._shadowOffsetY = $._shadowBlur = 10;
785
787
 
786
788
  $.shadow = function (c) {
@@ -791,6 +793,7 @@ Q5.renderers.q2d.canvas = ($, q) => {
791
793
  }
792
794
  }
793
795
  $.ctx.shadowColor = $._shadow = c.toString();
796
+ $._doShadow = true;
794
797
 
795
798
  $.ctx.shadowOffsetX ||= $._shadowOffsetX;
796
799
  $.ctx.shadowOffsetY ||= $._shadowOffsetY;
@@ -804,6 +807,7 @@ Q5.renderers.q2d.canvas = ($, q) => {
804
807
  };
805
808
 
806
809
  $.noShadow = () => {
810
+ $._doShadow = false;
807
811
  $.ctx.shadowOffsetX = $.ctx.shadowOffsetY = $.ctx.shadowBlur = 0;
808
812
  };
809
813
 
@@ -845,13 +849,18 @@ Q5.renderers.q2d.canvas = ($, q) => {
845
849
  $.pushMatrix = () => $.ctx.save();
846
850
  $.popMatrix = () => $.ctx.restore();
847
851
 
852
+ let _popStyles = $.popStyles;
853
+
848
854
  $.popStyles = () => {
849
- let styles = $._styles.pop();
850
- for (let s of $._styleNames) $[s] = styles[s];
855
+ _popStyles();
851
856
 
852
857
  $.ctx.fillStyle = $._fill;
853
858
  $.ctx.strokeStyle = $._stroke;
854
859
  $.ctx.lineWidth = $._strokeWeight;
860
+ $.ctx.shadowColor = $._shadow;
861
+ $.ctx.shadowOffsetX = $._doShadow ? $._shadowOffsetX : 0;
862
+ $.ctx.shadowOffsetY = $._doShadow ? $._shadowOffsetY : 0;
863
+ $.ctx.shadowBlur = $._doShadow ? $._shadowBlur : 0;
855
864
  };
856
865
 
857
866
  $.push = () => {
@@ -860,7 +869,7 @@ Q5.renderers.q2d.canvas = ($, q) => {
860
869
  };
861
870
  $.pop = () => {
862
871
  $.ctx.restore();
863
- $.popStyles();
872
+ _popStyles();
864
873
  };
865
874
 
866
875
  $.createCapture = (x) => {
@@ -1419,42 +1428,47 @@ Q5.renderers.q2d.image = ($, q) => {
1419
1428
  };
1420
1429
 
1421
1430
  $.filter = (type, value) => {
1422
- if (!$.ctx.filter) return $._softFilter(type, value);
1431
+ $.ctx.save();
1432
+
1423
1433
  let f = '';
1424
- if (typeof type === 'string') {
1425
- f = type;
1426
- } else if (type === Q5.GRAY) {
1427
- f = `saturate(0%)`;
1428
- } else if (type === Q5.INVERT) {
1429
- f = `invert(100%)`;
1430
- } else if (type === Q5.BLUR) {
1431
- let r = Math.ceil(value * $._pixelDensity) || 1;
1432
- f = `blur(${r}px)`;
1433
- } else if (type === Q5.THRESHOLD) {
1434
- value ??= 0.5;
1435
- let b = Math.floor((0.5 / Math.max(value, 0.00001)) * 100);
1436
- f = `saturate(0%) brightness(${b}%) contrast(1000000%)`;
1437
- } else if (type === Q5.SEPIA) {
1438
- f = `sepia(${value ?? 1})`;
1439
- } else if (type === Q5.BRIGHTNESS) {
1440
- f = `brightness(${value ?? 1})`;
1441
- } else if (type === Q5.SATURATION) {
1442
- f = `saturate(${value ?? 1})`;
1443
- } else if (type === Q5.CONTRAST) {
1444
- f = `contrast(${value ?? 1})`;
1445
- } else if (type === Q5.HUE_ROTATE) {
1446
- let unit = $._angleMode === 0 ? 'rad' : 'deg';
1447
- f = `hue-rotate(${value}${unit})`;
1448
- } else {
1449
- $._softFilter(type, value);
1450
- return;
1451
- }
1452
1434
 
1453
- $.ctx.save();
1454
- $.ctx.filter = f;
1455
- if ($.ctx.filter == 'none') {
1456
- throw new Error(`Invalid filter format: ${type}`);
1435
+ if ($.ctx.filter) {
1436
+ if (typeof type == 'string') {
1437
+ f = type;
1438
+ } else if (type == Q5.GRAY) {
1439
+ f = `saturate(0%)`;
1440
+ } else if (type == Q5.INVERT) {
1441
+ f = `invert(100%)`;
1442
+ } else if (type == Q5.BLUR) {
1443
+ let r = Math.ceil(value * $._pixelDensity) || 1;
1444
+ f = `blur(${r}px)`;
1445
+ } else if (type == Q5.THRESHOLD) {
1446
+ value ??= 0.5;
1447
+ let b = Math.floor((0.5 / Math.max(value, 0.00001)) * 100);
1448
+ f = `saturate(0%) brightness(${b}%) contrast(1000000%)`;
1449
+ } else if (type == Q5.SEPIA) {
1450
+ f = `sepia(${value ?? 1})`;
1451
+ } else if (type == Q5.BRIGHTNESS) {
1452
+ f = `brightness(${value ?? 1})`;
1453
+ } else if (type == Q5.SATURATION) {
1454
+ f = `saturate(${value ?? 1})`;
1455
+ } else if (type == Q5.CONTRAST) {
1456
+ f = `contrast(${value ?? 1})`;
1457
+ } else if (type == Q5.HUE_ROTATE) {
1458
+ let unit = $._angleMode == 0 ? 'rad' : 'deg';
1459
+ f = `hue-rotate(${value}${unit})`;
1460
+ }
1461
+
1462
+ if (f) {
1463
+ $.ctx.filter = f;
1464
+ if ($.ctx.filter == 'none') {
1465
+ throw new Error(`Invalid filter format: ${type}`);
1466
+ }
1467
+ }
1457
1468
  }
1469
+
1470
+ if (!f) $._softFilter(type, value);
1471
+
1458
1472
  $.ctx.globalCompositeOperation = 'source-over';
1459
1473
  $.ctx.drawImage($.canvas, 0, 0, $.canvas.w, $.canvas.h);
1460
1474
  $.ctx.restore();
@@ -3580,6 +3594,9 @@ Q5.renderers.webgpu.canvas = ($, q) => {
3580
3594
  c.width = $.width = 500;
3581
3595
  c.height = $.height = 500;
3582
3596
 
3597
+ // q2d graphics context
3598
+ $._g = $.createGraphics(1, 1);
3599
+
3583
3600
  if ($.colorMode) $.colorMode('rgb', 1);
3584
3601
 
3585
3602
  let pass,
@@ -3715,7 +3732,7 @@ Q5.renderers.webgpu.canvas = ($, q) => {
3715
3732
  };
3716
3733
 
3717
3734
  $._stroke = 0;
3718
- $._fill = 1;
3735
+ $._fill = $._tint = 1;
3719
3736
  $._doFill = $._doStroke = true;
3720
3737
 
3721
3738
  $.fill = (r, g, b, a) => {
@@ -3728,42 +3745,51 @@ Q5.renderers.webgpu.canvas = ($, q) => {
3728
3745
  $._doStroke = $._strokeSet = true;
3729
3746
  $._stroke = colorIndex;
3730
3747
  };
3748
+ $.tint = (r, g, b, a) => {
3749
+ addColor(r, g, b, a);
3750
+ $._tint = colorIndex;
3751
+ };
3731
3752
 
3732
3753
  $.noFill = () => ($._doFill = false);
3733
3754
  $.noStroke = () => ($._doStroke = false);
3755
+ $.noTint = () => ($._tint = 1);
3734
3756
 
3735
3757
  $._strokeWeight = 1;
3736
3758
  $.strokeWeight = (v) => ($._strokeWeight = Math.abs(v));
3737
3759
 
3738
- $.resetMatrix = () => {
3739
- // initialize the transformation matrix as 4x4 identity matrix
3740
-
3741
- // prettier-ignore
3742
- $._matrix = [
3743
- 1, 0, 0, 0,
3744
- 0, 1, 0, 0,
3745
- 0, 0, 1, 0,
3746
- 0, 0, 0, 1
3747
- ];
3748
- $._transformIndex = 0;
3749
- };
3750
- $.resetMatrix();
3760
+ const MAX_TRANSFORMS = 1e7, // or whatever maximum you need
3761
+ MATRIX_SIZE = 16, // 4x4 matrix
3762
+ transforms = new Float32Array(MAX_TRANSFORMS * MATRIX_SIZE),
3763
+ matrices = [],
3764
+ matricesIndexStack = [];
3765
+ let matrix;
3751
3766
 
3752
3767
  // tracks if the matrix has been modified
3753
3768
  $._matrixDirty = false;
3754
3769
 
3755
- // array to store transformation matrices for the render pass
3756
- let transformStates = [$._matrix.slice()];
3770
+ // initialize with a 4x4 identity matrix
3771
+ // prettier-ignore
3772
+ matrices.push([
3773
+ 1, 0, 0, 0,
3774
+ 0, 1, 0, 0,
3775
+ 0, 0, 1, 0,
3776
+ 0, 0, 0, 1
3777
+ ]);
3757
3778
 
3758
- // stack to keep track of transformation matrix indexes
3759
- $._transformIndexStack = [];
3779
+ transforms.set(matrices[0]);
3780
+
3781
+ $.resetMatrix = () => {
3782
+ matrix = matrices[0].slice();
3783
+ $._matrixIndex = 0;
3784
+ };
3785
+ $.resetMatrix();
3760
3786
 
3761
3787
  $.translate = (x, y, z) => {
3762
3788
  if (!x && !y && !z) return;
3763
3789
  // update the translation values
3764
- $._matrix[12] += x;
3765
- $._matrix[13] -= y;
3766
- $._matrix[14] += z || 0;
3790
+ matrix[12] += x;
3791
+ matrix[13] -= y;
3792
+ matrix[14] += z || 0;
3767
3793
  $._matrixDirty = true;
3768
3794
  };
3769
3795
 
@@ -3774,7 +3800,7 @@ Q5.renderers.webgpu.canvas = ($, q) => {
3774
3800
  let cosR = Math.cos(a);
3775
3801
  let sinR = Math.sin(a);
3776
3802
 
3777
- let m = $._matrix;
3803
+ let m = matrix;
3778
3804
 
3779
3805
  let m0 = m[0],
3780
3806
  m1 = m[1],
@@ -3801,7 +3827,7 @@ Q5.renderers.webgpu.canvas = ($, q) => {
3801
3827
  $.scale = (x = 1, y, z = 1) => {
3802
3828
  y ??= x;
3803
3829
 
3804
- let m = $._matrix;
3830
+ let m = matrix;
3805
3831
 
3806
3832
  m[0] *= x;
3807
3833
  m[1] *= x;
@@ -3825,13 +3851,13 @@ Q5.renderers.webgpu.canvas = ($, q) => {
3825
3851
 
3826
3852
  let tanAng = Math.tan(ang);
3827
3853
 
3828
- let m0 = $._matrix[0],
3829
- m1 = $._matrix[1],
3830
- m4 = $._matrix[4],
3831
- m5 = $._matrix[5];
3854
+ let m0 = matrix[0],
3855
+ m1 = matrix[1],
3856
+ m4 = matrix[4],
3857
+ m5 = matrix[5];
3832
3858
 
3833
- $._matrix[0] = m0 + m4 * tanAng;
3834
- $._matrix[1] = m1 + m5 * tanAng;
3859
+ matrix[0] = m0 + m4 * tanAng;
3860
+ matrix[1] = m1 + m5 * tanAng;
3835
3861
 
3836
3862
  $._matrixDirty = true;
3837
3863
  };
@@ -3842,13 +3868,13 @@ Q5.renderers.webgpu.canvas = ($, q) => {
3842
3868
 
3843
3869
  let tanAng = Math.tan(ang);
3844
3870
 
3845
- let m0 = $._matrix[0],
3846
- m1 = $._matrix[1],
3847
- m4 = $._matrix[4],
3848
- m5 = $._matrix[5];
3871
+ let m0 = matrix[0],
3872
+ m1 = matrix[1],
3873
+ m4 = matrix[4],
3874
+ m5 = matrix[5];
3849
3875
 
3850
- $._matrix[4] = m4 + m0 * tanAng;
3851
- $._matrix[5] = m5 + m1 * tanAng;
3876
+ matrix[4] = m4 + m0 * tanAng;
3877
+ matrix[5] = m5 + m1 * tanAng;
3852
3878
 
3853
3879
  $._matrixDirty = true;
3854
3880
  };
@@ -3866,31 +3892,32 @@ Q5.renderers.webgpu.canvas = ($, q) => {
3866
3892
  }
3867
3893
 
3868
3894
  // overwrite the current transformation matrix
3869
- $._matrix = m.slice();
3895
+ matrix = m.slice();
3870
3896
  $._matrixDirty = true;
3871
3897
  };
3872
3898
 
3873
3899
  // function to save the current matrix state if dirty
3874
3900
  $._saveMatrix = () => {
3875
- transformStates.push($._matrix.slice());
3876
- $._transformIndex = transformStates.length - 1;
3901
+ transforms.set(matrix, matrices.length * MATRIX_SIZE);
3902
+ $._matrixIndex = matrices.length;
3903
+ matrices.push(matrix.slice());
3877
3904
  $._matrixDirty = false;
3878
3905
  };
3879
3906
 
3880
3907
  // push the current matrix index onto the stack
3881
3908
  $.pushMatrix = () => {
3882
3909
  if ($._matrixDirty) $._saveMatrix();
3883
- $._transformIndexStack.push($._transformIndex);
3910
+ matricesIndexStack.push($._matrixIndex);
3884
3911
  };
3885
3912
 
3886
3913
  $.popMatrix = () => {
3887
- if (!$._transformIndexStack.length) {
3914
+ if (!matricesIndexStack.length) {
3888
3915
  return console.warn('Matrix index stack is empty!');
3889
3916
  }
3890
3917
  // pop the last matrix index and set it as the current matrix index
3891
- let idx = $._transformIndexStack.pop();
3892
- $._matrix = transformStates[idx].slice();
3893
- $._transformIndex = idx;
3918
+ let idx = matricesIndexStack.pop();
3919
+ matrix = matrices[idx].slice();
3920
+ $._matrixIndex = idx;
3894
3921
  $._matrixDirty = false;
3895
3922
  };
3896
3923
 
@@ -4015,14 +4042,14 @@ Q5.renderers.webgpu.canvas = ($, q) => {
4015
4042
  };
4016
4043
 
4017
4044
  $._render = () => {
4018
- if (transformStates.length > 1 || !$._transformBindGroup) {
4045
+ if (matrices.length > 1 || !$._transformBindGroup) {
4019
4046
  let transformBuffer = Q5.device.createBuffer({
4020
- size: transformStates.length * 64, // 64 is the size of 16 floats
4047
+ size: matrices.length * MATRIX_SIZE * 4, // 4 bytes per float
4021
4048
  usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
4022
4049
  mappedAtCreation: true
4023
4050
  });
4024
4051
 
4025
- new Float32Array(transformBuffer.getMappedRange()).set(transformStates.flat());
4052
+ new Float32Array(transformBuffer.getMappedRange()).set(transforms.slice(0, matrices.length * MATRIX_SIZE));
4026
4053
  transformBuffer.unmap();
4027
4054
 
4028
4055
  $._transformBindGroup = Q5.device.createBindGroup({
@@ -4050,7 +4077,7 @@ Q5.renderers.webgpu.canvas = ($, q) => {
4050
4077
  entries: [{ binding: 0, resource: { buffer: colorsBuffer } }]
4051
4078
  });
4052
4079
 
4053
- $.pass.setBindGroup(1, $._colorsBindGroup);
4080
+ pass.setBindGroup(1, $._colorsBindGroup);
4054
4081
 
4055
4082
  for (let m of $._hooks.preRender) m();
4056
4083
 
@@ -4074,6 +4101,7 @@ Q5.renderers.webgpu.canvas = ($, q) => {
4074
4101
  pass.draw(v, 1, drawVertOffset);
4075
4102
  drawVertOffset += v;
4076
4103
  } else if (curPipelineIndex == 1) {
4104
+ // let vertCount = drawStack[i + 2];
4077
4105
  // draw images
4078
4106
  if (curTextureIndex != v) {
4079
4107
  // v is the texture index
@@ -4081,6 +4109,7 @@ Q5.renderers.webgpu.canvas = ($, q) => {
4081
4109
  }
4082
4110
  pass.draw(4, 1, imageVertOffset);
4083
4111
  imageVertOffset += 4;
4112
+ // i++;
4084
4113
  } else if (curPipelineIndex == 2) {
4085
4114
  // draw text
4086
4115
  let o = drawStack[i + 2];
@@ -4109,8 +4138,9 @@ Q5.renderers.webgpu.canvas = ($, q) => {
4109
4138
  colorIndex = 1;
4110
4139
  colorStackIndex = 8;
4111
4140
  rotation = 0;
4112
- transformStates.length = 1;
4113
- $._transformIndexStack.length = 0;
4141
+ transforms.length = MATRIX_SIZE;
4142
+ matrices.length = 1;
4143
+ matricesIndexStack.length = 0;
4114
4144
  };
4115
4145
  };
4116
4146
 
@@ -4121,7 +4151,10 @@ Q5.initWebGPU = async () => {
4121
4151
  }
4122
4152
  if (!Q5.device) {
4123
4153
  let adapter = await navigator.gpu.requestAdapter();
4124
- if (!adapter) throw new Error('No appropriate GPUAdapter found.');
4154
+ if (!adapter) {
4155
+ console.warn('q5 WebGPU could not start! No appropriate GPUAdapter found, vulkan may need to be enabled.');
4156
+ return false;
4157
+ }
4125
4158
  Q5.device = await adapter.requestDevice();
4126
4159
  }
4127
4160
  return true;
@@ -4146,7 +4179,7 @@ Q5.renderers.webgpu.drawing = ($, q) => {
4146
4179
  struct VertexInput {
4147
4180
  @location(0) pos: vec2f,
4148
4181
  @location(1) colorIndex: f32,
4149
- @location(2) transformIndex: f32
4182
+ @location(2) matrixIndex: f32
4150
4183
  }
4151
4184
  struct VertexOutput {
4152
4185
  @builtin(position) position: vec4f,
@@ -4165,7 +4198,7 @@ struct Uniforms {
4165
4198
  @vertex
4166
4199
  fn vertexMain(input: VertexInput) -> VertexOutput {
4167
4200
  var vert = vec4f(input.pos, 0.0, 1.0);
4168
- vert = transforms[i32(input.transformIndex)] * vert;
4201
+ vert = transforms[i32(input.matrixIndex)] * vert;
4169
4202
  vert.x /= uniforms.halfWidth;
4170
4203
  vert.y /= uniforms.halfHeight;
4171
4204
 
@@ -4192,7 +4225,7 @@ fn fragmentMain(@location(0) color: vec4f) -> @location(0) vec4f {
4192
4225
  attributes: [
4193
4226
  { format: 'float32x2', offset: 0, shaderLocation: 0 }, // position
4194
4227
  { format: 'float32', offset: 8, shaderLocation: 1 }, // colorIndex
4195
- { format: 'float32', offset: 12, shaderLocation: 2 } // transformIndex
4228
+ { format: 'float32', offset: 12, shaderLocation: 2 } // matrixIndex
4196
4229
  ]
4197
4230
  };
4198
4231
 
@@ -4346,7 +4379,7 @@ fn fragmentMain(@location(0) color: vec4f) -> @location(0) vec4f {
4346
4379
  let [l, r, t, b] = $._calcBox(x, y, w, h, $._rectMode);
4347
4380
  let ci, ti;
4348
4381
  if ($._matrixDirty) $._saveMatrix();
4349
- ti = $._transformIndex;
4382
+ ti = $._matrixIndex;
4350
4383
 
4351
4384
  if ($._doFill) {
4352
4385
  ci = $._fill;
@@ -4420,7 +4453,7 @@ fn fragmentMain(@location(0) color: vec4f) -> @location(0) vec4f {
4420
4453
  let b = w == h ? a : Math.max(h, 1) / 2;
4421
4454
 
4422
4455
  if ($._matrixDirty) $._saveMatrix();
4423
- let ti = $._transformIndex;
4456
+ let ti = $._matrixIndex;
4424
4457
 
4425
4458
  if ($._doFill) {
4426
4459
  addEllipse(x, y, a, b, n, $._fill, ti);
@@ -4436,7 +4469,7 @@ fn fragmentMain(@location(0) color: vec4f) -> @location(0) vec4f {
4436
4469
 
4437
4470
  $.point = (x, y) => {
4438
4471
  if ($._matrixDirty) $._saveMatrix();
4439
- let ti = $._transformIndex,
4472
+ let ti = $._matrixIndex,
4440
4473
  ci = $._stroke,
4441
4474
  sw = $._strokeWeight;
4442
4475
 
@@ -4456,7 +4489,7 @@ fn fragmentMain(@location(0) color: vec4f) -> @location(0) vec4f {
4456
4489
 
4457
4490
  $.line = (x1, y1, x2, y2) => {
4458
4491
  if ($._matrixDirty) $._saveMatrix();
4459
- let ti = $._transformIndex,
4492
+ let ti = $._matrixIndex,
4460
4493
  ci = $._stroke,
4461
4494
  sw = $._strokeWeight,
4462
4495
  hsw = sw / 2;
@@ -4491,7 +4524,7 @@ fn fragmentMain(@location(0) color: vec4f) -> @location(0) vec4f {
4491
4524
 
4492
4525
  $.vertex = (x, y) => {
4493
4526
  if ($._matrixDirty) $._saveMatrix();
4494
- sv.push(x, -y, $._fill, $._transformIndex);
4527
+ sv.push(x, -y, $._fill, $._matrixIndex);
4495
4528
  shapeVertCount++;
4496
4529
  };
4497
4530
 
@@ -4536,7 +4569,7 @@ fn fragmentMain(@location(0) color: vec4f) -> @location(0) vec4f {
4536
4569
  (2 * p0.y - 5 * p1.y + 4 * p2.y - p3.y) * t2 +
4537
4570
  (-p0.y + 3 * p1.y - 3 * p2.y + p3.y) * t3);
4538
4571
 
4539
- sv.push(x, y, $._fill, $._transformIndex);
4572
+ sv.push(x, y, $._fill, $._matrixIndex);
4540
4573
  shapeVertCount++;
4541
4574
  }
4542
4575
  }
@@ -4654,14 +4687,22 @@ fn fragmentMain(@location(0) color: vec4f) -> @location(0) vec4f {
4654
4687
  };
4655
4688
  Q5.renderers.webgpu.image = ($, q) => {
4656
4689
  $._textureBindGroups = [];
4657
- let vertexStack = [];
4690
+ let vertexStack = new Float32Array(1e7),
4691
+ vertIndex = 0;
4658
4692
 
4659
- let vertexShader = Q5.device.createShaderModule({
4660
- label: 'imageVertexShader',
4693
+ let imageShader = Q5.device.createShaderModule({
4694
+ label: 'imageShader',
4661
4695
  code: `
4696
+ struct VertexInput {
4697
+ @location(0) pos: vec2f,
4698
+ @location(1) texCoord: vec2f,
4699
+ @location(2) tintIndex: f32,
4700
+ @location(3) matrixIndex: f32
4701
+ }
4662
4702
  struct VertexOutput {
4663
4703
  @builtin(position) position: vec4f,
4664
- @location(0) texCoord: vec2f
4704
+ @location(0) texCoord: vec2f,
4705
+ @location(1) tintIndex: f32
4665
4706
  }
4666
4707
  struct Uniforms {
4667
4708
  halfWidth: f32,
@@ -4671,31 +4712,33 @@ struct Uniforms {
4671
4712
  @group(0) @binding(0) var<uniform> uniforms: Uniforms;
4672
4713
  @group(0) @binding(1) var<storage> transforms: array<mat4x4<f32>>;
4673
4714
 
4715
+ @group(1) @binding(0) var<storage> colors : array<vec4f>;
4716
+
4717
+ @group(2) @binding(0) var samp: sampler;
4718
+ @group(2) @binding(1) var texture: texture_2d<f32>;
4719
+
4674
4720
  @vertex
4675
- fn vertexMain(@location(0) pos: vec2f, @location(1) texCoord: vec2f, @location(2) transformIndex: f32) -> VertexOutput {
4676
- var vert = vec4f(pos, 0.0, 1.0);
4677
- vert = transforms[i32(transformIndex)] * vert;
4721
+ fn vertexMain(input: VertexInput) -> VertexOutput {
4722
+ var vert = vec4f(input.pos, 0.0, 1.0);
4723
+ vert = transforms[i32(input.matrixIndex)] * vert;
4678
4724
  vert.x /= uniforms.halfWidth;
4679
4725
  vert.y /= uniforms.halfHeight;
4680
4726
 
4681
4727
  var output: VertexOutput;
4682
4728
  output.position = vert;
4683
- output.texCoord = texCoord;
4729
+ output.texCoord = input.texCoord;
4730
+ output.tintIndex = input.tintIndex;
4684
4731
  return output;
4685
4732
  }
4686
- `
4687
- });
4688
-
4689
- let fragmentShader = Q5.device.createShaderModule({
4690
- label: 'imageFragmentShader',
4691
- code: `
4692
- @group(2) @binding(0) var samp: sampler;
4693
- @group(2) @binding(1) var texture: texture_2d<f32>;
4694
4733
 
4695
4734
  @fragment
4696
- fn fragmentMain(@location(0) texCoord: vec2f) -> @location(0) vec4f {
4697
- // Sample the texture using the interpolated texture coordinate
4698
- return textureSample(texture, samp, texCoord);
4735
+ fn fragmentMain(@location(0) texCoord: vec2f, @location(1) tintIndex: f32) -> @location(0) vec4f {
4736
+ let texColor = textureSample(texture, samp, texCoord);
4737
+ let tintColor = colors[i32(tintIndex)];
4738
+
4739
+ // Mix original and tinted colors using tint alpha as blend factor
4740
+ let tinted = vec4f(texColor.rgb * tintColor.rgb, texColor.a);
4741
+ return mix(texColor, tinted, tintColor.a);
4699
4742
  }
4700
4743
  `
4701
4744
  });
@@ -4717,11 +4760,12 @@ fn fragmentMain(@location(0) texCoord: vec2f) -> @location(0) vec4f {
4717
4760
  });
4718
4761
 
4719
4762
  const vertexBufferLayout = {
4720
- arrayStride: 20,
4763
+ arrayStride: 24,
4721
4764
  attributes: [
4722
4765
  { shaderLocation: 0, offset: 0, format: 'float32x2' },
4723
4766
  { shaderLocation: 1, offset: 8, format: 'float32x2' },
4724
- { shaderLocation: 2, offset: 16, format: 'float32' } // transformIndex
4767
+ { shaderLocation: 2, offset: 16, format: 'float32' }, // tintIndex
4768
+ { shaderLocation: 3, offset: 20, format: 'float32' } // matrixIndex
4725
4769
  ]
4726
4770
  };
4727
4771
 
@@ -4734,12 +4778,12 @@ fn fragmentMain(@location(0) texCoord: vec2f) -> @location(0) vec4f {
4734
4778
  label: 'imagePipeline',
4735
4779
  layout: pipelineLayout,
4736
4780
  vertex: {
4737
- module: vertexShader,
4781
+ module: imageShader,
4738
4782
  entryPoint: 'vertexMain',
4739
4783
  buffers: [{ arrayStride: 0, attributes: [] }, vertexBufferLayout]
4740
4784
  },
4741
4785
  fragment: {
4742
- module: fragmentShader,
4786
+ module: imageShader,
4743
4787
  entryPoint: 'fragmentMain',
4744
4788
  targets: [{ format: 'bgra8unorm', blend: $.blendConfigs.normal }]
4745
4789
  },
@@ -4815,58 +4859,63 @@ fn fragmentMain(@location(0) texCoord: vec2f) -> @location(0) vec4f {
4815
4859
 
4816
4860
  $.loadImage = (src, cb) => {
4817
4861
  q._preloadCount++;
4818
- const img = new Image();
4819
- img.crossOrigin = 'Anonymous';
4820
- img.onload = () => {
4821
- // calculate the default width and height that the image
4822
- // should be drawn at if the user doesn't specify a display size
4823
- img.defaultWidth = img.width * $._defaultImageScale;
4824
- img.defaultHeight = img.height * $._defaultImageScale;
4825
- img.pixelDensity = 1;
4826
-
4862
+ let g = $._g.loadImage(src, (img) => {
4863
+ g.defaultWidth = img.width * $._defaultImageScale;
4864
+ g.defaultHeight = img.height * $._defaultImageScale;
4827
4865
  $._createTexture(img);
4828
4866
  q._preloadCount--;
4829
4867
  if (cb) cb(img);
4830
- };
4831
- img.src = src;
4832
- return img;
4868
+ });
4869
+ return g;
4833
4870
  };
4834
4871
 
4835
4872
  $.imageMode = (x) => ($._imageMode = x);
4836
4873
 
4837
- $.image = (img, dx, dy, dw, dh, sx = 0, sy = 0, sw, sh) => {
4874
+ const addVert = (x, y, u, v, ci, ti) => {
4875
+ let s = vertexStack,
4876
+ i = vertIndex;
4877
+ s[i++] = x;
4878
+ s[i++] = y;
4879
+ s[i++] = u;
4880
+ s[i++] = v;
4881
+ s[i++] = ci;
4882
+ s[i++] = ti;
4883
+ vertIndex = i;
4884
+ };
4885
+
4886
+ $.image = (img, dx = 0, dy = 0, dw, dh, sx = 0, sy = 0, sw, sh) => {
4887
+ let g = img;
4838
4888
  if (img.canvas) img = img.canvas;
4839
4889
  if (img.textureIndex == undefined) return;
4840
4890
 
4841
4891
  if ($._matrixDirty) $._saveMatrix();
4842
- let ti = $._transformIndex;
4843
4892
 
4844
- let w = img.width;
4845
- let h = img.height;
4893
+ let ti = $._matrixIndex,
4894
+ w = img.width,
4895
+ h = img.height;
4846
4896
 
4847
- dw ??= img.defaultWidth;
4848
- dh ??= img.defaultHeight;
4897
+ dw ??= g.defaultWidth;
4898
+ dh ??= g.defaultHeight;
4849
4899
  sw ??= w;
4850
4900
  sh ??= h;
4851
4901
 
4852
- let pd = img.pixelDensity || 1;
4902
+ let pd = g._pixelDensity || 1;
4853
4903
  dw *= pd;
4854
4904
  dh *= pd;
4855
4905
 
4856
4906
  let [l, r, t, b] = $._calcBox(dx, dy, dw, dh, $._imageMode);
4857
4907
 
4858
- let u0 = sx / w;
4859
- let v0 = sy / h;
4860
- let u1 = (sx + sw) / w;
4861
- let v1 = (sy + sh) / h;
4862
-
4863
- // prettier-ignore
4864
- vertexStack.push(
4865
- l, t, u0, v0, ti,
4866
- r, t, u1, v0, ti,
4867
- l, b, u0, v1, ti,
4868
- r, b, u1, v1, ti
4869
- );
4908
+ let u0 = sx / w,
4909
+ v0 = sy / h,
4910
+ u1 = (sx + sw) / w,
4911
+ v1 = (sy + sh) / h;
4912
+
4913
+ let ci = $._tint;
4914
+
4915
+ addVert(l, t, u0, v0, ci, ti);
4916
+ addVert(r, t, u1, v0, ci, ti);
4917
+ addVert(l, b, u0, v1, ci, ti);
4918
+ addVert(r, b, u1, v1, ci, ti);
4870
4919
 
4871
4920
  $.drawStack.push(1, img.textureIndex);
4872
4921
  };
@@ -4877,20 +4926,20 @@ fn fragmentMain(@location(0) texCoord: vec2f) -> @location(0) vec4f {
4877
4926
  // Switch to image pipeline
4878
4927
  $.pass.setPipeline($._pipelines[1]);
4879
4928
 
4880
- const vertexBuffer = Q5.device.createBuffer({
4881
- size: vertexStack.length * 4,
4929
+ let vertexBuffer = Q5.device.createBuffer({
4930
+ size: vertIndex * 4,
4882
4931
  usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
4883
4932
  mappedAtCreation: true
4884
4933
  });
4885
4934
 
4886
- new Float32Array(vertexBuffer.getMappedRange()).set(vertexStack);
4935
+ new Float32Array(vertexBuffer.getMappedRange()).set(vertexStack.slice(0, vertIndex));
4887
4936
  vertexBuffer.unmap();
4888
4937
 
4889
4938
  $.pass.setVertexBuffer(1, vertexBuffer);
4890
4939
  });
4891
4940
 
4892
4941
  $._hooks.postRender.push(() => {
4893
- vertexStack.length = 0;
4942
+ vertIndex = 0;
4894
4943
  });
4895
4944
  };
4896
4945
 
@@ -4927,7 +4976,7 @@ struct Char {
4927
4976
  struct Text {
4928
4977
  pos: vec2f,
4929
4978
  scale: f32,
4930
- transformIndex: f32,
4979
+ matrixIndex: f32,
4931
4980
  fillIndex: f32,
4932
4981
  strokeIndex: f32
4933
4982
  }
@@ -4959,7 +5008,7 @@ fn vertexMain(input : VertexInput) -> VertexOutput {
4959
5008
  let charPos = ((pos[input.vertex] * fontChar.size + char.xy + fontChar.offset) * text.scale) + text.pos;
4960
5009
 
4961
5010
  var vert = vec4f(charPos, 0.0, 1.0);
4962
- vert = transforms[i32(text.transformIndex)] * vert;
5011
+ vert = transforms[i32(text.matrixIndex)] * vert;
4963
5012
  vert.x /= uniforms.halfWidth;
4964
5013
  vert.y /= uniforms.halfHeight;
4965
5014
 
@@ -5183,13 +5232,11 @@ fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
5183
5232
  if (cb) cb(fontName);
5184
5233
  };
5185
5234
 
5186
- // q2d graphics context to use for text image creation
5187
- let g = $.createGraphics(1, 1);
5188
- g.colorMode($.RGB, 1);
5235
+ $._g.colorMode($.RGB, 1);
5189
5236
 
5190
5237
  $.loadFont = (url, cb) => {
5191
5238
  let ext = url.slice(url.lastIndexOf('.') + 1);
5192
- if (ext != 'json') return g.loadFont(url, cb);
5239
+ if (ext != 'json') return $._g.loadFont(url, cb);
5193
5240
  let fontName = url.slice(url.lastIndexOf('/') + 1, url.lastIndexOf('-'));
5194
5241
  createFont(url, fontName, cb);
5195
5242
  return fontName;
@@ -5374,7 +5421,7 @@ fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
5374
5421
  text[0] = x;
5375
5422
  text[1] = -y;
5376
5423
  text[2] = $._textSize / 44;
5377
- text[3] = $._transformIndex;
5424
+ text[3] = $._matrixIndex;
5378
5425
  text[4] = $._fillSet ? $._fill : 0;
5379
5426
  text[5] = $._stroke;
5380
5427
 
@@ -5388,18 +5435,18 @@ fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
5388
5435
  };
5389
5436
 
5390
5437
  $.createTextImage = (str, w, h) => {
5391
- g.textSize($._textSize);
5438
+ $._g.textSize($._textSize);
5392
5439
 
5393
5440
  if ($._doFill) {
5394
5441
  let fi = $._fill * 4;
5395
- g.fill(colorStack.slice(fi, fi + 4));
5442
+ $._g.fill(colorStack.slice(fi, fi + 4));
5396
5443
  }
5397
5444
  if ($._doStroke) {
5398
5445
  let si = $._stroke * 4;
5399
- g.stroke(colorStack.slice(si, si + 4));
5446
+ $._g.stroke(colorStack.slice(si, si + 4));
5400
5447
  }
5401
5448
 
5402
- let img = g.createTextImage(str, w, h);
5449
+ let img = $._g.createTextImage(str, w, h);
5403
5450
 
5404
5451
  if (img.canvas.textureIndex == undefined) {
5405
5452
  $._createTexture(img);