q5 4.0.0 → 4.1.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 4.0
3
+ * @version 4.1
4
4
  * @author quinton-ashley
5
5
  * @contributors evanalulu, Tezumie, ormaq, Dukemz, LingDong-
6
6
  * @license LGPL-3.0
@@ -47,6 +47,7 @@ function Q5(scope, parent, renderer) {
47
47
  });
48
48
 
49
49
  $.canvas = $.ctx = $.drawingContext = null;
50
+ $._flippedY = true;
50
51
  $.pixels = [];
51
52
  let looper = null,
52
53
  useRAF = true;
@@ -469,7 +470,7 @@ if (typeof window == 'object') {
469
470
  window.WEBGPU = 'webgpu';
470
471
  } else global.window = 0;
471
472
 
472
- Q5.version = Q5.VERSION = '4.0';
473
+ Q5.version = Q5.VERSION = '4.1';
473
474
 
474
475
  if (typeof document == 'object') {
475
476
  document.addEventListener('DOMContentLoaded', () => {
@@ -598,6 +599,7 @@ Q5.modules.canvas = ($, q) => {
598
599
 
599
600
  if ($._addEventMethods) $._addEventMethods(c);
600
601
 
602
+ if (!$._isImage) $.resetMatrix();
601
603
  $.canvas.ready = true;
602
604
 
603
605
  return rend;
@@ -3684,7 +3686,7 @@ Q5.modules.input = ($, q) => {
3684
3686
  let x = (touch.clientX - rect.left) / sx - modX,
3685
3687
  y = (touch.clientY - rect.top) / sy - modY;
3686
3688
 
3687
- if ($._webgpu && !$._flippedY) y *= -1;
3689
+ if (!$._flippedY) y *= -1;
3688
3690
 
3689
3691
  return {
3690
3692
  x,
@@ -5399,10 +5401,12 @@ struct Q5 {
5399
5401
  $._buffers = [];
5400
5402
  $._texturesToDestroy = [];
5401
5403
 
5402
- // local variables used for slightly better performance
5404
+ // local variables used for better performance
5403
5405
 
5404
5406
  // stores pipeline shifts and vertex counts/image indices
5405
- let drawStack = [];
5407
+ let drawStack = ($._drawStack = []);
5408
+ $._customDrawHandlers = {};
5409
+ $._customBindHandlers = {};
5406
5410
 
5407
5411
  // colors used for each draw call
5408
5412
  let colorStack = new Float32Array(1e6);
@@ -5435,7 +5439,7 @@ struct Q5 {
5435
5439
  ]
5436
5440
  });
5437
5441
 
5438
- $._bindGroupLayouts = [mainLayout];
5442
+ $._mainLayout = mainLayout;
5439
5443
 
5440
5444
  let uniformBuffer = Q5.device.createBuffer({
5441
5445
  size: 64,
@@ -5718,19 +5722,22 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
5718
5722
  matrixIdx = 0,
5719
5723
  matrixDirty = false; // tracks if the matrix has been modified
5720
5724
 
5725
+ $._getMatrixIdx = () => matrixIdx;
5726
+
5721
5727
  // 4x4 identity matrix
5722
5728
  // prettier-ignore
5723
5729
  matrices.push([
5724
5730
  1, 0, 0, 0,
5725
- 0, 1, 0, 0,
5731
+ 0, -1, 0, 0,
5726
5732
  0, 0, 1, 0,
5727
5733
  0, 0, 0, 1
5728
5734
  ]);
5729
5735
 
5730
5736
  transforms.set(matrices[0]);
5731
5737
 
5732
- let flippedY = false,
5733
- yDir = 1;
5738
+ // default is y-down for q5 WebGPU
5739
+ let flippedY = true,
5740
+ yDir = -1;
5734
5741
 
5735
5742
  $.flipY = () => {
5736
5743
  $._flippedY = flippedY = !flippedY;
@@ -5741,9 +5748,6 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
5741
5748
  transforms.set(matrices[0], 0);
5742
5749
  };
5743
5750
 
5744
- // current default is y-down for q5 WebGPU
5745
- $.flipY();
5746
-
5747
5751
  $.translate = (x, y) => {
5748
5752
  if (!x && !y) return;
5749
5753
  let m = matrix;
@@ -6354,6 +6358,8 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
6354
6358
  } else if (curPipelineIndex == 6) {
6355
6359
  pass.setIndexBuffer(ellipseIndexBuffer, 'uint16');
6356
6360
  pass.setBindGroup(1, ellipseBindGroup);
6361
+ } else if ($._customBindHandlers[curPipelineIndex]) {
6362
+ $._customBindHandlers[curPipelineIndex](pass);
6357
6363
  }
6358
6364
  }
6359
6365
 
@@ -6381,11 +6387,14 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
6381
6387
  pass.setBindGroup(1, $._textureBindGroups[v]);
6382
6388
  pass.draw(4, 1, imageVertOffset);
6383
6389
  imageVertOffset += 4;
6384
- } else {
6390
+ } else if (curPipelineIndex == 1 || curPipelineIndex >= 1000) {
6385
6391
  // draw a shape
6386
6392
  // v is the number of vertices
6387
6393
  pass.draw(v, 1, drawVertOffset);
6388
6394
  drawVertOffset += v;
6395
+ } else {
6396
+ let used = $._customDrawHandlers[curPipelineIndex](pass, v, drawStack, i);
6397
+ if (used) i += used;
6389
6398
  }
6390
6399
  }
6391
6400
  };
@@ -6522,7 +6531,7 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
6522
6531
 
6523
6532
  let shapesPipelineLayout = Q5.device.createPipelineLayout({
6524
6533
  label: 'shapesPipelineLayout',
6525
- bindGroupLayouts: $._bindGroupLayouts
6534
+ bindGroupLayouts: [mainLayout]
6526
6535
  });
6527
6536
 
6528
6537
  $._pipelineConfigs[1] = {
@@ -6951,7 +6960,7 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
6951
6960
 
6952
6961
  let rectPipelineLayout = Q5.device.createPipelineLayout({
6953
6962
  label: 'rectPipelineLayout',
6954
- bindGroupLayouts: [...$._bindGroupLayouts, rectBindGroupLayout]
6963
+ bindGroupLayouts: [mainLayout, rectBindGroupLayout]
6955
6964
  });
6956
6965
 
6957
6966
  $._pipelineConfigs[5] = {
@@ -7259,7 +7268,7 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
7259
7268
 
7260
7269
  let ellipsePipelineLayout = Q5.device.createPipelineLayout({
7261
7270
  label: 'ellipsePipelineLayout',
7262
- bindGroupLayouts: [...$._bindGroupLayouts, ellipseBindGroupLayout]
7271
+ bindGroupLayouts: [mainLayout, ellipseBindGroupLayout]
7263
7272
  });
7264
7273
 
7265
7274
  $._pipelineConfigs[6] = {
@@ -7529,12 +7538,12 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
7529
7538
 
7530
7539
  let imagePipelineLayout = Q5.device.createPipelineLayout({
7531
7540
  label: 'imagePipelineLayout',
7532
- bindGroupLayouts: [...$._bindGroupLayouts, textureLayout]
7541
+ bindGroupLayouts: [mainLayout, textureLayout]
7533
7542
  });
7534
7543
 
7535
7544
  let videoPipelineLayout = Q5.device.createPipelineLayout({
7536
7545
  label: 'videoPipelineLayout',
7537
- bindGroupLayouts: [...$._bindGroupLayouts, videoTextureLayout]
7546
+ bindGroupLayouts: [mainLayout, videoTextureLayout]
7538
7547
  });
7539
7548
 
7540
7549
  $._pipelineConfigs[2] = {
@@ -8027,7 +8036,7 @@ fn fragMain(f : FragParams) -> @location(0) vec4f {
8027
8036
  });
8028
8037
 
8029
8038
  let fontPipelineLayout = Q5.device.createPipelineLayout({
8030
- bindGroupLayouts: [...$._bindGroupLayouts, fontBindGroupLayout, textBindGroupLayout]
8039
+ bindGroupLayouts: [mainLayout, fontBindGroupLayout, textBindGroupLayout]
8031
8040
  });
8032
8041
 
8033
8042
  $._pipelineConfigs[4] = {
@@ -8536,9 +8545,117 @@ fn fragMain(f : FragParams) -> @location(0) vec4f {
8536
8545
  text: 4000
8537
8546
  };
8538
8547
 
8539
- $._createShader = (code, type = 'shapes') => {
8548
+ $._createPipeline = (opt) => {
8549
+ if (typeof opt == 'string') opt = { shader: opt };
8550
+
8551
+ let { label, shader = '', topology = 'triangle-list', cullMode = 'none', blend = 'source-over' } = opt;
8552
+
8553
+ let module;
8554
+ if (opt.module) module = opt.module;
8555
+ else {
8556
+ module = Q5.device.createShaderModule({
8557
+ label: label + 'Shader',
8558
+ code: $._baseShaderCode + shader
8559
+ });
8560
+ }
8561
+
8562
+ // Handle optional custom data buffer and its bind group layout
8563
+ let layout = opt.layout;
8564
+ let _dataBuffer = null;
8565
+ let _dataBindLayout = null;
8566
+ let _dataBindGroup = null;
8567
+ if (opt.data) {
8568
+ _dataBuffer = Q5.device.createBuffer({
8569
+ size: opt.data.byteLength,
8570
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
8571
+ });
8572
+ _dataBindLayout = Q5.device.createBindGroupLayout({
8573
+ entries: [
8574
+ {
8575
+ binding: 0,
8576
+ visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
8577
+ buffer: { type: 'read-only-storage' }
8578
+ }
8579
+ ]
8580
+ });
8581
+ _dataBindGroup = Q5.device.createBindGroup({
8582
+ layout: _dataBindLayout,
8583
+ entries: [{ binding: 0, resource: { buffer: _dataBuffer } }]
8584
+ });
8585
+ $._buffers.push(_dataBuffer);
8586
+ }
8587
+
8588
+ if (!layout) {
8589
+ if (_dataBindLayout) {
8590
+ layout = Q5.device.createPipelineLayout({
8591
+ bindGroupLayouts: [mainLayout, _dataBindLayout]
8592
+ });
8593
+ } else {
8594
+ layout = Q5.device.createPipelineLayout({
8595
+ bindGroupLayouts: [mainLayout]
8596
+ });
8597
+ }
8598
+ }
8599
+
8600
+ let pipelineConfig = {
8601
+ label: label + 'Pipeline',
8602
+ layout,
8603
+ vertex: {
8604
+ module,
8605
+ entryPoint: 'vertexMain'
8606
+ },
8607
+ fragment: {
8608
+ module,
8609
+ entryPoint: 'fragMain',
8610
+ targets: [
8611
+ {
8612
+ format: 'bgra8unorm',
8613
+ blend: $.blendConfigs[blend]
8614
+ }
8615
+ ]
8616
+ },
8617
+ primitive: {
8618
+ topology,
8619
+ cullMode
8620
+ },
8621
+ multisample: { count: 4 }
8622
+ };
8623
+
8624
+ let id = $._pipelines.length;
8625
+ $._pipelineConfigs[id] = pipelineConfig;
8626
+ $._pipelines[id] = Q5.device.createRenderPipeline(pipelineConfig);
8627
+
8628
+ // If we created a data buffer/bind group, register a bind handler
8629
+ if (_dataBindGroup) {
8630
+ $._customBindHandlers[id] = (pass) => {
8631
+ Q5.device.queue.writeBuffer(_dataBuffer, 0, opt.data);
8632
+ pass.setBindGroup(1, _dataBindGroup);
8633
+ };
8634
+ }
8635
+
8636
+ return id;
8637
+ };
8638
+
8639
+ $.createShader = (code, type = 'shapes', options = {}) => {
8540
8640
  code = code.trim();
8541
8641
 
8642
+ // create custom shader
8643
+ if (!pipelineTypes.includes(type)) {
8644
+ if (options instanceof Float32Array) options = { data: options };
8645
+ options.shader = code;
8646
+ options.label = type;
8647
+
8648
+ let id = $._createPipeline(options);
8649
+
8650
+ let shader = $._pipelineConfigs[id].vertex.module;
8651
+ shader.type = type;
8652
+ shader.pipelineIndex = id;
8653
+ $._customDrawHandlers[id] ??= (pass, count) => {
8654
+ pass.draw(count, 1, 0, 0);
8655
+ };
8656
+ return shader;
8657
+ }
8658
+
8542
8659
  // default shader code
8543
8660
  let def = $['_' + type + 'ShaderCode'];
8544
8661
 
@@ -8576,11 +8693,11 @@ fn fragMain(f : FragParams) -> @location(0) vec4f {
8576
8693
  return shader;
8577
8694
  };
8578
8695
 
8579
- $.createShader = $.createShapesShader = $._createShader;
8580
- $.createFrameShader = (code) => $._createShader(code, 'frame');
8581
- $.createImageShader = (code) => $._createShader(code, 'image');
8582
- $.createVideoShader = (code) => $._createShader(code, 'video');
8583
- $.createTextShader = (code) => $._createShader(code, 'text');
8696
+ $.createShapesShader = $.createShader;
8697
+ $.createFrameShader = (code) => $.createShader(code, 'frame');
8698
+ $.createImageShader = (code) => $.createShader(code, 'image');
8699
+ $.createVideoShader = (code) => $.createShader(code, 'video');
8700
+ $.createTextShader = (code) => $.createShader(code, 'text');
8584
8701
 
8585
8702
  $.shader = (shader) => {
8586
8703
  let type = shader.type;
@@ -8650,7 +8767,7 @@ Q5.MAX_TEXTS = 10000;
8650
8767
  Q5.initWebGPU = async () => {
8651
8768
  if (!navigator.gpu) {
8652
8769
  console.warn('q5 WebGPU not supported on this browser! Use Google Chrome or Edge.');
8653
- return false;
8770
+ return;
8654
8771
  }
8655
8772
 
8656
8773
  // fn can only be called once
@@ -8665,7 +8782,7 @@ Q5.initWebGPU = async () => {
8665
8782
 
8666
8783
  if (!adapter) {
8667
8784
  console.warn('q5 WebGPU could not start! No appropriate GPUAdapter found, Vulkan may need to be enabled.');
8668
- return false;
8785
+ return;
8669
8786
  }
8670
8787
 
8671
8788
  let device = await adapter.requestDevice();
@@ -8674,7 +8791,7 @@ Q5.initWebGPU = async () => {
8674
8791
  device.limits.maxStorageBuffersInVertexStage ?? device.limits.maxStorageBuffersPerShaderStage;
8675
8792
  if (vertexStorageLimit < 3) {
8676
8793
  console.warn('q5 WebGPU requires vertex storage buffers, which are not supported by this device.');
8677
- return false;
8794
+ return;
8678
8795
  }
8679
8796
 
8680
8797
  // Update to fit device limits