q5 2.20.10 → 2.21.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.
Files changed (6) hide show
  1. package/README.md +4 -4
  2. package/deno.json +1 -1
  3. package/package.json +3 -3
  4. package/q5.d.ts +1141 -920
  5. package/q5.js +261 -136
  6. package/q5.min.js +2 -2
package/q5.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * q5.js
3
- * @version 2.20
3
+ * @version 2.21
4
4
  * @author quinton-ashley, Tezumie, and LingDong-
5
5
  * @license LGPL-3.0
6
6
  * @class Q5
@@ -314,7 +314,7 @@ function createCanvas(w, h, opt) {
314
314
  }
315
315
  }
316
316
 
317
- Q5.version = Q5.VERSION = '2.20';
317
+ Q5.version = Q5.VERSION = '2.21';
318
318
 
319
319
  if (typeof document == 'object') {
320
320
  document.addEventListener('DOMContentLoaded', () => {
@@ -837,7 +837,7 @@ Q5.renderers.c2d.canvas = ($, q) => {
837
837
  _popStyles();
838
838
  };
839
839
  };
840
- Q5.renderers.c2d.drawing = ($) => {
840
+ Q5.renderers.c2d.shapes = ($) => {
841
841
  $._doStroke = true;
842
842
  $._doFill = true;
843
843
  $._strokeSet = false;
@@ -1580,7 +1580,7 @@ Q5.renderers.c2d.image = ($, q) => {
1580
1580
 
1581
1581
  $._saveCanvas = async (data, ext) => {
1582
1582
  data = data.canvas || data;
1583
- if (data instanceof HTMLCanvasElement || data instanceof OffscreenCanvas) {
1583
+ if (data instanceof OffscreenCanvas) {
1584
1584
  const blob = await data.convertToBlob({ type: 'image/' + ext });
1585
1585
 
1586
1586
  return await new Promise((resolve) => {
@@ -1786,7 +1786,7 @@ Q5.renderers.c2d.text = ($, q) => {
1786
1786
  };
1787
1787
 
1788
1788
  $.textFont = (x) => {
1789
- if (typeof x != 'string') x = x.family;
1789
+ if (x && typeof x != 'string') x = x.family;
1790
1790
  if (!x || x == font) return font;
1791
1791
  font = x;
1792
1792
  fontMod = true;
@@ -4494,6 +4494,23 @@ for (let k of ['fromAngle', 'fromAngles', 'random2D', 'random3D']) {
4494
4494
  Q5.renderers.webgpu = {};
4495
4495
 
4496
4496
  Q5.renderers.webgpu.canvas = ($, q) => {
4497
+ $._baseShaderCode = /* wgsl */ `
4498
+ struct Q5 {
4499
+ width: f32,
4500
+ height: f32,
4501
+ halfWidth: f32,
4502
+ halfHeight: f32,
4503
+ pixelDensity: f32,
4504
+ frameCount: f32,
4505
+ time: f32,
4506
+ deltaTime: f32,
4507
+ mouseX: f32,
4508
+ mouseY: f32,
4509
+ mouseIsPressed: f32,
4510
+ keyCode: f32,
4511
+ keyIsPressed: f32
4512
+ }`;
4513
+
4497
4514
  let c = $.canvas;
4498
4515
 
4499
4516
  c.width = $.width = 500;
@@ -4535,7 +4552,7 @@ Q5.renderers.webgpu.canvas = ($, q) => {
4535
4552
  entries: [
4536
4553
  {
4537
4554
  binding: 0,
4538
- visibility: GPUShaderStage.VERTEX,
4555
+ visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
4539
4556
  buffer: { type: 'uniform' }
4540
4557
  },
4541
4558
  {
@@ -4554,7 +4571,7 @@ Q5.renderers.webgpu.canvas = ($, q) => {
4554
4571
  $.bindGroupLayouts = [mainLayout];
4555
4572
 
4556
4573
  let uniformBuffer = Q5.device.createBuffer({
4557
- size: 8, // Size of two floats
4574
+ size: 64, // Size of four floats
4558
4575
  usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
4559
4576
  });
4560
4577
 
@@ -4624,8 +4641,6 @@ Q5.renderers.webgpu.canvas = ($, q) => {
4624
4641
 
4625
4642
  $.ctx.configure(opt);
4626
4643
 
4627
- Q5.device.queue.writeBuffer(uniformBuffer, 0, new Float32Array([$.canvas.hw, $.canvas.hh]));
4628
-
4629
4644
  createMainView();
4630
4645
  return c;
4631
4646
  };
@@ -4888,9 +4903,6 @@ Q5.renderers.webgpu.canvas = ($, q) => {
4888
4903
  };
4889
4904
 
4890
4905
  $._calcBox = (x, y, w, h, mode) => {
4891
- let hw = w / 2;
4892
- let hh = h / 2;
4893
-
4894
4906
  // left, right, top, bottom
4895
4907
  let l, r, t, b;
4896
4908
  if (!mode || mode == 'corner') {
@@ -4899,6 +4911,8 @@ Q5.renderers.webgpu.canvas = ($, q) => {
4899
4911
  t = -y;
4900
4912
  b = -(y + h);
4901
4913
  } else if (mode == 'center') {
4914
+ let hw = w / 2,
4915
+ hh = h / 2;
4902
4916
  l = x - hw;
4903
4917
  r = x + hw;
4904
4918
  t = -(y - hh);
@@ -5028,7 +5042,7 @@ Q5.renderers.webgpu.canvas = ($, q) => {
5028
5042
  $._render = () => {
5029
5043
  let transformBuffer = Q5.device.createBuffer({
5030
5044
  size: matrices.length * MATRIX_SIZE * 4, // 4 bytes per float
5031
- usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
5045
+ usage: GPUBufferUsage.STORAGE,
5032
5046
  mappedAtCreation: true
5033
5047
  });
5034
5048
 
@@ -5037,13 +5051,31 @@ Q5.renderers.webgpu.canvas = ($, q) => {
5037
5051
 
5038
5052
  let colorsBuffer = Q5.device.createBuffer({
5039
5053
  size: colorStackIndex * 4,
5040
- usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
5054
+ usage: GPUBufferUsage.STORAGE,
5041
5055
  mappedAtCreation: true
5042
5056
  });
5043
5057
 
5044
5058
  new Float32Array(colorsBuffer.getMappedRange()).set(colorStack.slice(0, colorStackIndex));
5045
5059
  colorsBuffer.unmap();
5046
5060
 
5061
+ $._uniforms = [
5062
+ $.width,
5063
+ $.height,
5064
+ $.halfWidth,
5065
+ $.halfHeight,
5066
+ $._pixelDensity,
5067
+ $.frameCount,
5068
+ performance.now(),
5069
+ $.deltaTime,
5070
+ $.mouseX,
5071
+ $.mouseY,
5072
+ $.mouseIsPressed ? 1 : 0,
5073
+ $.keyCode,
5074
+ $.keyIsPressed ? 1 : 0
5075
+ ];
5076
+
5077
+ Q5.device.queue.writeBuffer(uniformBuffer, 0, new Float32Array($._uniforms));
5078
+
5047
5079
  let mainBindGroup = Q5.device.createBindGroup({
5048
5080
  layout: mainLayout,
5049
5081
  entries: [
@@ -5070,18 +5102,7 @@ Q5.renderers.webgpu.canvas = ($, q) => {
5070
5102
  pass.setPipeline($._pipelines[curPipelineIndex]);
5071
5103
  }
5072
5104
 
5073
- if (curPipelineIndex == 0) {
5074
- // draw a shape
5075
- // v is the number of vertices
5076
- pass.draw(v, 1, drawVertOffset);
5077
- drawVertOffset += v;
5078
- } else if (curPipelineIndex <= 2) {
5079
- // draw an image or video frame
5080
- // v is the texture index
5081
- pass.setBindGroup(1, $._textureBindGroups[v]);
5082
- pass.draw(4, 1, imageVertOffset);
5083
- imageVertOffset += 4;
5084
- } else if (curPipelineIndex == 3) {
5105
+ if (curPipelineIndex == 3 || curPipelineIndex >= 400) {
5085
5106
  // draw text
5086
5107
  let o = drawStack[i + 2];
5087
5108
  pass.setBindGroup(1, $._fonts[o].bindGroup);
@@ -5091,6 +5112,17 @@ Q5.renderers.webgpu.canvas = ($, q) => {
5091
5112
  pass.draw(4, v, 0, textCharOffset);
5092
5113
  textCharOffset += v;
5093
5114
  i++;
5115
+ } else if (curPipelineIndex == 1 || curPipelineIndex == 2 || curPipelineIndex >= 200) {
5116
+ // draw an image or video frame
5117
+ // v is the texture index
5118
+ pass.setBindGroup(1, $._textureBindGroups[v]);
5119
+ pass.draw(4, 1, imageVertOffset);
5120
+ imageVertOffset += 4;
5121
+ } else if (curPipelineIndex == 0 || curPipelineIndex >= 100) {
5122
+ // draw a shape
5123
+ // v is the number of vertices
5124
+ pass.draw(v, 1, drawVertOffset);
5125
+ drawVertOffset += v;
5094
5126
  }
5095
5127
  }
5096
5128
  };
@@ -5134,7 +5166,7 @@ Q5.renderers.webgpu.canvas = ($, q) => {
5134
5166
  q.pass = $.encoder = null;
5135
5167
 
5136
5168
  // clear the stacks for the next frame
5137
- $.drawStack = drawStack = [];
5169
+ drawStack.splice(0, drawStack.length);
5138
5170
  colorIndex = 1;
5139
5171
  colorStackIndex = 8;
5140
5172
  matrices = [matrices[0]];
@@ -5149,7 +5181,7 @@ Q5.initWebGPU = async () => {
5149
5181
  console.warn('q5 WebGPU not supported on this browser! Use Google Chrome or Edge.');
5150
5182
  return false;
5151
5183
  }
5152
- if (!Q5.device) {
5184
+ if (!Q5.requestedGPU) {
5153
5185
  let adapter = await navigator.gpu.requestAdapter();
5154
5186
  if (!adapter) {
5155
5187
  console.warn('q5 WebGPU could not start! No appropriate GPUAdapter found, vulkan may need to be enabled.');
@@ -5172,62 +5204,63 @@ Q5.webgpu = async function (scope, parent) {
5172
5204
  }
5173
5205
  return new Q5(scope, parent, 'webgpu');
5174
5206
  };
5175
- Q5.renderers.webgpu.drawing = ($, q) => {
5176
- let c = $.canvas,
5177
- drawStack = $.drawStack,
5178
- vertexStack = new Float32Array($._graphics ? 1000 : 1e7),
5179
- vertIndex = 0;
5180
- const TAU = Math.PI * 2;
5181
- const HALF_PI = Math.PI / 2;
5207
+ Q5.renderers.webgpu.shapes = ($) => {
5208
+ $._shapesPL = 0;
5182
5209
 
5183
- let drawingShaderCode = `
5184
- struct Uniforms {
5185
- halfWidth: f32,
5186
- halfHeight: f32
5187
- }
5210
+ $._shapesShaderCode =
5211
+ $._baseShaderCode +
5212
+ /* wgsl */ `
5188
5213
  struct VertexParams {
5214
+ @builtin(vertex_index) vertexIndex : u32,
5189
5215
  @location(0) pos: vec2f,
5190
5216
  @location(1) colorIndex: f32,
5191
5217
  @location(2) matrixIndex: f32
5192
5218
  }
5193
- struct FragmentParams {
5219
+ struct FragParams {
5194
5220
  @builtin(position) position: vec4f,
5195
5221
  @location(0) color: vec4f
5196
5222
  }
5197
5223
 
5198
- @group(0) @binding(0) var<uniform> uniforms: Uniforms;
5224
+ @group(0) @binding(0) var<uniform> q: Q5;
5199
5225
  @group(0) @binding(1) var<storage> transforms: array<mat4x4<f32>>;
5200
5226
  @group(0) @binding(2) var<storage> colors : array<vec4f>;
5201
5227
 
5202
5228
  fn transformVertex(pos: vec2f, matrixIndex: f32) -> vec4f {
5203
5229
  var vert = vec4f(pos, 0.0, 1.0);
5204
5230
  vert = transforms[i32(matrixIndex)] * vert;
5205
- vert.x /= uniforms.halfWidth;
5206
- vert.y /= uniforms.halfHeight;
5231
+ vert.x /= q.halfWidth;
5232
+ vert.y /= q.halfHeight;
5207
5233
  return vert;
5208
5234
  }
5209
5235
 
5210
5236
  @vertex
5211
- fn vertexMain(v: VertexParams) -> FragmentParams {
5237
+ fn vertexMain(v: VertexParams) -> FragParams {
5212
5238
  var vert = transformVertex(v.pos, v.matrixIndex);
5213
5239
 
5214
- var f: FragmentParams;
5240
+ var f: FragParams;
5215
5241
  f.position = vert;
5216
5242
  f.color = colors[i32(v.colorIndex)];
5217
5243
  return f;
5218
5244
  }
5219
5245
 
5220
5246
  @fragment
5221
- fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
5247
+ fn fragMain(f: FragParams) -> @location(0) vec4f {
5222
5248
  return f.color;
5223
5249
  }
5224
5250
  `;
5225
5251
 
5226
- let drawingShader = Q5.device.createShaderModule({
5227
- label: 'drawingShader',
5228
- code: drawingShaderCode
5252
+ let shapesShader = Q5.device.createShaderModule({
5253
+ label: 'shapesShader',
5254
+ code: $._shapesShaderCode
5229
5255
  });
5230
5256
 
5257
+ let c = $.canvas,
5258
+ drawStack = $.drawStack,
5259
+ vertexStack = new Float32Array($._graphics ? 1000 : 1e7),
5260
+ vertIndex = 0;
5261
+ const TAU = Math.PI * 2;
5262
+ const HALF_PI = Math.PI / 2;
5263
+
5231
5264
  let vertexBufferLayout = {
5232
5265
  arrayStride: 16, // 4 floats * 4 bytes
5233
5266
  attributes: [
@@ -5238,21 +5271,21 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
5238
5271
  };
5239
5272
 
5240
5273
  let pipelineLayout = Q5.device.createPipelineLayout({
5241
- label: 'drawingPipelineLayout',
5274
+ label: 'shapesPipelineLayout',
5242
5275
  bindGroupLayouts: $.bindGroupLayouts
5243
5276
  });
5244
5277
 
5245
5278
  $._pipelineConfigs[0] = {
5246
- label: 'drawingPipeline',
5279
+ label: 'shapesPipeline',
5247
5280
  layout: pipelineLayout,
5248
5281
  vertex: {
5249
- module: drawingShader,
5282
+ module: shapesShader,
5250
5283
  entryPoint: 'vertexMain',
5251
5284
  buffers: [vertexBufferLayout]
5252
5285
  },
5253
5286
  fragment: {
5254
- module: drawingShader,
5255
- entryPoint: 'fragmentMain',
5287
+ module: shapesShader,
5288
+ entryPoint: 'fragMain',
5256
5289
  targets: [{ format: 'bgra8unorm', blend: $.blendConfigs.normal }]
5257
5290
  },
5258
5291
  primitive: { topology: 'triangle-strip', stripIndexFormat: 'uint32' },
@@ -5296,7 +5329,7 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
5296
5329
  v[i++] = ti;
5297
5330
 
5298
5331
  vertIndex = i;
5299
- drawStack.push(0, 4);
5332
+ drawStack.push($._shapesPL, 4);
5300
5333
  };
5301
5334
 
5302
5335
  const addArc = (x, y, a, b, startAngle, endAngle, n, ci, ti) => {
@@ -5328,7 +5361,7 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
5328
5361
  }
5329
5362
 
5330
5363
  vertIndex = i;
5331
- drawStack.push(0, (n + 1) * 2);
5364
+ drawStack.push($._shapesPL, (n + 1) * 2);
5332
5365
  };
5333
5366
 
5334
5367
  const addArcStroke = (x, y, outerA, outerB, innerA, innerB, startAngle, endAngle, n, ci, ti) => {
@@ -5363,7 +5396,7 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
5363
5396
  }
5364
5397
 
5365
5398
  vertIndex = i;
5366
- drawStack.push(0, (n + 1) * 2);
5399
+ drawStack.push($._shapesPL, (n + 1) * 2);
5367
5400
  };
5368
5401
 
5369
5402
  $.rectMode = (x) => ($._rectMode = x);
@@ -5468,6 +5501,13 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
5468
5501
 
5469
5502
  $.square = (x, y, s) => $.rect(x, y, s, s);
5470
5503
 
5504
+ $.plane = (x, y, w, h) => {
5505
+ h ??= w;
5506
+ let [l, r, t, b] = $._calcBox(x, y, w, h, 'center');
5507
+ if ($._matrixDirty) $._saveMatrix();
5508
+ addRect(l, t, r, t, r, b, l, b, $._fill, $._matrixIndex);
5509
+ };
5510
+
5471
5511
  // prettier-ignore
5472
5512
  const getArcSegments = (d) =>
5473
5513
  d < 4 ? 6 :
@@ -5713,7 +5753,7 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
5713
5753
  addVert(sv[4], sv[5], sv[6], sv[7]); // v1
5714
5754
  addVert(sv[12], sv[13], sv[14], sv[15]); // v3
5715
5755
  addVert(sv[8], sv[9], sv[10], sv[11]); // v2
5716
- drawStack.push(0, 4);
5756
+ drawStack.push($._shapesPL, 4);
5717
5757
  } else {
5718
5758
  // triangulate the shape
5719
5759
  for (let i = 1; i < shapeVertCount - 1; i++) {
@@ -5725,7 +5765,7 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
5725
5765
  addVert(sv[v1], sv[v1 + 1], sv[v1 + 2], sv[v1 + 3]);
5726
5766
  addVert(sv[v2], sv[v2 + 1], sv[v2 + 2], sv[v2 + 3]);
5727
5767
  }
5728
- drawStack.push(0, (shapeVertCount - 2) * 3);
5768
+ drawStack.push($._shapesPL, (shapeVertCount - 2) * 3);
5729
5769
  }
5730
5770
  }
5731
5771
 
@@ -5795,7 +5835,7 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
5795
5835
 
5796
5836
  let vertexBuffer = Q5.device.createBuffer({
5797
5837
  size: vertIndex * 4,
5798
- usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
5838
+ usage: GPUBufferUsage.VERTEX,
5799
5839
  mappedAtCreation: true
5800
5840
  });
5801
5841
 
@@ -5808,53 +5848,58 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
5808
5848
  });
5809
5849
 
5810
5850
  $._hooks.postRender.push(() => {
5811
- drawStack = $.drawStack;
5812
5851
  vertIndex = 0;
5813
5852
  });
5814
5853
  };
5815
5854
  Q5.renderers.webgpu.image = ($, q) => {
5816
- let vertexStack = new Float32Array($._graphics ? 1000 : 1e7),
5817
- vertIndex = 0;
5855
+ $._imagePL = 1;
5856
+ $._videoPL = 2;
5818
5857
 
5819
- let imageShaderCode = `
5820
- struct Uniforms {
5821
- halfWidth: f32,
5822
- halfHeight: f32
5823
- }
5858
+ $._imageShaderCode =
5859
+ $._baseShaderCode +
5860
+ /* wgsl */ `
5824
5861
  struct VertexParams {
5862
+ @builtin(vertex_index) vertexIndex : u32,
5825
5863
  @location(0) pos: vec2f,
5826
5864
  @location(1) texCoord: vec2f,
5827
5865
  @location(2) tintIndex: f32,
5828
5866
  @location(3) matrixIndex: f32,
5829
5867
  @location(4) imageAlpha: f32
5830
5868
  }
5831
- struct FragmentParams {
5869
+ struct FragParams {
5832
5870
  @builtin(position) position: vec4f,
5833
5871
  @location(0) texCoord: vec2f,
5834
5872
  @location(1) tintColor: vec4f,
5835
5873
  @location(2) imageAlpha: f32
5836
5874
  }
5837
5875
 
5838
- @group(0) @binding(0) var<uniform> uniforms: Uniforms;
5876
+ @group(0) @binding(0) var<uniform> q: Q5;
5839
5877
  @group(0) @binding(1) var<storage> transforms: array<mat4x4<f32>>;
5840
5878
  @group(0) @binding(2) var<storage> colors : array<vec4f>;
5841
5879
 
5842
5880
  @group(1) @binding(0) var samp: sampler;
5843
- @group(1) @binding(1) var texture: texture_2d<f32>;
5881
+ @group(1) @binding(1) var tex: texture_2d<f32>;
5844
5882
 
5845
5883
  fn transformVertex(pos: vec2f, matrixIndex: f32) -> vec4f {
5846
5884
  var vert = vec4f(pos, 0.0, 1.0);
5847
5885
  vert = transforms[i32(matrixIndex)] * vert;
5848
- vert.x /= uniforms.halfWidth;
5849
- vert.y /= uniforms.halfHeight;
5886
+ vert.x /= q.halfWidth;
5887
+ vert.y /= q.halfHeight;
5850
5888
  return vert;
5851
5889
  }
5852
5890
 
5891
+ fn applyTint(texColor: vec4f, tintColor: vec4f) -> vec4f {
5892
+ // apply the tint color to the sampled texture color at full strength
5893
+ let tinted = vec4f(texColor.rgb * tintColor.rgb, texColor.a);
5894
+ // mix in the tint using the tint alpha as the blend strength
5895
+ return mix(texColor, tinted, tintColor.a);
5896
+ }
5897
+
5853
5898
  @vertex
5854
- fn vertexMain(v: VertexParams) -> FragmentParams {
5899
+ fn vertexMain(v: VertexParams) -> FragParams {
5855
5900
  var vert = transformVertex(v.pos, v.matrixIndex);
5856
5901
 
5857
- var f: FragmentParams;
5902
+ var f: FragParams;
5858
5903
  f.position = vert;
5859
5904
  f.texCoord = v.texCoord;
5860
5905
  f.tintColor = colors[i32(v.tintIndex)];
@@ -5863,29 +5908,30 @@ fn vertexMain(v: VertexParams) -> FragmentParams {
5863
5908
  }
5864
5909
 
5865
5910
  @fragment
5866
- fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
5867
- let texColor = textureSample(texture, samp, f.texCoord);
5868
-
5869
- // Mix original and tinted colors using tint alpha as blend factor
5870
- let tinted = vec4f(texColor.rgb * f.tintColor.rgb, texColor.a * f.imageAlpha);
5871
- return mix(texColor, tinted, f.tintColor.a);
5911
+ fn fragMain(f: FragParams) -> @location(0) vec4f {
5912
+ var texColor = textureSample(tex, samp, f.texCoord);
5913
+ texColor.a *= f.imageAlpha;
5914
+ return applyTint(texColor, f.tintColor);
5872
5915
  }
5873
5916
  `;
5874
5917
 
5875
5918
  let imageShader = Q5.device.createShaderModule({
5876
5919
  label: 'imageShader',
5877
- code: imageShaderCode
5920
+ code: $._imageShaderCode
5878
5921
  });
5879
5922
 
5880
- let videoShaderCode = imageShaderCode
5923
+ $._videoShaderCode = $._imageShaderCode
5881
5924
  .replace('texture_2d<f32>', 'texture_external')
5882
5925
  .replace('textureSample', 'textureSampleBaseClampToEdge');
5883
5926
 
5884
5927
  let videoShader = Q5.device.createShaderModule({
5885
5928
  label: 'videoShader',
5886
- code: videoShaderCode
5929
+ code: $._videoShaderCode
5887
5930
  });
5888
5931
 
5932
+ let vertexStack = new Float32Array($._graphics ? 1000 : 1e7),
5933
+ vertIndex = 0;
5934
+
5889
5935
  let vertexBufferLayout = {
5890
5936
  arrayStride: 28,
5891
5937
  attributes: [
@@ -5949,7 +5995,7 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
5949
5995
  },
5950
5996
  fragment: {
5951
5997
  module: imageShader,
5952
- entryPoint: 'fragmentMain',
5998
+ entryPoint: 'fragMain',
5953
5999
  targets: [{ format: 'bgra8unorm', blend: $.blendConfigs.normal }]
5954
6000
  },
5955
6001
  primitive: { topology: 'triangle-strip', stripIndexFormat: 'uint32' },
@@ -5968,7 +6014,7 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
5968
6014
  },
5969
6015
  fragment: {
5970
6016
  module: videoShader,
5971
- entryPoint: 'fragmentMain',
6017
+ entryPoint: 'fragMain',
5972
6018
  targets: [{ format: 'bgra8unorm', blend: $.blendConfigs.normal }]
5973
6019
  },
5974
6020
  primitive: { topology: 'triangle-strip', stripIndexFormat: 'uint32' },
@@ -6081,7 +6127,7 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
6081
6127
  let isVideo;
6082
6128
  if (img.textureIndex == undefined) {
6083
6129
  isVideo = img.tagName == 'VIDEO';
6084
- if (!isVideo || !img.width) return;
6130
+ if (!isVideo || !img.width || !img.currentTime) return;
6085
6131
  if (img.flipped) $.scale(-1, 1);
6086
6132
  }
6087
6133
 
@@ -6129,7 +6175,7 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
6129
6175
  v1 = (sy + sh) / h,
6130
6176
  ti = $._matrixIndex,
6131
6177
  ci = $._tint,
6132
- ia = $._imageAlpha;
6178
+ ia = $._globalAlpha;
6133
6179
 
6134
6180
  addVert(l, t, u0, v0, ci, ti, ia);
6135
6181
  addVert(r, t, u1, v0, ci, ti, ia);
@@ -6137,7 +6183,7 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
6137
6183
  addVert(r, b, u1, v1, ci, ti, ia);
6138
6184
 
6139
6185
  if (!isVideo) {
6140
- $.drawStack.push(1, img.textureIndex);
6186
+ $.drawStack.push($._imagePL, img.textureIndex);
6141
6187
  } else {
6142
6188
  // render video
6143
6189
  let externalTexture = Q5.device.importExternalTexture({ source: img });
@@ -6154,7 +6200,7 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
6154
6200
  })
6155
6201
  );
6156
6202
 
6157
- $.drawStack.push(2, $._textureBindGroups.length - 1);
6203
+ $.drawStack.push($._videoPL, $._textureBindGroups.length - 1);
6158
6204
 
6159
6205
  if (img.flipped) $.scale(-1, 1);
6160
6206
  }
@@ -6224,7 +6270,7 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
6224
6270
 
6225
6271
  let vertexBuffer = Q5.device.createBuffer({
6226
6272
  size: vertIndex * 5,
6227
- usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
6273
+ usage: GPUBufferUsage.VERTEX,
6228
6274
  mappedAtCreation: true
6229
6275
  });
6230
6276
 
@@ -6258,16 +6304,16 @@ Q5.DILATE = 6;
6258
6304
  Q5.ERODE = 7;
6259
6305
  Q5.BLUR = 8;
6260
6306
  Q5.renderers.webgpu.text = ($, q) => {
6261
- let textShaderCode = `
6262
- struct Uniforms {
6263
- halfWidth: f32,
6264
- halfHeight: f32
6265
- }
6307
+ $._textPL = 3;
6308
+
6309
+ $._textShaderCode =
6310
+ $._baseShaderCode +
6311
+ /* wgsl */ `
6266
6312
  struct VertexParams {
6267
- @builtin(vertex_index) vertex : u32,
6268
- @builtin(instance_index) instance : u32
6313
+ @builtin(vertex_index) vertexIndex : u32,
6314
+ @builtin(instance_index) instanceIndex : u32
6269
6315
  }
6270
- struct FragmentParams {
6316
+ struct FragParams {
6271
6317
  @builtin(position) position : vec4f,
6272
6318
  @location(0) texCoord : vec2f,
6273
6319
  @location(1) fillColor : vec4f,
@@ -6289,7 +6335,7 @@ struct Text {
6289
6335
  strokeWeight: f32
6290
6336
  }
6291
6337
 
6292
- @group(0) @binding(0) var<uniform> uniforms: Uniforms;
6338
+ @group(0) @binding(0) var<uniform> q: Q5;
6293
6339
  @group(0) @binding(1) var<storage> transforms: array<mat4x4<f32>>;
6294
6340
  @group(0) @binding(2) var<storage> colors : array<vec4f>;
6295
6341
 
@@ -6302,32 +6348,48 @@ struct Text {
6302
6348
 
6303
6349
  const quad = array(vec2f(0, -1), vec2f(1, -1), vec2f(0, 0), vec2f(1, 0));
6304
6350
 
6305
- fn sampleMsdf(texCoord: vec2f) -> f32 {
6306
- let c = textureSample(fontTexture, fontSampler, texCoord);
6307
- return max(min(c.r, c.g), min(max(c.r, c.g), c.b));
6351
+ fn calcPos(i: u32, char: vec4f, fontChar: Char, text: Text) -> vec2f {
6352
+ return ((quad[i] * fontChar.size + char.xy + fontChar.offset) *
6353
+ text.scale) + text.pos;
6354
+ }
6355
+
6356
+ fn calcUV(i: u32, fontChar: Char) -> vec2f {
6357
+ return (quad[i] * vec2f(1, -1)) *
6358
+ fontChar.texExtent + fontChar.texOffset;
6308
6359
  }
6309
6360
 
6310
6361
  fn transformVertex(pos: vec2f, matrixIndex: f32) -> vec4f {
6311
6362
  var vert = vec4f(pos, 0.0, 1.0);
6312
6363
  vert = transforms[i32(matrixIndex)] * vert;
6313
- vert.x /= uniforms.halfWidth;
6314
- vert.y /= uniforms.halfHeight;
6364
+ vert.x /= q.halfWidth;
6365
+ vert.y /= q.halfHeight;
6315
6366
  return vert;
6316
6367
  }
6317
6368
 
6369
+ fn calcDist(texCoord: vec2f, edgeWidth: f32) -> f32 {
6370
+ let c = textureSample(fontTexture, fontSampler, texCoord);
6371
+ let sigDist = max(min(c.r, c.g), min(max(c.r, c.g), c.b)) - edgeWidth;
6372
+
6373
+ let pxRange = 4.0;
6374
+ let sz = vec2f(textureDimensions(fontTexture, 0));
6375
+ let dx = sz.x * length(vec2f(dpdxFine(texCoord.x), dpdyFine(texCoord.x)));
6376
+ let dy = sz.y * length(vec2f(dpdxFine(texCoord.y), dpdyFine(texCoord.y)));
6377
+ let toPixels = pxRange * inverseSqrt(dx * dx + dy * dy);
6378
+ return sigDist * toPixels;
6379
+ }
6380
+
6318
6381
  @vertex
6319
- fn vertexMain(v : VertexParams) -> FragmentParams {
6320
- let char = textChars[v.instance];
6382
+ fn vertexMain(v : VertexParams) -> FragParams {
6383
+ let char = textChars[v.instanceIndex];
6321
6384
  let text = textMetadata[i32(char.w)];
6322
6385
  let fontChar = fontChars[i32(char.z)];
6386
+ let pos = calcPos(v.vertexIndex, char, fontChar, text);
6323
6387
 
6324
- let charPos = ((quad[v.vertex] * fontChar.size + char.xy + fontChar.offset) * text.scale) + text.pos;
6325
-
6326
- var vert = transformVertex(charPos, text.matrixIndex);
6388
+ var vert = transformVertex(pos, text.matrixIndex);
6327
6389
 
6328
- var f : FragmentParams;
6390
+ var f : FragParams;
6329
6391
  f.position = vert;
6330
- f.texCoord = (quad[v.vertex] * vec2f(1, -1)) * fontChar.texExtent + fontChar.texOffset;
6392
+ f.texCoord = calcUV(v.vertexIndex, fontChar);
6331
6393
  f.fillColor = colors[i32(text.fillIndex)];
6332
6394
  f.strokeColor = colors[i32(text.strokeIndex)];
6333
6395
  f.strokeWeight = text.strokeWeight;
@@ -6335,19 +6397,13 @@ fn vertexMain(v : VertexParams) -> FragmentParams {
6335
6397
  }
6336
6398
 
6337
6399
  @fragment
6338
- fn fragmentMain(f : FragmentParams) -> @location(0) vec4f {
6339
- let pxRange = 4.0;
6340
- let sz = vec2f(textureDimensions(fontTexture, 0));
6341
- let dx = sz.x * length(vec2f(dpdxFine(f.texCoord.x), dpdyFine(f.texCoord.x)));
6342
- let dy = sz.y * length(vec2f(dpdxFine(f.texCoord.y), dpdyFine(f.texCoord.y)));
6343
- let toPixels = pxRange * inverseSqrt(dx * dx + dy * dy);
6344
- let sigDist = sampleMsdf(f.texCoord) - 0.5;
6345
- let pxDist = sigDist * toPixels;
6346
- let edgeWidth = 0.5;
6400
+ fn fragMain(f : FragParams) -> @location(0) vec4f {
6401
+ let edge = 0.5;
6402
+ let dist = calcDist(f.texCoord, edge);
6347
6403
 
6348
6404
  if (f.strokeWeight == 0.0) {
6349
- let fillAlpha = smoothstep(-edgeWidth, edgeWidth, pxDist);
6350
- var color = vec4f(f.fillColor.rgb, f.fillColor.a * fillAlpha);
6405
+ let fillAlpha = smoothstep(-edge, edge, dist);
6406
+ let color = vec4f(f.fillColor.rgb, f.fillColor.a * fillAlpha);
6351
6407
  if (color.a < 0.01) {
6352
6408
  discard;
6353
6409
  }
@@ -6355,8 +6411,8 @@ fn fragmentMain(f : FragmentParams) -> @location(0) vec4f {
6355
6411
  }
6356
6412
 
6357
6413
  let halfStroke = f.strokeWeight / 2.0;
6358
- let fillAlpha = smoothstep(-edgeWidth, edgeWidth, pxDist - halfStroke);
6359
- let strokeAlpha = smoothstep(-edgeWidth, edgeWidth, pxDist + halfStroke);
6414
+ let fillAlpha = smoothstep(-edge, edge, dist - halfStroke);
6415
+ let strokeAlpha = smoothstep(-edge, edge, dist + halfStroke);
6360
6416
  var color = mix(f.strokeColor, f.fillColor, fillAlpha);
6361
6417
  color = vec4f(color.rgb, color.a * strokeAlpha);
6362
6418
  if (color.a < 0.01) {
@@ -6368,11 +6424,11 @@ fn fragmentMain(f : FragmentParams) -> @location(0) vec4f {
6368
6424
 
6369
6425
  let textShader = Q5.device.createShaderModule({
6370
6426
  label: 'textShader',
6371
- code: textShaderCode
6427
+ code: $._textShaderCode
6372
6428
  });
6373
6429
 
6374
6430
  let textBindGroupLayout = Q5.device.createBindGroupLayout({
6375
- label: 'MSDF text group layout',
6431
+ label: 'textBindGroupLayout',
6376
6432
  entries: [
6377
6433
  {
6378
6434
  binding: 0,
@@ -6395,7 +6451,7 @@ fn fragmentMain(f : FragmentParams) -> @location(0) vec4f {
6395
6451
  });
6396
6452
 
6397
6453
  let fontBindGroupLayout = Q5.device.createBindGroupLayout({
6398
- label: 'MSDF font group layout',
6454
+ label: 'fontBindGroupLayout',
6399
6455
  entries: [
6400
6456
  {
6401
6457
  binding: 0,
@@ -6420,12 +6476,12 @@ fn fragmentMain(f : FragmentParams) -> @location(0) vec4f {
6420
6476
  });
6421
6477
 
6422
6478
  $._pipelineConfigs[3] = {
6423
- label: 'msdf font pipeline',
6479
+ label: 'textPipeline',
6424
6480
  layout: fontPipelineLayout,
6425
6481
  vertex: { module: textShader, entryPoint: 'vertexMain' },
6426
6482
  fragment: {
6427
6483
  module: textShader,
6428
- entryPoint: 'fragmentMain',
6484
+ entryPoint: 'fragMain',
6429
6485
  targets: [{ format: 'bgra8unorm', blend: $.blendConfigs.normal }]
6430
6486
  },
6431
6487
  primitive: { topology: 'triangle-strip', stripIndexFormat: 'uint32' },
@@ -6526,7 +6582,7 @@ fn fragmentMain(f : FragmentParams) -> @location(0) vec4f {
6526
6582
  charsBuffer.unmap();
6527
6583
 
6528
6584
  let fontBindGroup = Q5.device.createBindGroup({
6529
- label: 'msdf font bind group',
6585
+ label: 'fontBindGroup',
6530
6586
  layout: fontBindGroupLayout,
6531
6587
  entries: [
6532
6588
  { binding: 0, resource: texture.createView() },
@@ -6771,7 +6827,7 @@ fn fragmentMain(f : FragmentParams) -> @location(0) vec4f {
6771
6827
  txt[7] = 0; // padding
6772
6828
 
6773
6829
  textStack.push(txt);
6774
- $.drawStack.push(3, measurements.printedCharCount, $._font.index);
6830
+ $.drawStack.push($._textPL, measurements.printedCharCount, $._font.index);
6775
6831
  };
6776
6832
 
6777
6833
  $.textWidth = (str) => {
@@ -6836,7 +6892,7 @@ fn fragmentMain(f : FragmentParams) -> @location(0) vec4f {
6836
6892
  // create a single buffer for all the char data
6837
6893
  let charBuffer = Q5.device.createBuffer({
6838
6894
  size: totalTextSize,
6839
- usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
6895
+ usage: GPUBufferUsage.STORAGE,
6840
6896
  mappedAtCreation: true
6841
6897
  });
6842
6898
 
@@ -6851,7 +6907,7 @@ fn fragmentMain(f : FragmentParams) -> @location(0) vec4f {
6851
6907
  let textBuffer = Q5.device.createBuffer({
6852
6908
  label: 'textBuffer',
6853
6909
  size: totalMetadataSize,
6854
- usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
6910
+ usage: GPUBufferUsage.STORAGE,
6855
6911
  mappedAtCreation: true
6856
6912
  });
6857
6913
 
@@ -6863,7 +6919,7 @@ fn fragmentMain(f : FragmentParams) -> @location(0) vec4f {
6863
6919
 
6864
6920
  // create a single bind group for the text buffer and metadata buffer
6865
6921
  $._textBindGroup = Q5.device.createBindGroup({
6866
- label: 'msdf text bind group',
6922
+ label: 'textBindGroup',
6867
6923
  layout: textBindGroupLayout,
6868
6924
  entries: [
6869
6925
  { binding: 0, resource: { buffer: charBuffer } },
@@ -6877,3 +6933,72 @@ fn fragmentMain(f : FragmentParams) -> @location(0) vec4f {
6877
6933
  textStack = [];
6878
6934
  });
6879
6935
  };
6936
+ Q5.renderers.webgpu.shaders = ($) => {
6937
+ let pipelineTypes = ['shapes', 'image', 'video', 'text'];
6938
+
6939
+ let plCounters = {
6940
+ shapes: 100,
6941
+ image: 200,
6942
+ video: 300,
6943
+ text: 400
6944
+ };
6945
+
6946
+ $._createShader = (code, type = 'shapes') => {
6947
+ code = code.trim();
6948
+
6949
+ // default shader code
6950
+ let def = $['_' + type + 'ShaderCode'];
6951
+
6952
+ let defVertIdx = def.indexOf('@vertex');
6953
+ let defFragIdx = def.indexOf('@fragment');
6954
+
6955
+ if (!code.includes('@fragment')) {
6956
+ // replace @vertex section
6957
+ code = def.slice(0, defVertIdx) + code + '\n\n' + def.slice(defFragIdx);
6958
+ } else if (!code.includes('@vertex')) {
6959
+ // replace @fragment section
6960
+ code = def.slice(0, defFragIdx) + code;
6961
+ } else {
6962
+ // replace @vertex and @fragment sections
6963
+ code = def.slice(0, defVertIdx) + code;
6964
+ }
6965
+
6966
+ let shader = Q5.device.createShaderModule({
6967
+ label: type + 'Shader',
6968
+ code: code
6969
+ });
6970
+ shader.type = type;
6971
+
6972
+ let pipelineIndex = pipelineTypes.indexOf(type);
6973
+ let config = Object.assign({}, $._pipelineConfigs[pipelineIndex]);
6974
+ config.vertex.module = config.fragment.module = shader;
6975
+
6976
+ let pl = plCounters[type];
6977
+ $._pipelines[pl] = Q5.device.createRenderPipeline(config);
6978
+ shader.pipelineIndex = pl;
6979
+ plCounters[type]++;
6980
+
6981
+ return shader;
6982
+ };
6983
+
6984
+ $.createShader = $.createShapesShader = $._createShader;
6985
+ $.createImageShader = (code) => $._createShader(code, 'image');
6986
+ $.createVideoShader = (code) => $._createShader(code, 'video');
6987
+ $.createTextShader = (code) => $._createShader(code, 'text');
6988
+
6989
+ $.shader = (shader) => {
6990
+ $['_' + shader.type + 'PL'] = shader.pipelineIndex;
6991
+ };
6992
+
6993
+ $.resetShader = (type = 'shapes') => {
6994
+ $['_' + type + 'PL'] = pipelineTypes.indexOf(type);
6995
+ };
6996
+
6997
+ $.resetShaders = () => {
6998
+ $._shapesPL = 0;
6999
+ $._imagePL = 1;
7000
+ $._videoPL = 2;
7001
+ $._textPL = 3;
7002
+ $._planePL = 4;
7003
+ };
7004
+ };