q5 2.20.5 → 2.20.7

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 (4) hide show
  1. package/package.json +1 -1
  2. package/q5.d.ts +6 -6
  3. package/q5.js +205 -120
  4. package/q5.min.js +1 -1
package/q5.js CHANGED
@@ -29,6 +29,7 @@ function Q5(scope, parent, renderer) {
29
29
  Q5._hasGlobal = $._isGlobal = true;
30
30
  globalScope = Q5._esm ? globalThis : !Q5._server ? window : global;
31
31
  }
32
+ if (scope == 'graphics') $._graphics = true;
32
33
 
33
34
  let q = new Proxy($, {
34
35
  set: (t, p, v) => {
@@ -171,18 +172,15 @@ function Q5(scope, parent, renderer) {
171
172
  }
172
173
  }
173
174
 
174
- if (scope == 'graphics') {
175
- $._graphics = true;
176
- return;
177
- }
175
+ if ($._webgpuFallback) $.colorMode('rgb', 1);
176
+
177
+ if ($._graphics) return;
178
178
 
179
179
  if (scope == 'global') {
180
180
  Object.assign(Q5, $);
181
181
  delete Q5.Q5;
182
182
  }
183
183
 
184
- if ($._webgpuFallback) $.colorMode('rgb', 1);
185
-
186
184
  for (let m of Q5.methods.init) {
187
185
  m.call($);
188
186
  }
@@ -324,7 +322,7 @@ if (typeof document == 'object') {
324
322
  });
325
323
  }
326
324
  Q5.modules.canvas = ($, q) => {
327
- $._OffscreenCanvas =
325
+ $._Canvas =
328
326
  window.OffscreenCanvas ||
329
327
  function () {
330
328
  return document.createElement('canvas');
@@ -335,7 +333,7 @@ Q5.modules.canvas = ($, q) => {
335
333
  q.canvas = Q5._createServerCanvas(100, 100);
336
334
  }
337
335
  } else if ($._scope == 'image' || $._scope == 'graphics') {
338
- q.canvas = new $._OffscreenCanvas(100, 100);
336
+ q.canvas = new $._Canvas(100, 100);
339
337
  }
340
338
 
341
339
  if (!$.canvas) {
@@ -411,12 +409,13 @@ Q5.modules.canvas = ($, q) => {
411
409
  }
412
410
  if ($._beginRender) $._beginRender();
413
411
 
412
+ c.mousePressed = (cb) => c.addEventListener('mousedown', cb);
413
+
414
414
  return rend;
415
415
  };
416
416
 
417
- $.createGraphics = function (w, h, opt) {
418
- let g = new Q5('graphics');
419
- opt ??= {};
417
+ $.createGraphics = function (w, h, opt = {}) {
418
+ let g = new Q5('graphics', undefined, opt.renderer || ($._webgpuFallback ? 'webgpu-fallback' : $._renderer));
420
419
  opt.alpha ??= true;
421
420
  opt.colorSpace ??= $.canvas.colorSpace;
422
421
  g.createCanvas.call($, w, h, opt);
@@ -698,7 +697,7 @@ Q5.renderers.c2d.canvas = ($, q) => {
698
697
 
699
698
  let o;
700
699
  if ($.frameCount > 1) {
701
- o = new $._OffscreenCanvas(c.width, c.height);
700
+ o = new $._Canvas(c.width, c.height);
702
701
  o.w = c.w;
703
702
  o.h = c.h;
704
703
  let oCtx = o.getContext('2d');
@@ -1443,7 +1442,7 @@ Q5.renderers.c2d.image = ($, q) => {
1443
1442
  if ($._scope == 'image') {
1444
1443
  $.resize = (w, h) => {
1445
1444
  let c = $.canvas;
1446
- let o = new $._OffscreenCanvas(c.width, c.height);
1445
+ let o = new $._Canvas(c.width, c.height);
1447
1446
  let tmpCtx = o.getContext('2d', {
1448
1447
  colorSpace: c.colorSpace
1449
1448
  });
@@ -1581,7 +1580,7 @@ Q5.renderers.c2d.image = ($, q) => {
1581
1580
 
1582
1581
  $._saveCanvas = async (data, ext) => {
1583
1582
  data = data.canvas || data;
1584
- if (data instanceof OffscreenCanvas) {
1583
+ if (data instanceof HTMLCanvasElement || data instanceof OffscreenCanvas) {
1585
1584
  const blob = await data.convertToBlob({ type: 'image/' + ext });
1586
1585
 
1587
1586
  return await new Promise((resolve) => {
@@ -2610,6 +2609,8 @@ Q5.modules.dom = ($, q) => {
2610
2609
  return el;
2611
2610
  };
2612
2611
 
2612
+ el.mousePressed = (cb) => el.addEventListener('mousedown', cb);
2613
+
2613
2614
  $._elements.push(el);
2614
2615
  if ($.canvas) $.canvas.parentElement.append(el);
2615
2616
  else document.body.append(el);
@@ -4469,15 +4470,14 @@ Q5.renderers.webgpu.canvas = ($, q) => {
4469
4470
  c.width = $.width = 500;
4470
4471
  c.height = $.height = 500;
4471
4472
 
4472
- // c2d graphics context
4473
- $._g = $.createGraphics(1, 1);
4473
+ $._g = $.createGraphics(1, 1, { renderer: 'c2d' });
4474
4474
 
4475
4475
  if ($.colorMode) $.colorMode('rgb', 1);
4476
4476
 
4477
4477
  let pass,
4478
4478
  mainView,
4479
- frameTextureA,
4480
- frameTextureB,
4479
+ frameA,
4480
+ frameB,
4481
4481
  frameSampler,
4482
4482
  framePipeline,
4483
4483
  frameBindGroup,
@@ -4486,6 +4486,7 @@ Q5.renderers.webgpu.canvas = ($, q) => {
4486
4486
 
4487
4487
  $._pipelineConfigs = [];
4488
4488
  $._pipelines = [];
4489
+ $._buffers = [];
4489
4490
 
4490
4491
  // local variables used for slightly better performance
4491
4492
  // stores pipeline shifts and vertex counts/image indices
@@ -4549,8 +4550,8 @@ Q5.renderers.webgpu.canvas = ($, q) => {
4549
4550
  GPUTextureUsage.TEXTURE_BINDING |
4550
4551
  GPUTextureUsage.RENDER_ATTACHMENT;
4551
4552
 
4552
- frameTextureA = Q5.device.createTexture({ size, format, usage });
4553
- frameTextureB = Q5.device.createTexture({ size, format, usage });
4553
+ $._frameA = frameA = Q5.device.createTexture({ size, format, usage });
4554
+ $._frameB = frameB = Q5.device.createTexture({ size, format, usage });
4554
4555
 
4555
4556
  let finalShader = Q5.device.createShaderModule({
4556
4557
  code: `
@@ -4673,7 +4674,7 @@ Q5.renderers.webgpu.canvas = ($, q) => {
4673
4674
  $._hsw = v / 2;
4674
4675
  };
4675
4676
 
4676
- const MAX_TRANSFORMS = 1e7, // or whatever maximum you need
4677
+ const MAX_TRANSFORMS = $._graphics ? 1000 : 1e7,
4677
4678
  MATRIX_SIZE = 16, // 4x4 matrix
4678
4679
  transforms = new Float32Array(MAX_TRANSFORMS * MATRIX_SIZE);
4679
4680
 
@@ -4953,8 +4954,6 @@ Q5.renderers.webgpu.canvas = ($, q) => {
4953
4954
  shouldClear = true;
4954
4955
  };
4955
4956
 
4956
- $.createGraphics = (w, h, opt) => $._g.createGraphics(w, h, opt);
4957
-
4958
4957
  const _drawFrame = () => {
4959
4958
  pass.setPipeline(framePipeline);
4960
4959
  pass.setBindGroup(0, frameBindGroup);
@@ -4963,14 +4962,13 @@ Q5.renderers.webgpu.canvas = ($, q) => {
4963
4962
 
4964
4963
  $._beginRender = () => {
4965
4964
  // swap the frame textures
4966
- const temp = frameTextureA;
4967
- frameTextureA = frameTextureB;
4968
- frameTextureB = temp;
4969
- $.canvas.texture = frameTextureA;
4965
+ const temp = frameA;
4966
+ frameA = frameB;
4967
+ frameB = temp;
4970
4968
 
4971
4969
  $.encoder = Q5.device.createCommandEncoder();
4972
4970
 
4973
- let target = shouldClear ? $.ctx.getCurrentTexture().createView() : frameTextureA.createView();
4971
+ let target = shouldClear ? $.ctx.getCurrentTexture().createView() : frameA.createView();
4974
4972
 
4975
4973
  pass = q.pass = $.encoder.beginRenderPass({
4976
4974
  label: 'q5-webgpu',
@@ -4990,7 +4988,7 @@ Q5.renderers.webgpu.canvas = ($, q) => {
4990
4988
  layout: framePipeline.getBindGroupLayout(0),
4991
4989
  entries: [
4992
4990
  { binding: 0, resource: frameSampler },
4993
- { binding: 1, resource: frameTextureB.createView() }
4991
+ { binding: 1, resource: frameB.createView() }
4994
4992
  ]
4995
4993
  });
4996
4994
 
@@ -5048,7 +5046,7 @@ Q5.renderers.webgpu.canvas = ($, q) => {
5048
5046
  // v is the number of vertices
5049
5047
  pass.draw(v, 1, drawVertOffset);
5050
5048
  drawVertOffset += v;
5051
- } else if (curPipelineIndex == 1 || curPipelineIndex == 2) {
5049
+ } else if (curPipelineIndex <= 2) {
5052
5050
  // draw an image or video frame
5053
5051
  // v is the texture index
5054
5052
  pass.setBindGroup(1, $._textureBindGroups[v]);
@@ -5088,7 +5086,7 @@ Q5.renderers.webgpu.canvas = ($, q) => {
5088
5086
  layout: framePipeline.getBindGroupLayout(0),
5089
5087
  entries: [
5090
5088
  { binding: 0, resource: frameSampler },
5091
- { binding: 1, resource: frameTextureA.createView() }
5089
+ { binding: 1, resource: frameA.createView() }
5092
5090
  ]
5093
5091
  });
5094
5092
  _drawFrame();
@@ -5098,6 +5096,12 @@ Q5.renderers.webgpu.canvas = ($, q) => {
5098
5096
 
5099
5097
  Q5.device.queue.submit([$.encoder.finish()]);
5100
5098
 
5099
+ // destroy buffers
5100
+ Q5.device.queue.onSubmittedWorkDone().then(() => {
5101
+ for (let b of $._buffers) b.destroy();
5102
+ $._buffers = [];
5103
+ });
5104
+
5101
5105
  q.pass = $.encoder = null;
5102
5106
 
5103
5107
  // clear the stacks for the next frame
@@ -5123,6 +5127,11 @@ Q5.initWebGPU = async () => {
5123
5127
  return false;
5124
5128
  }
5125
5129
  Q5.device = await adapter.requestDevice();
5130
+
5131
+ Q5.device.lost.then((e) => {
5132
+ console.error('WebGPU crashed!');
5133
+ console.error(e);
5134
+ });
5126
5135
  }
5127
5136
  return true;
5128
5137
  };
@@ -5137,14 +5146,12 @@ Q5.webgpu = async function (scope, parent) {
5137
5146
  Q5.renderers.webgpu.drawing = ($, q) => {
5138
5147
  let c = $.canvas,
5139
5148
  drawStack = $.drawStack,
5140
- vertexStack = new Float32Array(1e7),
5149
+ vertexStack = new Float32Array($._graphics ? 1000 : 1e7),
5141
5150
  vertIndex = 0;
5142
5151
  const TAU = Math.PI * 2;
5143
5152
  const HALF_PI = Math.PI / 2;
5144
5153
 
5145
- let drawingShader = Q5.device.createShaderModule({
5146
- label: 'drawingShader',
5147
- code: `
5154
+ let drawingShaderCode = `
5148
5155
  struct Uniforms {
5149
5156
  halfWidth: f32,
5150
5157
  halfHeight: f32
@@ -5163,12 +5170,17 @@ struct FragmentParams {
5163
5170
  @group(0) @binding(1) var<storage> transforms: array<mat4x4<f32>>;
5164
5171
  @group(0) @binding(2) var<storage> colors : array<vec4f>;
5165
5172
 
5166
- @vertex
5167
- fn vertexMain(v: VertexParams) -> FragmentParams {
5168
- var vert = vec4f(v.pos, 0.0, 1.0);
5169
- vert = transforms[i32(v.matrixIndex)] * vert;
5173
+ fn transformVertex(pos: vec2f, matrixIndex: f32) -> vec4f {
5174
+ var vert = vec4f(pos, 0.0, 1.0);
5175
+ vert = transforms[i32(matrixIndex)] * vert;
5170
5176
  vert.x /= uniforms.halfWidth;
5171
5177
  vert.y /= uniforms.halfHeight;
5178
+ return vert;
5179
+ }
5180
+
5181
+ @vertex
5182
+ fn vertexMain(v: VertexParams) -> FragmentParams {
5183
+ var vert = transformVertex(v.pos, v.matrixIndex);
5172
5184
 
5173
5185
  var f: FragmentParams;
5174
5186
  f.position = vert;
@@ -5177,10 +5189,14 @@ fn vertexMain(v: VertexParams) -> FragmentParams {
5177
5189
  }
5178
5190
 
5179
5191
  @fragment
5180
- fn fragmentMain(@location(0) color: vec4f) -> @location(0) vec4f {
5181
- return color;
5192
+ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
5193
+ return f.color;
5182
5194
  }
5183
- `
5195
+ `;
5196
+
5197
+ let drawingShader = Q5.device.createShaderModule({
5198
+ label: 'drawingShader',
5199
+ code: drawingShaderCode
5184
5200
  });
5185
5201
 
5186
5202
  let vertexBufferLayout = {
@@ -5758,6 +5774,8 @@ fn fragmentMain(@location(0) color: vec4f) -> @location(0) vec4f {
5758
5774
  vertexBuffer.unmap();
5759
5775
 
5760
5776
  $.pass.setVertexBuffer(0, vertexBuffer);
5777
+
5778
+ $._buffers.push(vertexBuffer);
5761
5779
  });
5762
5780
 
5763
5781
  $._hooks.postRender.push(() => {
@@ -5766,7 +5784,7 @@ fn fragmentMain(@location(0) color: vec4f) -> @location(0) vec4f {
5766
5784
  });
5767
5785
  };
5768
5786
  Q5.renderers.webgpu.image = ($, q) => {
5769
- let vertexStack = new Float32Array(1e7),
5787
+ let vertexStack = new Float32Array($._graphics ? 1000 : 1e7),
5770
5788
  vertIndex = 0;
5771
5789
 
5772
5790
  let imageShaderCode = `
@@ -5779,13 +5797,13 @@ struct VertexParams {
5779
5797
  @location(1) texCoord: vec2f,
5780
5798
  @location(2) tintIndex: f32,
5781
5799
  @location(3) matrixIndex: f32,
5782
- @location(4) globalAlpha: f32
5800
+ @location(4) imageAlpha: f32
5783
5801
  }
5784
5802
  struct FragmentParams {
5785
5803
  @builtin(position) position: vec4f,
5786
5804
  @location(0) texCoord: vec2f,
5787
- @location(1) tintIndex: f32,
5788
- @location(2) globalAlpha: f32
5805
+ @location(1) tintColor: vec4f,
5806
+ @location(2) imageAlpha: f32
5789
5807
  }
5790
5808
 
5791
5809
  @group(0) @binding(0) var<uniform> uniforms: Uniforms;
@@ -5795,29 +5813,33 @@ struct FragmentParams {
5795
5813
  @group(1) @binding(0) var samp: sampler;
5796
5814
  @group(1) @binding(1) var texture: texture_2d<f32>;
5797
5815
 
5798
- @vertex
5799
- fn vertexMain(v: VertexParams) -> FragmentParams {
5800
- var vert = vec4f(v.pos, 0.0, 1.0);
5801
- vert = transforms[i32(v.matrixIndex)] * vert;
5816
+ fn transformVertex(pos: vec2f, matrixIndex: f32) -> vec4f {
5817
+ var vert = vec4f(pos, 0.0, 1.0);
5818
+ vert = transforms[i32(matrixIndex)] * vert;
5802
5819
  vert.x /= uniforms.halfWidth;
5803
5820
  vert.y /= uniforms.halfHeight;
5821
+ return vert;
5822
+ }
5823
+
5824
+ @vertex
5825
+ fn vertexMain(v: VertexParams) -> FragmentParams {
5826
+ var vert = transformVertex(v.pos, v.matrixIndex);
5804
5827
 
5805
5828
  var f: FragmentParams;
5806
5829
  f.position = vert;
5807
5830
  f.texCoord = v.texCoord;
5808
- f.tintIndex = v.tintIndex;
5809
- f.globalAlpha = v.globalAlpha;
5831
+ f.tintColor = colors[i32(v.tintIndex)];
5832
+ f.imageAlpha = v.imageAlpha;
5810
5833
  return f;
5811
5834
  }
5812
5835
 
5813
5836
  @fragment
5814
5837
  fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
5815
- let texColor = textureSample(texture, samp, f.texCoord);
5816
- let tintColor = colors[i32(f.tintIndex)];
5817
-
5818
- // Mix original and tinted colors using tint alpha as blend factor
5819
- let tinted = vec4f(texColor.rgb * tintColor.rgb, texColor.a * f.globalAlpha);
5820
- return mix(texColor, tinted, tintColor.a);
5838
+ let texColor = textureSample(texture, samp, f.texCoord);
5839
+
5840
+ // Mix original and tinted colors using tint alpha as blend factor
5841
+ let tinted = vec4f(texColor.rgb * f.tintColor.rgb, texColor.a * f.imageAlpha);
5842
+ return mix(texColor, tinted, f.tintColor.a);
5821
5843
  }
5822
5844
  `;
5823
5845
 
@@ -5842,7 +5864,7 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
5842
5864
  { shaderLocation: 1, offset: 8, format: 'float32x2' },
5843
5865
  { shaderLocation: 2, offset: 16, format: 'float32' }, // tintIndex
5844
5866
  { shaderLocation: 3, offset: 20, format: 'float32' }, // matrixIndex
5845
- { shaderLocation: 4, offset: 24, format: 'float32' } // globalAlpha
5867
+ { shaderLocation: 4, offset: 24, format: 'float32' } // imageAlpha
5846
5868
  ]
5847
5869
  };
5848
5870
 
@@ -5943,29 +5965,30 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
5943
5965
  let tIdx = 0,
5944
5966
  vidFrames = 0;
5945
5967
 
5946
- $._createTexture = (img) => {
5947
- let g = img;
5948
- if (img.canvas) img = img.canvas;
5968
+ $._addTexture = (img, texture) => {
5969
+ let cnv = img.canvas || img;
5949
5970
 
5950
- let textureSize = [img.width, img.height, 1];
5971
+ let textureSize = [cnv.width, cnv.height, 1];
5951
5972
 
5952
- let texture = Q5.device.createTexture({
5953
- size: textureSize,
5954
- format: 'bgra8unorm',
5955
- usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
5956
- });
5973
+ if (!texture) {
5974
+ texture = Q5.device.createTexture({
5975
+ size: textureSize,
5976
+ format: 'bgra8unorm',
5977
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
5978
+ });
5957
5979
 
5958
- Q5.device.queue.copyExternalImageToTexture(
5959
- { source: img },
5960
- {
5961
- texture,
5962
- colorSpace: $.canvas.colorSpace
5963
- },
5964
- textureSize
5965
- );
5980
+ Q5.device.queue.copyExternalImageToTexture(
5981
+ { source: cnv },
5982
+ {
5983
+ texture,
5984
+ colorSpace: $.canvas.colorSpace
5985
+ },
5986
+ textureSize
5987
+ );
5988
+ }
5966
5989
 
5967
- g.texture = texture;
5968
- g.textureIndex = tIdx + vidFrames;
5990
+ img.texture = texture;
5991
+ img.textureIndex = tIdx + vidFrames;
5969
5992
 
5970
5993
  $._textureBindGroups[tIdx + vidFrames] = Q5.device.createBindGroup({
5971
5994
  layout: textureLayout,
@@ -5978,21 +6001,41 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
5978
6001
  tIdx++;
5979
6002
  };
5980
6003
 
6004
+ let Q5Image = Q5.Image;
6005
+ Q5.Image = function (w, h) {
6006
+ let g = new Q5Image(...arguments);
6007
+ if (w > 1 && h > 1) {
6008
+ $._addTexture(g);
6009
+ g.modified = true;
6010
+ }
6011
+ return g;
6012
+ };
6013
+
5981
6014
  $.loadImage = (src, cb) => {
5982
6015
  q._preloadCount++;
5983
6016
  let g = $._g.loadImage(src, (img) => {
5984
- g.defaultWidth = img.width * $._defaultImageScale;
5985
- g.defaultHeight = img.height * $._defaultImageScale;
5986
- $._createTexture(img);
6017
+ $._addTexture(img);
5987
6018
  q._preloadCount--;
5988
6019
  if (cb) cb(g);
5989
6020
  });
5990
6021
  return g;
5991
6022
  };
5992
6023
 
6024
+ $.createImage = $._g.createImage;
6025
+
6026
+ let _createGraphics = $.createGraphics;
6027
+
6028
+ $.createGraphics = (w, h, opt) => {
6029
+ let g = _createGraphics(w, h, opt);
6030
+ $._addTexture(g, g._frameA);
6031
+ $._addTexture(g, g._frameB);
6032
+ g._beginRender();
6033
+ return g;
6034
+ };
6035
+
5993
6036
  $.imageMode = (x) => ($._imageMode = x);
5994
6037
 
5995
- const addVert = (x, y, u, v, ci, ti, ga) => {
6038
+ const addVert = (x, y, u, v, ci, ti, ia) => {
5996
6039
  let s = vertexStack,
5997
6040
  i = vertIndex;
5998
6041
  s[i++] = x;
@@ -6001,15 +6044,15 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
6001
6044
  s[i++] = v;
6002
6045
  s[i++] = ci;
6003
6046
  s[i++] = ti;
6004
- s[i++] = ga;
6047
+ s[i++] = ia;
6005
6048
  vertIndex = i;
6006
6049
  };
6007
6050
 
6008
6051
  $.image = (img, dx = 0, dy = 0, dw, dh, sx = 0, sy = 0, sw, sh) => {
6009
- let useExternal;
6052
+ let isVideo;
6010
6053
  if (img.textureIndex == undefined) {
6011
- useExternal = img._graphics || img.tagName == 'VIDEO';
6012
- if (!useExternal || !img.width) return;
6054
+ isVideo = img.tagName == 'VIDEO';
6055
+ if (!isVideo || !img.width) return;
6013
6056
  if (img.flipped) $.scale(-1, 1);
6014
6057
  }
6015
6058
 
@@ -6021,7 +6064,19 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
6021
6064
  h = cnv.height,
6022
6065
  pd = img._pixelDensity || 1;
6023
6066
 
6024
- if (!useExternal && img.modified) {
6067
+ if (img._graphics) {
6068
+ let g = img;
6069
+ if (g.drawStack.length) {
6070
+ g._render();
6071
+ g._finishRender();
6072
+ g.textureIndex += g.frameCount % 2 == 0 ? -1 : 1;
6073
+ g.resetMatrix();
6074
+ g._beginRender();
6075
+ g.frameCount++;
6076
+ }
6077
+ }
6078
+
6079
+ if (img.modified) {
6025
6080
  Q5.device.queue.copyExternalImageToTexture(
6026
6081
  { source: cnv },
6027
6082
  { texture: img.texture, colorSpace: $.canvas.colorSpace },
@@ -6045,17 +6100,17 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
6045
6100
  v1 = (sy + sh) / h,
6046
6101
  ti = $._matrixIndex,
6047
6102
  ci = $._tint,
6048
- ga = $._globalAlpha;
6103
+ ia = $._imageAlpha;
6049
6104
 
6050
- addVert(l, t, u0, v0, ci, ti, ga);
6051
- addVert(r, t, u1, v0, ci, ti, ga);
6052
- addVert(l, b, u0, v1, ci, ti, ga);
6053
- addVert(r, b, u1, v1, ci, ti, ga);
6105
+ addVert(l, t, u0, v0, ci, ti, ia);
6106
+ addVert(r, t, u1, v0, ci, ti, ia);
6107
+ addVert(l, b, u0, v1, ci, ti, ia);
6108
+ addVert(r, b, u1, v1, ci, ti, ia);
6054
6109
 
6055
- if (!useExternal) {
6110
+ if (!isVideo) {
6056
6111
  $.drawStack.push(1, img.textureIndex);
6057
6112
  } else {
6058
- // render using an external texture
6113
+ // render video
6059
6114
  let externalTexture = Q5.device.importExternalTexture({ source: img });
6060
6115
 
6061
6116
  // Create bind group for the external texture that will
@@ -6087,6 +6142,8 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
6087
6142
  usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
6088
6143
  });
6089
6144
 
6145
+ $._buffers.push(buffer);
6146
+
6090
6147
  let en = Q5.device.createCommandEncoder();
6091
6148
 
6092
6149
  en.copyTextureToBuffer({ texture }, { buffer, bytesPerRow, rowsPerImage: h }, { width: w, height: h });
@@ -6117,7 +6174,7 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
6117
6174
  let colorSpace = $.canvas.colorSpace;
6118
6175
  data = new Uint8ClampedArray(data.buffer);
6119
6176
  data = new ImageData(data, w, h, { colorSpace });
6120
- let cnv = new OffscreenCanvas(w, h);
6177
+ let cnv = new $._Canvas(w, h);
6121
6178
  let ctx = cnv.getContext('2d', { colorSpace });
6122
6179
  ctx.putImageData(data, 0, 0);
6123
6180
 
@@ -6147,6 +6204,8 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
6147
6204
 
6148
6205
  $.pass.setVertexBuffer(1, vertexBuffer);
6149
6206
 
6207
+ $._buffers.push(vertexBuffer);
6208
+
6150
6209
  if (vidFrames) {
6151
6210
  // Switch to video pipeline
6152
6211
  $.pass.setPipeline($._pipelines[3]);
@@ -6170,9 +6229,7 @@ Q5.DILATE = 6;
6170
6229
  Q5.ERODE = 7;
6171
6230
  Q5.BLUR = 8;
6172
6231
  Q5.renderers.webgpu.text = ($, q) => {
6173
- let textShader = Q5.device.createShaderModule({
6174
- label: 'MSDF text shader',
6175
- code: `
6232
+ let textShaderCode = `
6176
6233
  struct Uniforms {
6177
6234
  halfWidth: f32,
6178
6235
  halfHeight: f32
@@ -6184,7 +6241,9 @@ struct VertexParams {
6184
6241
  struct FragmentParams {
6185
6242
  @builtin(position) position : vec4f,
6186
6243
  @location(0) texCoord : vec2f,
6187
- @location(1) fillColor : vec4f
6244
+ @location(1) fillColor : vec4f,
6245
+ @location(2) strokeColor : vec4f,
6246
+ @location(3) strokeWeight : f32
6188
6247
  }
6189
6248
  struct Char {
6190
6249
  texOffset: vec2f,
@@ -6197,7 +6256,8 @@ struct Text {
6197
6256
  scale: f32,
6198
6257
  matrixIndex: f32,
6199
6258
  fillIndex: f32,
6200
- strokeIndex: f32
6259
+ strokeIndex: f32,
6260
+ strokeWeight: f32
6201
6261
  }
6202
6262
 
6203
6263
  @group(0) @binding(0) var<uniform> uniforms: Uniforms;
@@ -6213,52 +6273,73 @@ struct Text {
6213
6273
 
6214
6274
  const quad = array(vec2f(0, -1), vec2f(1, -1), vec2f(0, 0), vec2f(1, 0));
6215
6275
 
6276
+ fn sampleMsdf(texCoord: vec2f) -> f32 {
6277
+ let c = textureSample(fontTexture, fontSampler, texCoord);
6278
+ return max(min(c.r, c.g), min(max(c.r, c.g), c.b));
6279
+ }
6280
+
6281
+ fn transformVertex(pos: vec2f, matrixIndex: f32) -> vec4f {
6282
+ var vert = vec4f(pos, 0.0, 1.0);
6283
+ vert = transforms[i32(matrixIndex)] * vert;
6284
+ vert.x /= uniforms.halfWidth;
6285
+ vert.y /= uniforms.halfHeight;
6286
+ return vert;
6287
+ }
6288
+
6216
6289
  @vertex
6217
6290
  fn vertexMain(v : VertexParams) -> FragmentParams {
6218
6291
  let char = textChars[v.instance];
6219
-
6220
6292
  let text = textMetadata[i32(char.w)];
6221
-
6222
6293
  let fontChar = fontChars[i32(char.z)];
6223
6294
 
6224
6295
  let charPos = ((quad[v.vertex] * fontChar.size + char.xy + fontChar.offset) * text.scale) + text.pos;
6225
6296
 
6226
- var vert = vec4f(charPos, 0.0, 1.0);
6227
- vert = transforms[i32(text.matrixIndex)] * vert;
6228
- vert.x /= uniforms.halfWidth;
6229
- vert.y /= uniforms.halfHeight;
6297
+ var vert = transformVertex(charPos, text.matrixIndex);
6230
6298
 
6231
6299
  var f : FragmentParams;
6232
6300
  f.position = vert;
6233
6301
  f.texCoord = (quad[v.vertex] * vec2f(1, -1)) * fontChar.texExtent + fontChar.texOffset;
6234
6302
  f.fillColor = colors[i32(text.fillIndex)];
6303
+ f.strokeColor = colors[i32(text.strokeIndex)];
6304
+ f.strokeWeight = text.strokeWeight;
6235
6305
  return f;
6236
6306
  }
6237
6307
 
6238
- fn sampleMsdf(texCoord: vec2f) -> f32 {
6239
- let c = textureSample(fontTexture, fontSampler, texCoord);
6240
- return max(min(c.r, c.g), min(max(c.r, c.g), c.b));
6241
- }
6242
-
6243
6308
  @fragment
6244
6309
  fn fragmentMain(f : FragmentParams) -> @location(0) vec4f {
6245
- // pxRange (AKA distanceRange) comes from the msdfgen tool,
6246
- // uses the default which is 4.
6247
6310
  let pxRange = 4.0;
6248
6311
  let sz = vec2f(textureDimensions(fontTexture, 0));
6249
- let dx = sz.x*length(vec2f(dpdxFine(f.texCoord.x), dpdyFine(f.texCoord.x)));
6250
- let dy = sz.y*length(vec2f(dpdxFine(f.texCoord.y), dpdyFine(f.texCoord.y)));
6312
+ let dx = sz.x * length(vec2f(dpdxFine(f.texCoord.x), dpdyFine(f.texCoord.x)));
6313
+ let dy = sz.y * length(vec2f(dpdxFine(f.texCoord.y), dpdyFine(f.texCoord.y)));
6251
6314
  let toPixels = pxRange * inverseSqrt(dx * dx + dy * dy);
6252
6315
  let sigDist = sampleMsdf(f.texCoord) - 0.5;
6253
6316
  let pxDist = sigDist * toPixels;
6254
6317
  let edgeWidth = 0.5;
6255
- let alpha = smoothstep(-edgeWidth, edgeWidth, pxDist);
6256
- if (alpha < 0.001) {
6318
+
6319
+ if (f.strokeWeight == 0.0) {
6320
+ let fillAlpha = smoothstep(-edgeWidth, edgeWidth, pxDist);
6321
+ var color = vec4f(f.fillColor.rgb, f.fillColor.a * fillAlpha);
6322
+ if (color.a < 0.01) {
6323
+ discard;
6324
+ }
6325
+ return color;
6326
+ }
6327
+
6328
+ let halfStroke = f.strokeWeight / 2.0;
6329
+ let fillAlpha = smoothstep(-edgeWidth, edgeWidth, pxDist - halfStroke);
6330
+ let strokeAlpha = smoothstep(-edgeWidth, edgeWidth, pxDist + halfStroke);
6331
+ var color = mix(f.strokeColor, f.fillColor, fillAlpha);
6332
+ color = vec4f(color.rgb, color.a * strokeAlpha);
6333
+ if (color.a < 0.01) {
6257
6334
  discard;
6258
6335
  }
6259
- return vec4f(f.fillColor.rgb, f.fillColor.a * alpha);
6336
+ return color;
6260
6337
  }
6261
- `
6338
+ `;
6339
+
6340
+ let textShader = Q5.device.createShaderModule({
6341
+ label: 'textShader',
6342
+ code: textShaderCode
6262
6343
  });
6263
6344
 
6264
6345
  let textBindGroupLayout = Q5.device.createBindGroupLayout({
@@ -6657,6 +6738,8 @@ fn fragmentMain(f : FragmentParams) -> @location(0) vec4f {
6657
6738
  txt[3] = $._matrixIndex;
6658
6739
  txt[4] = $._fillSet ? $._fill : 0;
6659
6740
  txt[5] = $._stroke;
6741
+ txt[6] = $._strokeSet ? $._strokeWeight : 0;
6742
+ txt[7] = 0; // padding
6660
6743
 
6661
6744
  textStack.push(txt);
6662
6745
  $.drawStack.push(3, measurements.printedCharCount, $._font.index);
@@ -6733,7 +6816,7 @@ fn fragmentMain(f : FragmentParams) -> @location(0) vec4f {
6733
6816
  charBuffer.unmap();
6734
6817
 
6735
6818
  // calculate total buffer size for metadata
6736
- let totalMetadataSize = textStack.length * 6 * 4;
6819
+ let totalMetadataSize = textStack.length * 8 * 4;
6737
6820
 
6738
6821
  // create a single buffer for all metadata
6739
6822
  let textBuffer = Q5.device.createBuffer({
@@ -6747,6 +6830,8 @@ fn fragmentMain(f : FragmentParams) -> @location(0) vec4f {
6747
6830
  new Float32Array(textBuffer.getMappedRange()).set(textStack.flat());
6748
6831
  textBuffer.unmap();
6749
6832
 
6833
+ $._buffers.push(charBuffer, textBuffer);
6834
+
6750
6835
  // create a single bind group for the text buffer and metadata buffer
6751
6836
  $._textBindGroup = Q5.device.createBindGroup({
6752
6837
  label: 'msdf text bind group',