q5 2.2.2 → 2.2.4
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/README.md +5 -2
- package/package.json +3 -4
- package/q5.js +274 -41
- package/q5.min.js +1 -1
- package/src/q5-2d-canvas.js +6 -1
- package/src/q5-2d-drawing.js +8 -6
- package/src/q5-ai.js +4 -5
- package/src/q5-color.js +11 -16
- package/src/q5-core.js +1 -1
- package/src/q5-vector.js +57 -10
- package/src/q5-webgpu-canvas.js +1 -1
- package/src/q5-webgpu-drawing.js +3 -0
- package/src/q5-webgpu-image.js +192 -1
- package/src/readme.md +3 -3
- package/q5-q2d.js +0 -3078
- package/q5-q2d.min.js +0 -8
package/README.md
CHANGED
|
@@ -339,13 +339,13 @@ Unminified:
|
|
|
339
339
|
|
|
340
340
|
- p5.js **5112kb** ⚠️
|
|
341
341
|
- p5.sound.js 488kb
|
|
342
|
-
- q5.js
|
|
342
|
+
- q5.js 90kb
|
|
343
343
|
|
|
344
344
|
Minified:
|
|
345
345
|
|
|
346
346
|
- p5.min.js 1034kb ⚠️
|
|
347
347
|
- p5.sound.min.js 200kb
|
|
348
|
-
- q5.min.js **
|
|
348
|
+
- q5.min.js **58kb** 🎉
|
|
349
349
|
|
|
350
350
|
## Benchmarks
|
|
351
351
|
|
|
@@ -410,6 +410,9 @@ https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline
|
|
|
410
410
|
ziggurat:
|
|
411
411
|
http://ziggurat.glitch.me/
|
|
412
412
|
|
|
413
|
+
Vector.slerp:
|
|
414
|
+
https://github.com/processing/p5.js/blob/v1.10.0/src/math/p5.Vector.js#L2803
|
|
415
|
+
|
|
413
416
|
random:
|
|
414
417
|
https://github.com/processing/p5.js/blob/1.1.9/src/math/noise.js
|
|
415
418
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "q5",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.4",
|
|
4
4
|
"description": "A sequel to p5.js that's smaller and faster",
|
|
5
5
|
"author": "quinton-ashley",
|
|
6
6
|
"contributors": [
|
|
@@ -12,9 +12,8 @@
|
|
|
12
12
|
"main": "q5-server.js",
|
|
13
13
|
"scripts": {
|
|
14
14
|
"bundle": "cat src/q5-core.js src/q5-canvas.js src/q5-2d-canvas.js src/q5-2d-drawing.js src/q5-2d-image.js src/q5-2d-text.js src/q5-ai.js src/q5-color.js src/q5-display.js src/q5-input.js src/q5-math.js src/q5-sound.js src/q5-util.js src/q5-vector.js src/q5-webgpu-canvas.js src/q5-webgpu-drawing.js src/q5-webgpu-image.js src/q5-webgpu-text.js > q5.js",
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"dist": "bun bundle && bun bundle-q2d && bun min",
|
|
15
|
+
"min": "terser q5.js --compress ecma=2024 --mangle > q5.min.js",
|
|
16
|
+
"dist": "bun bundle && bun min",
|
|
18
17
|
"dist-p5play": "bun dist && cp q5.js ../../web/p5play-web/v3/q5.js && cp q5.min.js ../../web/p5play-web/v3/q5.min.js",
|
|
19
18
|
"v": "npm version patch --force",
|
|
20
19
|
"V": "npm version minor --force",
|
package/q5.js
CHANGED
|
@@ -219,7 +219,7 @@ function Q5(scope, parent, renderer) {
|
|
|
219
219
|
return t[k]();
|
|
220
220
|
} catch (e) {
|
|
221
221
|
if ($._aiErrorAssistance) $._aiErrorAssistance(e);
|
|
222
|
-
|
|
222
|
+
throw e;
|
|
223
223
|
}
|
|
224
224
|
};
|
|
225
225
|
}
|
|
@@ -551,7 +551,12 @@ Q5.renderers.q2d.canvas = ($, q) => {
|
|
|
551
551
|
$.ctx.lineWidth = n || 0.0001;
|
|
552
552
|
};
|
|
553
553
|
$.noStroke = () => ($._doStroke = false);
|
|
554
|
-
$.clear = () =>
|
|
554
|
+
$.clear = () => {
|
|
555
|
+
$.ctx.save();
|
|
556
|
+
$.ctx.resetTransform();
|
|
557
|
+
$.ctx.clearRect(0, 0, $.canvas.width, $.canvas.height);
|
|
558
|
+
$.ctx.restore();
|
|
559
|
+
};
|
|
555
560
|
|
|
556
561
|
// DRAWING MATRIX
|
|
557
562
|
|
|
@@ -717,15 +722,17 @@ Q5.renderers.q2d.drawing = ($) => {
|
|
|
717
722
|
// DRAWING
|
|
718
723
|
|
|
719
724
|
$.background = function (c) {
|
|
720
|
-
if (c.canvas) return $.image(c, 0, 0, $.width, $.height);
|
|
721
725
|
$.ctx.save();
|
|
722
726
|
$.ctx.resetTransform();
|
|
723
|
-
if (
|
|
724
|
-
|
|
725
|
-
|
|
727
|
+
if (c.canvas) $.image(c, 0, 0, $.width, $.height);
|
|
728
|
+
else {
|
|
729
|
+
if (Q5.Color) {
|
|
730
|
+
if (!c._q5Color && typeof c != 'string') c = $.color(...arguments);
|
|
731
|
+
else if ($._namedColors[c]) c = $.color(...$._namedColors[c]);
|
|
732
|
+
}
|
|
733
|
+
$.ctx.fillStyle = c.toString();
|
|
734
|
+
$.ctx.fillRect(0, 0, $.canvas.width, $.canvas.height);
|
|
726
735
|
}
|
|
727
|
-
$.ctx.fillStyle = c.toString();
|
|
728
|
-
$.ctx.fillRect(0, 0, $.canvas.width, $.canvas.height);
|
|
729
736
|
$.ctx.restore();
|
|
730
737
|
};
|
|
731
738
|
|
|
@@ -1596,8 +1603,7 @@ Q5.modules.ai = ($) => {
|
|
|
1596
1603
|
|
|
1597
1604
|
$._aiErrorAssistance = async (e) => {
|
|
1598
1605
|
let askAI = e.message?.includes('Ask AI ✨');
|
|
1599
|
-
if (!askAI)
|
|
1600
|
-
if (Q5.disableFriendlyErrors) return;
|
|
1606
|
+
if (Q5.disableFriendlyErrors && !askAI) return;
|
|
1601
1607
|
if (askAI || !Q5.errorTolerant) $.noLoop();
|
|
1602
1608
|
let stackLines = e.stack?.split('\n');
|
|
1603
1609
|
if (!e.stack || stackLines.length <= 1) return;
|
|
@@ -1608,7 +1614,7 @@ Q5.modules.ai = ($) => {
|
|
|
1608
1614
|
idx = 0;
|
|
1609
1615
|
sep = '@';
|
|
1610
1616
|
}
|
|
1611
|
-
while (stackLines[idx].indexOf('q5
|
|
1617
|
+
while (stackLines[idx].indexOf('q5') >= 0) idx++;
|
|
1612
1618
|
|
|
1613
1619
|
let parts = stackLines[idx].split(sep).at(-1);
|
|
1614
1620
|
parts = parts.split(':');
|
|
@@ -1646,11 +1652,11 @@ Q5.modules.ai = ($) => {
|
|
|
1646
1652
|
'%0A%0AExcerpt+for+context%3A%0A%0A' +
|
|
1647
1653
|
encodeURIComponent(context);
|
|
1648
1654
|
|
|
1649
|
-
|
|
1655
|
+
console.warn('Error in ' + fileBase + ' on line ' + lineNum + ':\n\n' + errLine);
|
|
1650
1656
|
|
|
1651
1657
|
console.warn('Ask AI ✨ ' + url);
|
|
1652
1658
|
|
|
1653
|
-
if (askAI) window.open(url, '_blank');
|
|
1659
|
+
if (askAI) return window.open(url, '_blank');
|
|
1654
1660
|
} catch (err) {}
|
|
1655
1661
|
};
|
|
1656
1662
|
};
|
|
@@ -1728,27 +1734,22 @@ Q5.modules.color = ($, q) => {
|
|
|
1728
1734
|
c0 = parseInt(c0.slice(1, 3), 16);
|
|
1729
1735
|
}
|
|
1730
1736
|
} else if ($._namedColors[c0]) {
|
|
1731
|
-
c0 = $._namedColors[c0];
|
|
1737
|
+
[c0, c1, c2, c3] = $._namedColors[c0];
|
|
1732
1738
|
} else {
|
|
1733
1739
|
console.error(
|
|
1734
1740
|
"q5 can't parse color: " + c0 + '\nOnly numeric input, hex, and common named colors are supported.'
|
|
1735
1741
|
);
|
|
1736
1742
|
return new C(0, 0, 0);
|
|
1737
1743
|
}
|
|
1738
|
-
}
|
|
1739
|
-
if (Array.isArray(c0)) {
|
|
1740
|
-
c1 = c0[1];
|
|
1741
|
-
c2 = c0[2];
|
|
1742
|
-
c3 = c0[3];
|
|
1743
|
-
c0 = c0[0];
|
|
1744
|
-
}
|
|
1745
|
-
}
|
|
1746
1744
|
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1745
|
+
if ($._colorFormat == 1) {
|
|
1746
|
+
c0 /= 255;
|
|
1747
|
+
if (c1) c1 /= 255;
|
|
1748
|
+
if (c2) c2 /= 255;
|
|
1749
|
+
if (c3) c3 /= 255;
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
if (Array.isArray(c0)) [c0, c1, c2, c3] = c0;
|
|
1752
1753
|
}
|
|
1753
1754
|
|
|
1754
1755
|
if (c2 == undefined) return new C(c0, c0, c0, c1);
|
|
@@ -1760,11 +1761,11 @@ Q5.modules.color = ($, q) => {
|
|
|
1760
1761
|
$.blue = (c) => c.b;
|
|
1761
1762
|
$.alpha = (c) => c.a;
|
|
1762
1763
|
$.lightness = (c) => {
|
|
1763
|
-
if (
|
|
1764
|
+
if (c.l) return c.l;
|
|
1764
1765
|
return ((0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b) * 100) / 255;
|
|
1765
1766
|
};
|
|
1766
1767
|
$.hue = (c) => {
|
|
1767
|
-
if (
|
|
1768
|
+
if (c.h) return c.h;
|
|
1768
1769
|
let r = c.r;
|
|
1769
1770
|
let g = c.g;
|
|
1770
1771
|
let b = c.b;
|
|
@@ -2680,18 +2681,19 @@ Q5.modules.vector = ($) => {
|
|
|
2680
2681
|
};
|
|
2681
2682
|
|
|
2682
2683
|
Q5.Vector = class {
|
|
2683
|
-
constructor(
|
|
2684
|
-
this.x =
|
|
2685
|
-
this.y =
|
|
2686
|
-
this.z =
|
|
2687
|
-
this._$ =
|
|
2684
|
+
constructor(x, y, z, $) {
|
|
2685
|
+
this.x = x || 0;
|
|
2686
|
+
this.y = y || 0;
|
|
2687
|
+
this.z = z || 0;
|
|
2688
|
+
this._$ = $ || window;
|
|
2688
2689
|
this._cn = null;
|
|
2689
2690
|
this._cnsq = null;
|
|
2690
2691
|
}
|
|
2691
|
-
set(
|
|
2692
|
-
this.x =
|
|
2693
|
-
this.y =
|
|
2694
|
-
this.z =
|
|
2692
|
+
set(x, y, z) {
|
|
2693
|
+
this.x = x?.x || x || 0;
|
|
2694
|
+
this.y = x?.y || y || 0;
|
|
2695
|
+
this.z = x?.z || z || 0;
|
|
2696
|
+
return this;
|
|
2695
2697
|
}
|
|
2696
2698
|
copy() {
|
|
2697
2699
|
return new Q5.Vector(this.x, this.y, this.z);
|
|
@@ -2813,6 +2815,12 @@ Q5.Vector = class {
|
|
|
2813
2815
|
heading() {
|
|
2814
2816
|
return this._$.atan2(this.y, this.x);
|
|
2815
2817
|
}
|
|
2818
|
+
setHeading(ang) {
|
|
2819
|
+
let mag = this.mag(); // Calculate the magnitude of the vector
|
|
2820
|
+
this.x = mag * this._$.cos(ang); // Set the new x component
|
|
2821
|
+
this.y = mag * this._$.sin(ang); // Set the new y component
|
|
2822
|
+
return this;
|
|
2823
|
+
}
|
|
2816
2824
|
rotate(ang) {
|
|
2817
2825
|
let costh = this._$.cos(ang);
|
|
2818
2826
|
let sinth = this._$.sin(ang);
|
|
@@ -2830,13 +2838,52 @@ Q5.Vector = class {
|
|
|
2830
2838
|
}
|
|
2831
2839
|
lerp() {
|
|
2832
2840
|
let args = [...arguments];
|
|
2833
|
-
let u = this._arg2v(...args.slice(0, -1));
|
|
2834
2841
|
let amt = args.at(-1);
|
|
2842
|
+
if (amt == 0) return this;
|
|
2843
|
+
let u = this._arg2v(...args.slice(0, -1));
|
|
2835
2844
|
this.x += (u.x - this.x) * amt;
|
|
2836
2845
|
this.y += (u.y - this.y) * amt;
|
|
2837
2846
|
this.z += (u.z - this.z) * amt;
|
|
2838
2847
|
return this;
|
|
2839
2848
|
}
|
|
2849
|
+
slerp() {
|
|
2850
|
+
let args = [...arguments];
|
|
2851
|
+
let amt = args.at(-1);
|
|
2852
|
+
if (amt == 0) return this;
|
|
2853
|
+
let u = this._arg2v(...args.slice(0, -1));
|
|
2854
|
+
if (amt == 1) return this.set(u);
|
|
2855
|
+
|
|
2856
|
+
let v0Mag = this.mag();
|
|
2857
|
+
let v1Mag = u.mag();
|
|
2858
|
+
|
|
2859
|
+
if (v0Mag == 0 || v1Mag == 0) {
|
|
2860
|
+
return this.mult(1 - amt).add(u.mult(amt));
|
|
2861
|
+
}
|
|
2862
|
+
|
|
2863
|
+
let axis = Q5.Vector.cross(this, u);
|
|
2864
|
+
let axisMag = axis.mag();
|
|
2865
|
+
let theta = Math.atan2(axisMag, this.dot(u));
|
|
2866
|
+
|
|
2867
|
+
if (axisMag > 0) {
|
|
2868
|
+
axis.div(axisMag);
|
|
2869
|
+
} else if (theta < this._$.HALF_PI) {
|
|
2870
|
+
return this.mult(1 - amt).add(u.mult(amt));
|
|
2871
|
+
} else {
|
|
2872
|
+
if (this.z == 0 && u.z == 0) axis.set(0, 0, 1);
|
|
2873
|
+
else if (this.x != 0) axis.set(this.y, -this.x, 0).normalize();
|
|
2874
|
+
else axis.set(1, 0, 0);
|
|
2875
|
+
}
|
|
2876
|
+
|
|
2877
|
+
let ey = axis.cross(this);
|
|
2878
|
+
let lerpedMagFactor = 1 - amt + (amt * v1Mag) / v0Mag;
|
|
2879
|
+
let cosMultiplier = lerpedMagFactor * Math.cos(amt * theta);
|
|
2880
|
+
let sinMultiplier = lerpedMagFactor * Math.sin(amt * theta);
|
|
2881
|
+
|
|
2882
|
+
this.x = this.x * cosMultiplier + ey.x * sinMultiplier;
|
|
2883
|
+
this.y = this.y * cosMultiplier + ey.y * sinMultiplier;
|
|
2884
|
+
this.z = this.z * cosMultiplier + ey.z * sinMultiplier;
|
|
2885
|
+
return this;
|
|
2886
|
+
}
|
|
2840
2887
|
reflect(n) {
|
|
2841
2888
|
n.normalize();
|
|
2842
2889
|
return this.sub(n.mult(2 * this.dot(n)));
|
|
@@ -2889,6 +2936,7 @@ Q5.Vector.div = (v, u) => v.copy().div(u);
|
|
|
2889
2936
|
Q5.Vector.dot = (v, u) => v.copy().dot(u);
|
|
2890
2937
|
Q5.Vector.equals = (v, u, epsilon) => v.equals(u, epsilon);
|
|
2891
2938
|
Q5.Vector.lerp = (v, u, amt) => v.copy().lerp(u, amt);
|
|
2939
|
+
Q5.Vector.slerp = (v, u, amt) => v.copy().slerp(u, amt);
|
|
2892
2940
|
Q5.Vector.limit = (v, m) => v.copy().limit(m);
|
|
2893
2941
|
Q5.Vector.heading = (v) => this._$.atan2(v.y, v.x);
|
|
2894
2942
|
Q5.Vector.magSq = (v) => v.x * v.x + v.y * v.y + v.z * v.z;
|
|
@@ -3200,7 +3248,7 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
3200
3248
|
Q5.webgpu = async function (scope, parent) {
|
|
3201
3249
|
if (!scope || scope == 'global') Q5._hasGlobal = true;
|
|
3202
3250
|
if (!navigator.gpu) {
|
|
3203
|
-
console.
|
|
3251
|
+
console.warn('q5 WebGPU not supported on this browser!');
|
|
3204
3252
|
let q = new Q5(scope, parent);
|
|
3205
3253
|
q.colorMode('rgb', 1);
|
|
3206
3254
|
q._beginRender = () => q.translate(q.canvas.hw, q.canvas.hh);
|
|
@@ -3563,5 +3611,190 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
|
3563
3611
|
$.pass.setBindGroup(2, colorsBindGroup);
|
|
3564
3612
|
});
|
|
3565
3613
|
};
|
|
3566
|
-
Q5.renderers.webgpu.image = ($, q) => {
|
|
3614
|
+
Q5.renderers.webgpu.image = ($, q) => {
|
|
3615
|
+
$.imageStack = [];
|
|
3616
|
+
$.textures = [];
|
|
3617
|
+
|
|
3618
|
+
$._hooks.postCanvas.push(() => {
|
|
3619
|
+
let imageVertexShader = Q5.device.createShaderModule({
|
|
3620
|
+
code: `
|
|
3621
|
+
struct VertexOutput {
|
|
3622
|
+
@builtin(position) position: vec4<f32>,
|
|
3623
|
+
@location(0) texCoord: vec2<f32>,
|
|
3624
|
+
@location(1) textureIndex: f32
|
|
3625
|
+
};
|
|
3626
|
+
|
|
3627
|
+
struct Uniforms {
|
|
3628
|
+
halfWidth: f32,
|
|
3629
|
+
halfHeight: f32
|
|
3630
|
+
};
|
|
3631
|
+
|
|
3632
|
+
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
|
3633
|
+
@group(1) @binding(0) var<storage, read> transforms: array<mat4x4<f32>>;
|
|
3634
|
+
|
|
3635
|
+
@vertex
|
|
3636
|
+
fn vertexMain(@location(0) pos: vec2<f32>, @location(1) texCoord: vec2<f32>, @location(2) transformIndex: f32, @location(3) textureIndex: f32) -> VertexOutput {
|
|
3637
|
+
var vert = vec4<f32>(pos, 0.0, 1.0);
|
|
3638
|
+
vert *= transforms[i32(transformIndex)];
|
|
3639
|
+
vert.x /= uniforms.halfWidth;
|
|
3640
|
+
vert.y /= uniforms.halfHeight;
|
|
3641
|
+
|
|
3642
|
+
var output: VertexOutput;
|
|
3643
|
+
output.position = vert;
|
|
3644
|
+
output.texCoord = texCoord;
|
|
3645
|
+
output.textureIndex = textureIndex;
|
|
3646
|
+
return output;
|
|
3647
|
+
}
|
|
3648
|
+
`
|
|
3649
|
+
});
|
|
3650
|
+
|
|
3651
|
+
let imageFragmentShader = Q5.device.createShaderModule({
|
|
3652
|
+
code: `
|
|
3653
|
+
@group(0) @binding(0) var samp: sampler;
|
|
3654
|
+
@group(0) @binding(1) var textures: array<texture_2d<f32>>;
|
|
3655
|
+
|
|
3656
|
+
@fragment
|
|
3657
|
+
fn fragmentMain(@location(0) texCoord: vec2<f32>, @location(1) textureIndex: f32) -> @location(0) vec4<f32> {
|
|
3658
|
+
return textureSample(textures[i32(textureIndex)], samp, texCoord);
|
|
3659
|
+
}
|
|
3660
|
+
`
|
|
3661
|
+
});
|
|
3662
|
+
|
|
3663
|
+
const bindGroupLayouts = [
|
|
3664
|
+
Q5.device.createBindGroupLayout({
|
|
3665
|
+
entries: [
|
|
3666
|
+
{ binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: 'uniform' } },
|
|
3667
|
+
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, sampler: {} },
|
|
3668
|
+
{ binding: 2, visibility: GPUShaderStage.FRAGMENT, texture: { viewDimension: '2d', sampleType: 'float' } }
|
|
3669
|
+
]
|
|
3670
|
+
}),
|
|
3671
|
+
Q5.device.createBindGroupLayout({
|
|
3672
|
+
entries: [{ binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: 'read-only-storage' } }]
|
|
3673
|
+
})
|
|
3674
|
+
];
|
|
3675
|
+
|
|
3676
|
+
const pipelineLayout = Q5.device.createPipelineLayout({
|
|
3677
|
+
bindGroupLayouts: bindGroupLayouts
|
|
3678
|
+
});
|
|
3679
|
+
|
|
3680
|
+
$.pipelines[1] = Q5.device.createRenderPipeline({
|
|
3681
|
+
layout: pipelineLayout,
|
|
3682
|
+
vertex: {
|
|
3683
|
+
module: imageVertexShader,
|
|
3684
|
+
entryPoint: 'vertexMain',
|
|
3685
|
+
buffers: [
|
|
3686
|
+
{
|
|
3687
|
+
arrayStride: 5 * 4, // 4 floats for position and texCoord, 1 float for textureIndex
|
|
3688
|
+
attributes: [
|
|
3689
|
+
{ shaderLocation: 0, offset: 0, format: 'float32x2' },
|
|
3690
|
+
{ shaderLocation: 1, offset: 2 * 4, format: 'float32x2' },
|
|
3691
|
+
{ shaderLocation: 2, offset: 4 * 4, format: 'float32' } // textureIndex
|
|
3692
|
+
]
|
|
3693
|
+
}
|
|
3694
|
+
]
|
|
3695
|
+
},
|
|
3696
|
+
fragment: {
|
|
3697
|
+
module: imageFragmentShader,
|
|
3698
|
+
entryPoint: 'fragmentMain',
|
|
3699
|
+
targets: [{ format: 'bgra8unorm' }]
|
|
3700
|
+
},
|
|
3701
|
+
primitive: {
|
|
3702
|
+
topology: 'triangle-list'
|
|
3703
|
+
}
|
|
3704
|
+
});
|
|
3705
|
+
|
|
3706
|
+
$.sampler = Q5.device.createSampler({
|
|
3707
|
+
magFilter: 'linear',
|
|
3708
|
+
minFilter: 'linear'
|
|
3709
|
+
});
|
|
3710
|
+
});
|
|
3711
|
+
|
|
3712
|
+
$.loadImage = async (src) => {
|
|
3713
|
+
const img = new Image();
|
|
3714
|
+
img.onload = async () => {
|
|
3715
|
+
const imageBitmap = await createImageBitmap(img);
|
|
3716
|
+
const texture = Q5.device.createTexture({
|
|
3717
|
+
size: [img.width, img.height, 1],
|
|
3718
|
+
format: 'bgra8unorm',
|
|
3719
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
|
|
3720
|
+
});
|
|
3721
|
+
|
|
3722
|
+
Q5.device.queue.copyExternalImageToTexture({ source: imageBitmap }, { texture }, [img.width, img.height, 1]);
|
|
3723
|
+
|
|
3724
|
+
img.texture = texture;
|
|
3725
|
+
img.index = $.textures.length;
|
|
3726
|
+
$.textures.push(texture);
|
|
3727
|
+
};
|
|
3728
|
+
img.src = src;
|
|
3729
|
+
return img;
|
|
3730
|
+
};
|
|
3731
|
+
|
|
3732
|
+
$._hooks.preRender.push(() => {
|
|
3733
|
+
if (!$.imageStack.length) return;
|
|
3734
|
+
|
|
3735
|
+
// Switch to image pipeline
|
|
3736
|
+
$.pass.setPipeline($.pipelines[1]);
|
|
3737
|
+
|
|
3738
|
+
// Create a vertex buffer for the image quads
|
|
3739
|
+
const vertices = new Float32Array($.vertexStack);
|
|
3740
|
+
|
|
3741
|
+
const vertexBuffer = Q5.device.createBuffer({
|
|
3742
|
+
size: vertices.byteLength,
|
|
3743
|
+
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
|
|
3744
|
+
});
|
|
3745
|
+
|
|
3746
|
+
Q5.device.queue.writeBuffer(vertexBuffer, 0, vertices);
|
|
3747
|
+
$.pass.setVertexBuffer(0, vertexBuffer);
|
|
3748
|
+
|
|
3749
|
+
// Set the bind group for the sampler and textures
|
|
3750
|
+
if ($.textures.length !== previousTextureCount) {
|
|
3751
|
+
previousTextureCount = $.textures.length;
|
|
3752
|
+
|
|
3753
|
+
// Create the bind group for all textures
|
|
3754
|
+
const textureViews = $.textures.map((tex) => tex.createView());
|
|
3755
|
+
|
|
3756
|
+
$.textureBindGroup = Q5.device.createBindGroup({
|
|
3757
|
+
layout: $.pipelines[1].getBindGroupLayout(0),
|
|
3758
|
+
entries: [
|
|
3759
|
+
{ binding: 0, resource: $.sampler },
|
|
3760
|
+
...textureViews.map((view, i) => ({ binding: i + 1, resource: view }))
|
|
3761
|
+
]
|
|
3762
|
+
});
|
|
3763
|
+
}
|
|
3764
|
+
|
|
3765
|
+
// Set the bind group for the sampler and textures
|
|
3766
|
+
$.pass.setBindGroup(0, $.textureBindGroup);
|
|
3767
|
+
});
|
|
3768
|
+
|
|
3769
|
+
$.image = (img, x, y, w, h) => {
|
|
3770
|
+
if ($._matrixDirty) $._saveMatrix();
|
|
3771
|
+
let ti = $._transformIndex;
|
|
3772
|
+
|
|
3773
|
+
$.imageStack.push(img.index);
|
|
3774
|
+
|
|
3775
|
+
// Calculate half-width and half-height
|
|
3776
|
+
let hw = w / 2;
|
|
3777
|
+
let hh = h / 2;
|
|
3778
|
+
|
|
3779
|
+
// Calculate vertices positions
|
|
3780
|
+
let left = x - hw;
|
|
3781
|
+
let right = x + hw;
|
|
3782
|
+
let top = -(y - hh); // y is inverted in WebGPU
|
|
3783
|
+
let bottom = -(y + hh);
|
|
3784
|
+
|
|
3785
|
+
let ii = img.index;
|
|
3786
|
+
|
|
3787
|
+
// prettier-ignore
|
|
3788
|
+
$.vertexStack.push(
|
|
3789
|
+
left, top, 0, 0, ti, ii,
|
|
3790
|
+
right, top, 1, 0, ti, ii,
|
|
3791
|
+
left, bottom, 0, 1, ti, ii,
|
|
3792
|
+
right, top, 1, 0, ti, ii,
|
|
3793
|
+
left, bottom, 0, 1, ti, ii,
|
|
3794
|
+
right, bottom, 1, 1, ti, ii
|
|
3795
|
+
);
|
|
3796
|
+
|
|
3797
|
+
$.drawStack.push(6);
|
|
3798
|
+
};
|
|
3799
|
+
};
|
|
3567
3800
|
Q5.renderers.webgpu.text = ($, q) => {};
|