q5 4.0.1 → 4.2.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.2
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;
@@ -148,7 +149,12 @@ function Q5(scope, parent, renderer) {
148
149
 
149
150
  q.pmouseX = $.mouseX;
150
151
  q.pmouseY = $.mouseY;
151
- q.moveX = q.moveY = 0;
152
+ q.movedX = q.movedY = 0;
153
+ if ($.pointers) {
154
+ for (let i = $.pointers.length - 1; i >= 0; i--) {
155
+ if ($.pointers[i]._ended) $.pointers.splice(i, 1);
156
+ }
157
+ }
152
158
  $._lastFrameTime = ts;
153
159
  let post = performance.now();
154
160
  $._fps = Math.round(1000 / (post - pre));
@@ -469,7 +475,7 @@ if (typeof window == 'object') {
469
475
  window.WEBGPU = 'webgpu';
470
476
  } else global.window = 0;
471
477
 
472
- Q5.version = Q5.VERSION = '4.0';
478
+ Q5.version = Q5.VERSION = '4.2';
473
479
 
474
480
  if (typeof document == 'object') {
475
481
  document.addEventListener('DOMContentLoaded', () => {
@@ -598,6 +604,7 @@ Q5.modules.canvas = ($, q) => {
598
604
 
599
605
  if ($._addEventMethods) $._addEventMethods(c);
600
606
 
607
+ if (!$._isImage) $.resetMatrix();
601
608
  $.canvas.ready = true;
602
609
 
603
610
  return rend;
@@ -3500,12 +3507,9 @@ Q5.modules.fes = ($) => {
3500
3507
  Q5.modules.input = ($, q) => {
3501
3508
  if ($._isGraphics) return;
3502
3509
 
3503
- $.mouseX = 0;
3504
- $.mouseY = 0;
3505
- $.pmouseX = 0;
3506
- $.pmouseY = 0;
3510
+ $.mouseX = $.mouseY = $.pmouseX = $.pmouseY = $.movedX = $.movedY = 0;
3507
3511
  $.touches = [];
3508
- $.pointers = {};
3512
+ $.pointers = [];
3509
3513
  $.mouseButton = '';
3510
3514
  $.keyIsPressed = false;
3511
3515
  $.mouseIsPressed = false;
@@ -3541,10 +3545,18 @@ Q5.modules.input = ($, q) => {
3541
3545
  };
3542
3546
 
3543
3547
  $._updatePointer = (e) => {
3544
- let pid = e.pointerId;
3545
- $.pointers[pid] ??= { event: e };
3546
- let pointer = $.pointers[pid];
3547
- pointer.event = e;
3548
+ let id = e.pointerId || $.pointers[0]?.id;
3549
+ if (id == undefined) {
3550
+ if (e instanceof MouseEvent) id = 0;
3551
+ else return;
3552
+ }
3553
+
3554
+ let p = $.pointers.find((p) => p.id === id);
3555
+ if (!p) {
3556
+ p = { id };
3557
+ $.pointers.push(p);
3558
+ }
3559
+ p.event = e;
3548
3560
 
3549
3561
  let x, y;
3550
3562
  if (c) {
@@ -3564,20 +3576,29 @@ Q5.modules.input = ($, q) => {
3564
3576
  y = e.clientY;
3565
3577
  }
3566
3578
 
3567
- pointer.x = x;
3568
- pointer.y = y;
3579
+ p.x = x;
3580
+ p.y = y;
3581
+
3582
+ return p;
3583
+ };
3584
+
3585
+ $._updateMouse = (e) => {
3586
+ let p = $.pointers[0];
3587
+ if (e.pointerId != undefined && e.pointerId != p.id) return;
3569
3588
 
3570
- if (e.isPrimary || !e.pointerId) {
3571
- if (document.pointerLockElement) {
3589
+ if (document.pointerLockElement) {
3590
+ if (e.movementX != undefined) {
3572
3591
  q.mouseX += e.movementX;
3573
3592
  q.mouseY += e.movementY;
3574
- } else {
3575
- q.mouseX = x;
3576
- q.mouseY = y;
3577
3593
  }
3594
+ } else {
3595
+ q.mouseX = p.x;
3596
+ q.mouseY = p.y;
3597
+ }
3578
3598
 
3579
- q.moveX = e.movementX;
3580
- q.moveY = e.movementY;
3599
+ if (e.movementX != undefined) {
3600
+ q.movedX = e.movementX;
3601
+ q.movedY = e.movementY;
3581
3602
  }
3582
3603
  };
3583
3604
 
@@ -3587,6 +3608,7 @@ Q5.modules.input = ($, q) => {
3587
3608
  pressAmt++;
3588
3609
  $._startAudio();
3589
3610
  $._updatePointer(e);
3611
+ $._updateMouse(e);
3590
3612
  q.mouseIsPressed = true;
3591
3613
  q.mouseButton = mouseBtns[e.button];
3592
3614
  $.mousePressed(e);
@@ -3595,6 +3617,7 @@ Q5.modules.input = ($, q) => {
3595
3617
  $._onpointermove = (e) => {
3596
3618
  if (c && !c.visible) return;
3597
3619
  $._updatePointer(e);
3620
+ $._updateMouse(e);
3598
3621
  if ($.mouseIsPressed) $.mouseDragged(e);
3599
3622
  else $.mouseMoved(e);
3600
3623
  };
@@ -3604,19 +3627,21 @@ Q5.modules.input = ($, q) => {
3604
3627
  if (pressAmt > 0) pressAmt--;
3605
3628
  else return;
3606
3629
  $._updatePointer(e);
3607
- delete $.pointers[e.pointerId];
3630
+ $._updateMouse(e);
3631
+ if (e.pointerType === 'touch' || e.pointerType === 'pen') {
3632
+ let p = $.pointers.find((p) => p.id === e.pointerId);
3633
+ if (p) p._ended = true;
3634
+ }
3608
3635
  $.mouseReleased(e);
3609
3636
  };
3610
3637
 
3611
3638
  $._onclick = (e) => {
3612
- $._updatePointer(e);
3613
3639
  q.mouseIsPressed = true;
3614
3640
  $.mouseClicked(e);
3615
3641
  q.mouseIsPressed = false;
3616
3642
  };
3617
3643
 
3618
3644
  $._ondblclick = (e) => {
3619
- $._updatePointer(e);
3620
3645
  q.mouseIsPressed = true;
3621
3646
  $.doubleClicked(e);
3622
3647
  q.mouseIsPressed = false;
@@ -3624,6 +3649,7 @@ Q5.modules.input = ($, q) => {
3624
3649
 
3625
3650
  $._onwheel = (e) => {
3626
3651
  $._updatePointer(e);
3652
+ $._updateMouse(e);
3627
3653
  e.delta = e.deltaY;
3628
3654
  let ret = $.mouseWheel(e);
3629
3655
  if (($._isGlobal && !ret) || ret == false) {
@@ -3684,7 +3710,7 @@ Q5.modules.input = ($, q) => {
3684
3710
  let x = (touch.clientX - rect.left) / sx - modX,
3685
3711
  y = (touch.clientY - rect.top) / sy - modY;
3686
3712
 
3687
- if ($._webgpu && !$._flippedY) y *= -1;
3713
+ if (!$._flippedY) y *= -1;
3688
3714
 
3689
3715
  return {
3690
3716
  x,
@@ -3752,7 +3778,14 @@ Q5.modules.input = ($, q) => {
3752
3778
 
3753
3779
  if (c) c.addEventListener('wheel', (e) => $._onwheel(e));
3754
3780
 
3755
- if (!$._isGlobal && c) l = c.addEventListener.bind(c);
3781
+ if (!$._isGlobal && c) {
3782
+ // If not global, only trigger pointer events when pointer is locked or over canvas
3783
+ l(pointer + 'down', (e) => !document.pointerLockElement || $._onpointerdown(e));
3784
+ l('click', (e) => !document.pointerLockElement || $._onclick(e));
3785
+ l('dblclick', (e) => !document.pointerLockElement || $._ondblclick(e));
3786
+
3787
+ l = c.addEventListener.bind(c);
3788
+ }
3756
3789
 
3757
3790
  l(pointer + 'down', (e) => $._onpointerdown(e));
3758
3791
  l('click', (e) => $._onclick(e));
@@ -5399,10 +5432,12 @@ struct Q5 {
5399
5432
  $._buffers = [];
5400
5433
  $._texturesToDestroy = [];
5401
5434
 
5402
- // local variables used for slightly better performance
5435
+ // local variables used for better performance
5403
5436
 
5404
5437
  // stores pipeline shifts and vertex counts/image indices
5405
- let drawStack = [];
5438
+ let drawStack = ($._drawStack = []);
5439
+ $._customDrawHandlers = {};
5440
+ $._customBindHandlers = {};
5406
5441
 
5407
5442
  // colors used for each draw call
5408
5443
  let colorStack = new Float32Array(1e6);
@@ -5435,7 +5470,7 @@ struct Q5 {
5435
5470
  ]
5436
5471
  });
5437
5472
 
5438
- $._bindGroupLayouts = [mainLayout];
5473
+ $._mainLayout = mainLayout;
5439
5474
 
5440
5475
  let uniformBuffer = Q5.device.createBuffer({
5441
5476
  size: 64,
@@ -5718,19 +5753,22 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
5718
5753
  matrixIdx = 0,
5719
5754
  matrixDirty = false; // tracks if the matrix has been modified
5720
5755
 
5756
+ $._getMatrixIdx = () => matrixIdx;
5757
+
5721
5758
  // 4x4 identity matrix
5722
5759
  // prettier-ignore
5723
5760
  matrices.push([
5724
5761
  1, 0, 0, 0,
5725
- 0, 1, 0, 0,
5762
+ 0, -1, 0, 0,
5726
5763
  0, 0, 1, 0,
5727
5764
  0, 0, 0, 1
5728
5765
  ]);
5729
5766
 
5730
5767
  transforms.set(matrices[0]);
5731
5768
 
5732
- let flippedY = false,
5733
- yDir = 1;
5769
+ // default is y-down for q5 WebGPU
5770
+ let flippedY = true,
5771
+ yDir = -1;
5734
5772
 
5735
5773
  $.flipY = () => {
5736
5774
  $._flippedY = flippedY = !flippedY;
@@ -5741,9 +5779,6 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
5741
5779
  transforms.set(matrices[0], 0);
5742
5780
  };
5743
5781
 
5744
- // current default is y-down for q5 WebGPU
5745
- $.flipY();
5746
-
5747
5782
  $.translate = (x, y) => {
5748
5783
  if (!x && !y) return;
5749
5784
  let m = matrix;
@@ -6354,6 +6389,8 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
6354
6389
  } else if (curPipelineIndex == 6) {
6355
6390
  pass.setIndexBuffer(ellipseIndexBuffer, 'uint16');
6356
6391
  pass.setBindGroup(1, ellipseBindGroup);
6392
+ } else if ($._customBindHandlers[curPipelineIndex]) {
6393
+ $._customBindHandlers[curPipelineIndex](pass);
6357
6394
  }
6358
6395
  }
6359
6396
 
@@ -6381,11 +6418,14 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
6381
6418
  pass.setBindGroup(1, $._textureBindGroups[v]);
6382
6419
  pass.draw(4, 1, imageVertOffset);
6383
6420
  imageVertOffset += 4;
6384
- } else {
6421
+ } else if (curPipelineIndex == 1 || curPipelineIndex >= 1000) {
6385
6422
  // draw a shape
6386
6423
  // v is the number of vertices
6387
6424
  pass.draw(v, 1, drawVertOffset);
6388
6425
  drawVertOffset += v;
6426
+ } else {
6427
+ let used = $._customDrawHandlers[curPipelineIndex](pass, v, drawStack, i);
6428
+ if (used) i += used;
6389
6429
  }
6390
6430
  }
6391
6431
  };
@@ -6522,7 +6562,7 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
6522
6562
 
6523
6563
  let shapesPipelineLayout = Q5.device.createPipelineLayout({
6524
6564
  label: 'shapesPipelineLayout',
6525
- bindGroupLayouts: $._bindGroupLayouts
6565
+ bindGroupLayouts: [mainLayout]
6526
6566
  });
6527
6567
 
6528
6568
  $._pipelineConfigs[1] = {
@@ -6951,7 +6991,7 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
6951
6991
 
6952
6992
  let rectPipelineLayout = Q5.device.createPipelineLayout({
6953
6993
  label: 'rectPipelineLayout',
6954
- bindGroupLayouts: [...$._bindGroupLayouts, rectBindGroupLayout]
6994
+ bindGroupLayouts: [mainLayout, rectBindGroupLayout]
6955
6995
  });
6956
6996
 
6957
6997
  $._pipelineConfigs[5] = {
@@ -7259,7 +7299,7 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
7259
7299
 
7260
7300
  let ellipsePipelineLayout = Q5.device.createPipelineLayout({
7261
7301
  label: 'ellipsePipelineLayout',
7262
- bindGroupLayouts: [...$._bindGroupLayouts, ellipseBindGroupLayout]
7302
+ bindGroupLayouts: [mainLayout, ellipseBindGroupLayout]
7263
7303
  });
7264
7304
 
7265
7305
  $._pipelineConfigs[6] = {
@@ -7529,12 +7569,12 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
7529
7569
 
7530
7570
  let imagePipelineLayout = Q5.device.createPipelineLayout({
7531
7571
  label: 'imagePipelineLayout',
7532
- bindGroupLayouts: [...$._bindGroupLayouts, textureLayout]
7572
+ bindGroupLayouts: [mainLayout, textureLayout]
7533
7573
  });
7534
7574
 
7535
7575
  let videoPipelineLayout = Q5.device.createPipelineLayout({
7536
7576
  label: 'videoPipelineLayout',
7537
- bindGroupLayouts: [...$._bindGroupLayouts, videoTextureLayout]
7577
+ bindGroupLayouts: [mainLayout, videoTextureLayout]
7538
7578
  });
7539
7579
 
7540
7580
  $._pipelineConfigs[2] = {
@@ -8027,7 +8067,7 @@ fn fragMain(f : FragParams) -> @location(0) vec4f {
8027
8067
  });
8028
8068
 
8029
8069
  let fontPipelineLayout = Q5.device.createPipelineLayout({
8030
- bindGroupLayouts: [...$._bindGroupLayouts, fontBindGroupLayout, textBindGroupLayout]
8070
+ bindGroupLayouts: [mainLayout, fontBindGroupLayout, textBindGroupLayout]
8031
8071
  });
8032
8072
 
8033
8073
  $._pipelineConfigs[4] = {
@@ -8536,9 +8576,117 @@ fn fragMain(f : FragParams) -> @location(0) vec4f {
8536
8576
  text: 4000
8537
8577
  };
8538
8578
 
8539
- $._createShader = (code, type = 'shapes') => {
8579
+ $._createPipeline = (opt) => {
8580
+ if (typeof opt == 'string') opt = { shader: opt };
8581
+
8582
+ let { label, shader = '', topology = 'triangle-list', cullMode = 'none', blend = 'source-over' } = opt;
8583
+
8584
+ let module;
8585
+ if (opt.module) module = opt.module;
8586
+ else {
8587
+ module = Q5.device.createShaderModule({
8588
+ label: label + 'Shader',
8589
+ code: $._baseShaderCode + shader
8590
+ });
8591
+ }
8592
+
8593
+ // Handle optional custom data buffer and its bind group layout
8594
+ let layout = opt.layout;
8595
+ let _dataBuffer = null;
8596
+ let _dataBindLayout = null;
8597
+ let _dataBindGroup = null;
8598
+ if (opt.data) {
8599
+ _dataBuffer = Q5.device.createBuffer({
8600
+ size: opt.data.byteLength,
8601
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
8602
+ });
8603
+ _dataBindLayout = Q5.device.createBindGroupLayout({
8604
+ entries: [
8605
+ {
8606
+ binding: 0,
8607
+ visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
8608
+ buffer: { type: 'read-only-storage' }
8609
+ }
8610
+ ]
8611
+ });
8612
+ _dataBindGroup = Q5.device.createBindGroup({
8613
+ layout: _dataBindLayout,
8614
+ entries: [{ binding: 0, resource: { buffer: _dataBuffer } }]
8615
+ });
8616
+ $._buffers.push(_dataBuffer);
8617
+ }
8618
+
8619
+ if (!layout) {
8620
+ if (_dataBindLayout) {
8621
+ layout = Q5.device.createPipelineLayout({
8622
+ bindGroupLayouts: [mainLayout, _dataBindLayout]
8623
+ });
8624
+ } else {
8625
+ layout = Q5.device.createPipelineLayout({
8626
+ bindGroupLayouts: [mainLayout]
8627
+ });
8628
+ }
8629
+ }
8630
+
8631
+ let pipelineConfig = {
8632
+ label: label + 'Pipeline',
8633
+ layout,
8634
+ vertex: {
8635
+ module,
8636
+ entryPoint: 'vertexMain'
8637
+ },
8638
+ fragment: {
8639
+ module,
8640
+ entryPoint: 'fragMain',
8641
+ targets: [
8642
+ {
8643
+ format: 'bgra8unorm',
8644
+ blend: $.blendConfigs[blend]
8645
+ }
8646
+ ]
8647
+ },
8648
+ primitive: {
8649
+ topology,
8650
+ cullMode
8651
+ },
8652
+ multisample: { count: 4 }
8653
+ };
8654
+
8655
+ let id = $._pipelines.length;
8656
+ $._pipelineConfigs[id] = pipelineConfig;
8657
+ $._pipelines[id] = Q5.device.createRenderPipeline(pipelineConfig);
8658
+
8659
+ // If we created a data buffer/bind group, register a bind handler
8660
+ if (_dataBindGroup) {
8661
+ $._customBindHandlers[id] = (pass) => {
8662
+ Q5.device.queue.writeBuffer(_dataBuffer, 0, opt.data);
8663
+ pass.setBindGroup(1, _dataBindGroup);
8664
+ };
8665
+ }
8666
+
8667
+ return id;
8668
+ };
8669
+
8670
+ $.createShader = (code, type = 'shapes', options = {}) => {
8540
8671
  code = code.trim();
8541
8672
 
8673
+ // create custom shader
8674
+ if (!pipelineTypes.includes(type)) {
8675
+ if (options instanceof Float32Array) options = { data: options };
8676
+ options.shader = code;
8677
+ options.label = type;
8678
+
8679
+ let id = $._createPipeline(options);
8680
+
8681
+ let shader = $._pipelineConfigs[id].vertex.module;
8682
+ shader.type = type;
8683
+ shader.pipelineIndex = id;
8684
+ $._customDrawHandlers[id] ??= (pass, count) => {
8685
+ pass.draw(count, 1, 0, 0);
8686
+ };
8687
+ return shader;
8688
+ }
8689
+
8542
8690
  // default shader code
8543
8691
  let def = $['_' + type + 'ShaderCode'];
8544
8692
 
@@ -8576,11 +8724,11 @@ fn fragMain(f : FragParams) -> @location(0) vec4f {
8576
8724
  return shader;
8577
8725
  };
8578
8726
 
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');
8727
+ $.createShapesShader = $.createShader;
8728
+ $.createFrameShader = (code) => $.createShader(code, 'frame');
8729
+ $.createImageShader = (code) => $.createShader(code, 'image');
8730
+ $.createVideoShader = (code) => $.createShader(code, 'video');
8731
+ $.createTextShader = (code) => $.createShader(code, 'text');
8584
8732
 
8585
8733
  $.shader = (shader) => {
8586
8734
  let type = shader.type;
@@ -8650,7 +8798,7 @@ Q5.MAX_TEXTS = 10000;
8650
8798
  Q5.initWebGPU = async () => {
8651
8799
  if (!navigator.gpu) {
8652
8800
  console.warn('q5 WebGPU not supported on this browser! Use Google Chrome or Edge.');
8653
- return false;
8801
+ return;
8654
8802
  }
8655
8803
 
8656
8804
  // fn can only be called once
@@ -8665,7 +8813,7 @@ Q5.initWebGPU = async () => {
8665
8813
 
8666
8814
  if (!adapter) {
8667
8815
  console.warn('q5 WebGPU could not start! No appropriate GPUAdapter found, Vulkan may need to be enabled.');
8668
- return false;
8816
+ return;
8669
8817
  }
8670
8818
 
8671
8819
  let device = await adapter.requestDevice();
@@ -8674,7 +8822,7 @@ Q5.initWebGPU = async () => {
8674
8822
  device.limits.maxStorageBuffersInVertexStage ?? device.limits.maxStorageBuffersPerShaderStage;
8675
8823
  if (vertexStorageLimit < 3) {
8676
8824
  console.warn('q5 WebGPU requires vertex storage buffers, which are not supported by this device.');
8677
- return false;
8825
+ return;
8678
8826
  }
8679
8827
 
8680
8828
  // Update to fit device limits