q5 2.18.3 → 2.19.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/deno.json +1 -1
- package/package.json +1 -1
- package/q5.d.ts +195 -49
- package/q5.js +279 -124
- package/q5.min.js +2 -2
package/q5.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* q5.js
|
|
3
|
-
* @version 2.
|
|
3
|
+
* @version 2.19
|
|
4
4
|
* @author quinton-ashley, Tezumie, and LingDong-
|
|
5
5
|
* @license LGPL-3.0
|
|
6
6
|
* @class Q5
|
|
@@ -104,9 +104,9 @@ function Q5(scope, parent, renderer) {
|
|
|
104
104
|
throw e;
|
|
105
105
|
}
|
|
106
106
|
for (let m of Q5.methods.post) m.call($);
|
|
107
|
+
$.postProcess();
|
|
107
108
|
if ($._render) $._render();
|
|
108
109
|
if ($._finishRender) $._finishRender();
|
|
109
|
-
$.postProcess();
|
|
110
110
|
q.pmouseX = $.mouseX;
|
|
111
111
|
q.pmouseY = $.mouseY;
|
|
112
112
|
q.moveX = q.moveY = 0;
|
|
@@ -313,7 +313,7 @@ function createCanvas(w, h, opt) {
|
|
|
313
313
|
}
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
-
Q5.version = Q5.VERSION = '2.
|
|
316
|
+
Q5.version = Q5.VERSION = '2.19';
|
|
317
317
|
|
|
318
318
|
if (typeof document == 'object') {
|
|
319
319
|
document.addEventListener('DOMContentLoaded', () => {
|
|
@@ -422,49 +422,6 @@ Q5.modules.canvas = ($, q) => {
|
|
|
422
422
|
return g;
|
|
423
423
|
};
|
|
424
424
|
|
|
425
|
-
async function saveFile(data, name, ext) {
|
|
426
|
-
name = name || 'untitled';
|
|
427
|
-
ext = ext || 'png';
|
|
428
|
-
if (ext == 'jpg' || ext == 'png' || ext == 'webp') {
|
|
429
|
-
if (data instanceof OffscreenCanvas) {
|
|
430
|
-
const blob = await data.convertToBlob({ type: 'image/' + ext });
|
|
431
|
-
data = await new Promise((resolve) => {
|
|
432
|
-
const reader = new FileReader();
|
|
433
|
-
reader.onloadend = () => resolve(reader.result);
|
|
434
|
-
reader.readAsDataURL(blob);
|
|
435
|
-
});
|
|
436
|
-
} else {
|
|
437
|
-
data = data.toDataURL('image/' + ext);
|
|
438
|
-
}
|
|
439
|
-
} else {
|
|
440
|
-
let type = 'text/plain';
|
|
441
|
-
if (ext == 'json') {
|
|
442
|
-
if (typeof data != 'string') data = JSON.stringify(data);
|
|
443
|
-
type = 'text/json';
|
|
444
|
-
}
|
|
445
|
-
data = new Blob([data], { type });
|
|
446
|
-
data = URL.createObjectURL(data);
|
|
447
|
-
}
|
|
448
|
-
let a = document.createElement('a');
|
|
449
|
-
a.href = data;
|
|
450
|
-
a.download = name + '.' + ext;
|
|
451
|
-
a.click();
|
|
452
|
-
URL.revokeObjectURL(a.href);
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
$.save = (a, b, c) => {
|
|
456
|
-
if (!a || (typeof a == 'string' && (!b || (!c && b.length < 5)))) {
|
|
457
|
-
c = b;
|
|
458
|
-
b = a;
|
|
459
|
-
a = $.canvas;
|
|
460
|
-
}
|
|
461
|
-
if (c) return saveFile(a, b, c);
|
|
462
|
-
if (b) {
|
|
463
|
-
b = b.split('.');
|
|
464
|
-
saveFile(a, b[0], b.at(-1));
|
|
465
|
-
} else saveFile(a);
|
|
466
|
-
};
|
|
467
|
-
|
|
468
425
|
$._setCanvasSize = (w, h) => {
|
|
469
426
|
if (!w) h ??= window.innerHeight;
|
|
470
427
|
else h ??= w;
|
|
@@ -1324,10 +1281,7 @@ Q5.renderers.c2d.image = ($, q) => {
|
|
|
1324
1281
|
opt ??= {};
|
|
1325
1282
|
opt.alpha ??= true;
|
|
1326
1283
|
opt.colorSpace ??= $.canvas.colorSpace || Q5.canvasOptions.colorSpace;
|
|
1327
|
-
|
|
1328
|
-
img.defaultWidth = w * $._defaultImageScale;
|
|
1329
|
-
img.defaultHeight = h * $._defaultImageScale;
|
|
1330
|
-
return img;
|
|
1284
|
+
return new Q5.Image(w, h, opt);
|
|
1331
1285
|
};
|
|
1332
1286
|
|
|
1333
1287
|
$.loadImage = function (url, cb, opt) {
|
|
@@ -1483,7 +1437,7 @@ Q5.renderers.c2d.image = ($, q) => {
|
|
|
1483
1437
|
$.ctx.globalCompositeOperation = 'source-over';
|
|
1484
1438
|
$.ctx.drawImage($.canvas, 0, 0, $.canvas.w, $.canvas.h);
|
|
1485
1439
|
$.ctx.restore();
|
|
1486
|
-
$._retint = true;
|
|
1440
|
+
$.modified = $._retint = true;
|
|
1487
1441
|
};
|
|
1488
1442
|
|
|
1489
1443
|
if ($._scope == 'image') {
|
|
@@ -1501,7 +1455,7 @@ Q5.renderers.c2d.image = ($, q) => {
|
|
|
1501
1455
|
$.ctx.clearRect(0, 0, c.width, c.height);
|
|
1502
1456
|
$.ctx.drawImage(o, 0, 0, c.width, c.height);
|
|
1503
1457
|
|
|
1504
|
-
$._retint = true;
|
|
1458
|
+
$.modified = $._retint = true;
|
|
1505
1459
|
};
|
|
1506
1460
|
}
|
|
1507
1461
|
|
|
@@ -1548,17 +1502,25 @@ Q5.renderers.c2d.image = ($, q) => {
|
|
|
1548
1502
|
$.ctx.globalCompositeOperation = old;
|
|
1549
1503
|
$.ctx.restore();
|
|
1550
1504
|
|
|
1551
|
-
$._retint = true;
|
|
1505
|
+
$.modified = $._retint = true;
|
|
1552
1506
|
};
|
|
1553
1507
|
|
|
1554
1508
|
$.inset = (x, y, w, h, dx, dy, dw, dh) => {
|
|
1555
1509
|
let pd = $._pixelDensity || 1;
|
|
1556
1510
|
$.ctx.drawImage($.canvas, x * pd, y * pd, w * pd, h * pd, dx, dy, dw, dh);
|
|
1557
1511
|
|
|
1558
|
-
$._retint = true;
|
|
1512
|
+
$.modified = $._retint = true;
|
|
1559
1513
|
};
|
|
1560
1514
|
|
|
1561
|
-
$.copy = () =>
|
|
1515
|
+
$.copy = () => {
|
|
1516
|
+
let img = $.get();
|
|
1517
|
+
for (let prop in $) {
|
|
1518
|
+
if (typeof $[prop] != 'function' && !/(canvas|ctx|texture|textureIndex)/.test(prop)) {
|
|
1519
|
+
img[prop] = $[prop];
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
return img;
|
|
1523
|
+
};
|
|
1562
1524
|
|
|
1563
1525
|
$.get = (x, y, w, h) => {
|
|
1564
1526
|
let pd = $._pixelDensity || 1;
|
|
@@ -1568,22 +1530,19 @@ Q5.renderers.c2d.image = ($, q) => {
|
|
|
1568
1530
|
}
|
|
1569
1531
|
x = Math.floor(x || 0) * pd;
|
|
1570
1532
|
y = Math.floor(y || 0) * pd;
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
w
|
|
1574
|
-
h
|
|
1575
|
-
|
|
1576
|
-
img.
|
|
1577
|
-
img._pixelDensity = pd;
|
|
1578
|
-
img.width = _w;
|
|
1579
|
-
img.height = _h;
|
|
1533
|
+
w ??= $.width;
|
|
1534
|
+
h ??= $.height;
|
|
1535
|
+
let img = $.createImage(w, h, { pixelDensity: pd });
|
|
1536
|
+
img.ctx.drawImage($.canvas, x, y, w * pd, h * pd, 0, 0, w, h);
|
|
1537
|
+
img.width = w;
|
|
1538
|
+
img.height = h;
|
|
1580
1539
|
return img;
|
|
1581
1540
|
};
|
|
1582
1541
|
|
|
1583
1542
|
$.set = (x, y, c) => {
|
|
1584
1543
|
x = Math.floor(x);
|
|
1585
1544
|
y = Math.floor(y);
|
|
1586
|
-
$._retint = true;
|
|
1545
|
+
$.modified = $._retint = true;
|
|
1587
1546
|
if (c.canvas) {
|
|
1588
1547
|
let old = $._tint;
|
|
1589
1548
|
$._tint = null;
|
|
@@ -1611,7 +1570,7 @@ Q5.renderers.c2d.image = ($, q) => {
|
|
|
1611
1570
|
$.updatePixels = () => {
|
|
1612
1571
|
if (imgData != null) {
|
|
1613
1572
|
$.ctx.putImageData(imgData, 0, 0);
|
|
1614
|
-
$._retint = true;
|
|
1573
|
+
$.modified = $._retint = true;
|
|
1615
1574
|
}
|
|
1616
1575
|
};
|
|
1617
1576
|
|
|
@@ -1620,6 +1579,20 @@ Q5.renderers.c2d.image = ($, q) => {
|
|
|
1620
1579
|
|
|
1621
1580
|
if ($._scope == 'image') return;
|
|
1622
1581
|
|
|
1582
|
+
$._saveCanvas = async (data, ext) => {
|
|
1583
|
+
data = data.canvas || data;
|
|
1584
|
+
if (data instanceof OffscreenCanvas) {
|
|
1585
|
+
const blob = await data.convertToBlob({ type: 'image/' + ext });
|
|
1586
|
+
|
|
1587
|
+
return await new Promise((resolve) => {
|
|
1588
|
+
const reader = new FileReader();
|
|
1589
|
+
reader.onloadend = () => resolve(reader.result);
|
|
1590
|
+
reader.readAsDataURL(blob);
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1593
|
+
return data.toDataURL('image/' + ext);
|
|
1594
|
+
};
|
|
1595
|
+
|
|
1623
1596
|
$.tint = function (c) {
|
|
1624
1597
|
$._tint = (c._q5Color ? c : $.color(...arguments)).toString();
|
|
1625
1598
|
};
|
|
@@ -1963,10 +1936,15 @@ Q5.renderers.c2d.text = ($, q) => {
|
|
|
1963
1936
|
tY = leading * lines.length;
|
|
1964
1937
|
|
|
1965
1938
|
if (!img) {
|
|
1939
|
+
let ogBaseline = $.ctx.textBaseline;
|
|
1940
|
+
$.ctx.textBaseline = 'alphabetic';
|
|
1941
|
+
|
|
1966
1942
|
let measure = ctx.measureText(' ');
|
|
1967
1943
|
let ascent = measure.fontBoundingBoxAscent;
|
|
1968
1944
|
let descent = measure.fontBoundingBoxDescent;
|
|
1969
1945
|
|
|
1946
|
+
$.ctx.textBaseline = ogBaseline;
|
|
1947
|
+
|
|
1970
1948
|
img = $.createImage.call($, Math.ceil(ctx.measureText(str).width), Math.ceil(tY + descent), {
|
|
1971
1949
|
pixelDensity: $._pixelDensity
|
|
1972
1950
|
});
|
|
@@ -1977,12 +1955,13 @@ Q5.renderers.c2d.text = ($, q) => {
|
|
|
1977
1955
|
img._middle = img._top + ascent * 0.5;
|
|
1978
1956
|
img._bottom = img._top + ascent;
|
|
1979
1957
|
img._leading = leading;
|
|
1958
|
+
} else {
|
|
1959
|
+
img.modified = true;
|
|
1980
1960
|
}
|
|
1981
1961
|
|
|
1982
1962
|
img._fill = $._fill;
|
|
1983
1963
|
img._stroke = $._stroke;
|
|
1984
1964
|
img._strokeWeight = $._strokeWeight;
|
|
1985
|
-
img.modified = true;
|
|
1986
1965
|
|
|
1987
1966
|
ctx = img.ctx;
|
|
1988
1967
|
|
|
@@ -2913,7 +2892,7 @@ Q5.modules.input = ($, q) => {
|
|
|
2913
2892
|
$._onwheel = (e) => {
|
|
2914
2893
|
$._updateMouse(e);
|
|
2915
2894
|
e.delta = e.deltaY;
|
|
2916
|
-
if ($.mouseWheel(e) == false) e.preventDefault();
|
|
2895
|
+
if ($.mouseWheel(e) == false || $._noScroll) e.preventDefault();
|
|
2917
2896
|
};
|
|
2918
2897
|
|
|
2919
2898
|
$.cursor = (name, x, y) => {
|
|
@@ -2928,9 +2907,8 @@ Q5.modules.input = ($, q) => {
|
|
|
2928
2907
|
$.canvas.style.cursor = name + pfx;
|
|
2929
2908
|
};
|
|
2930
2909
|
|
|
2931
|
-
$.noCursor = () =>
|
|
2932
|
-
|
|
2933
|
-
};
|
|
2910
|
+
$.noCursor = () => ($.canvas.style.cursor = 'none');
|
|
2911
|
+
$.noScroll = () => ($._noScroll = true);
|
|
2934
2912
|
|
|
2935
2913
|
if (window) {
|
|
2936
2914
|
$.lockMouse = document.body?.requestPointerLock;
|
|
@@ -4044,6 +4022,10 @@ Q5.modules.util = ($, q) => {
|
|
|
4044
4022
|
$.loadJSON = (url, cb) => $._loadFile(url, cb, 'json');
|
|
4045
4023
|
$.loadCSV = (url, cb) => $._loadFile(url, cb, 'csv');
|
|
4046
4024
|
|
|
4025
|
+
const imgRegex = /(jpe?g|png|gif|webp|avif|svg)/,
|
|
4026
|
+
fontRegex = /(ttf|otf|woff2?|eot|json)/,
|
|
4027
|
+
audioRegex = /(wav|flac|mp3|ogg|m4a|aac|aiff|weba)/;
|
|
4028
|
+
|
|
4047
4029
|
$.load = function (...urls) {
|
|
4048
4030
|
if (Array.isArray(urls[0])) urls = urls[0];
|
|
4049
4031
|
|
|
@@ -4057,11 +4039,11 @@ Q5.modules.util = ($, q) => {
|
|
|
4057
4039
|
obj = $.loadJSON(url);
|
|
4058
4040
|
} else if (ext == 'csv') {
|
|
4059
4041
|
obj = $.loadCSV(url);
|
|
4060
|
-
} else if (
|
|
4042
|
+
} else if (imgRegex.test(ext)) {
|
|
4061
4043
|
obj = $.loadImage(url);
|
|
4062
|
-
} else if (
|
|
4044
|
+
} else if (fontRegex.test(ext)) {
|
|
4063
4045
|
obj = $.loadFont(url);
|
|
4064
|
-
} else if (
|
|
4046
|
+
} else if (audioRegex.test(ext)) {
|
|
4065
4047
|
obj = $.loadSound(url);
|
|
4066
4048
|
} else {
|
|
4067
4049
|
obj = $.loadText(url);
|
|
@@ -4073,6 +4055,40 @@ Q5.modules.util = ($, q) => {
|
|
|
4073
4055
|
return Promise.all(loaders);
|
|
4074
4056
|
};
|
|
4075
4057
|
|
|
4058
|
+
async function saveFile(data, name, ext) {
|
|
4059
|
+
name = name || 'untitled';
|
|
4060
|
+
ext = ext || 'png';
|
|
4061
|
+
if (imgRegex.test(ext)) {
|
|
4062
|
+
data = await $._saveCanvas(data, ext);
|
|
4063
|
+
} else {
|
|
4064
|
+
let type = 'text/plain';
|
|
4065
|
+
if (ext == 'json') {
|
|
4066
|
+
if (typeof data != 'string') data = JSON.stringify(data);
|
|
4067
|
+
type = 'text/json';
|
|
4068
|
+
}
|
|
4069
|
+
data = new Blob([data], { type });
|
|
4070
|
+
data = URL.createObjectURL(data);
|
|
4071
|
+
}
|
|
4072
|
+
let a = document.createElement('a');
|
|
4073
|
+
a.href = data;
|
|
4074
|
+
a.download = name + '.' + ext;
|
|
4075
|
+
a.click();
|
|
4076
|
+
setTimeout(() => URL.revokeObjectURL(a.href), 1000);
|
|
4077
|
+
}
|
|
4078
|
+
|
|
4079
|
+
$.save = (a, b, c) => {
|
|
4080
|
+
if (!a || (typeof a == 'string' && (!b || (!c && b.length < 5)))) {
|
|
4081
|
+
c = b;
|
|
4082
|
+
b = a;
|
|
4083
|
+
a = $.canvas;
|
|
4084
|
+
}
|
|
4085
|
+
if (c) saveFile(a, b, c);
|
|
4086
|
+
else if (b) {
|
|
4087
|
+
let lastDot = b.lastIndexOf('.');
|
|
4088
|
+
saveFile(a, b.slice(0, lastDot), b.slice(lastDot + 1));
|
|
4089
|
+
} else saveFile(a);
|
|
4090
|
+
};
|
|
4091
|
+
|
|
4076
4092
|
$.CSV = {};
|
|
4077
4093
|
$.CSV.parse = (csv, sep = ',', lineSep = '\n') => {
|
|
4078
4094
|
if (!csv.length) return [];
|
|
@@ -4406,11 +4422,6 @@ Q5.Vector.sub = (v, u) => v.copy().sub(u);
|
|
|
4406
4422
|
for (let k of ['fromAngle', 'fromAngles', 'random2D', 'random3D']) {
|
|
4407
4423
|
Q5.Vector[k] = (u, v, t) => new Q5.Vector()[k](u, v, t);
|
|
4408
4424
|
}
|
|
4409
|
-
/**
|
|
4410
|
-
* q5-webgpu
|
|
4411
|
-
*
|
|
4412
|
-
* EXPERIMENTAL, for developer testing only!
|
|
4413
|
-
*/
|
|
4414
4425
|
Q5.renderers.webgpu = {};
|
|
4415
4426
|
|
|
4416
4427
|
Q5.renderers.webgpu.canvas = ($, q) => {
|
|
@@ -4426,6 +4437,11 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
4426
4437
|
|
|
4427
4438
|
let pass,
|
|
4428
4439
|
mainView,
|
|
4440
|
+
frameTextureA,
|
|
4441
|
+
frameTextureB,
|
|
4442
|
+
frameSampler,
|
|
4443
|
+
framePipeline,
|
|
4444
|
+
frameBindGroup,
|
|
4429
4445
|
colorIndex = 1,
|
|
4430
4446
|
colorStackIndex = 8;
|
|
4431
4447
|
|
|
@@ -4474,14 +4490,60 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
4474
4490
|
});
|
|
4475
4491
|
|
|
4476
4492
|
let createMainView = () => {
|
|
4493
|
+
let w = $.canvas.width,
|
|
4494
|
+
h = $.canvas.height,
|
|
4495
|
+
size = [w, h],
|
|
4496
|
+
format = 'bgra8unorm';
|
|
4497
|
+
|
|
4477
4498
|
mainView = Q5.device
|
|
4478
4499
|
.createTexture({
|
|
4479
|
-
size
|
|
4500
|
+
size,
|
|
4480
4501
|
sampleCount: 4,
|
|
4481
|
-
format
|
|
4502
|
+
format,
|
|
4482
4503
|
usage: GPUTextureUsage.RENDER_ATTACHMENT
|
|
4483
4504
|
})
|
|
4484
4505
|
.createView();
|
|
4506
|
+
|
|
4507
|
+
let usage =
|
|
4508
|
+
GPUTextureUsage.COPY_SRC |
|
|
4509
|
+
GPUTextureUsage.COPY_DST |
|
|
4510
|
+
GPUTextureUsage.TEXTURE_BINDING |
|
|
4511
|
+
GPUTextureUsage.RENDER_ATTACHMENT;
|
|
4512
|
+
|
|
4513
|
+
frameTextureA = Q5.device.createTexture({ size, format, usage });
|
|
4514
|
+
frameTextureB = Q5.device.createTexture({ size, format, usage });
|
|
4515
|
+
|
|
4516
|
+
let finalShader = Q5.device.createShaderModule({
|
|
4517
|
+
code: `
|
|
4518
|
+
@vertex fn v(@builtin(vertex_index)i:u32)->@builtin(position)vec4<f32>{
|
|
4519
|
+
const pos=array(vec2(-1f,-1f),vec2(1f,-1f),vec2(-1f,1f),vec2(1f,1f));
|
|
4520
|
+
return vec4(pos[i],0f,1f);
|
|
4521
|
+
}
|
|
4522
|
+
@group(0) @binding(0) var s: sampler;
|
|
4523
|
+
@group(0) @binding(1) var t: texture_2d<f32>;
|
|
4524
|
+
@fragment fn f(@builtin(position)c:vec4<f32>)->@location(0)vec4<f32>{
|
|
4525
|
+
let uv=c.xy/vec2(${w}, ${h});
|
|
4526
|
+
return textureSample(t,s,uv);
|
|
4527
|
+
}`
|
|
4528
|
+
});
|
|
4529
|
+
|
|
4530
|
+
frameSampler = Q5.device.createSampler({
|
|
4531
|
+
magFilter: 'linear',
|
|
4532
|
+
minFilter: 'linear'
|
|
4533
|
+
});
|
|
4534
|
+
|
|
4535
|
+
// Create a pipeline for rendering
|
|
4536
|
+
framePipeline = Q5.device.createRenderPipeline({
|
|
4537
|
+
layout: 'auto',
|
|
4538
|
+
vertex: { module: finalShader, entryPoint: 'v' },
|
|
4539
|
+
fragment: {
|
|
4540
|
+
module: finalShader,
|
|
4541
|
+
entryPoint: 'f',
|
|
4542
|
+
targets: [{ format, writeMask: GPUColorWrite.ALL }]
|
|
4543
|
+
},
|
|
4544
|
+
primitive: { topology: 'triangle-strip' },
|
|
4545
|
+
multisample: { count: 4 }
|
|
4546
|
+
});
|
|
4485
4547
|
};
|
|
4486
4548
|
|
|
4487
4549
|
$._createCanvas = (w, h, opt) => {
|
|
@@ -4847,23 +4909,52 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
4847
4909
|
}
|
|
4848
4910
|
};
|
|
4849
4911
|
|
|
4850
|
-
|
|
4912
|
+
let shouldClear = false;
|
|
4913
|
+
$.clear = () => {
|
|
4914
|
+
shouldClear = true;
|
|
4915
|
+
};
|
|
4916
|
+
|
|
4917
|
+
const _drawFrame = () => {
|
|
4918
|
+
pass.setPipeline(framePipeline);
|
|
4919
|
+
pass.setBindGroup(0, frameBindGroup);
|
|
4920
|
+
pass.draw(4);
|
|
4921
|
+
};
|
|
4851
4922
|
|
|
4852
4923
|
$._beginRender = () => {
|
|
4924
|
+
// swap the frame textures
|
|
4925
|
+
const temp = frameTextureA;
|
|
4926
|
+
frameTextureA = frameTextureB;
|
|
4927
|
+
frameTextureB = temp;
|
|
4928
|
+
$.canvas.texture = frameTextureA;
|
|
4929
|
+
|
|
4853
4930
|
$.encoder = Q5.device.createCommandEncoder();
|
|
4854
4931
|
|
|
4932
|
+
let target = shouldClear ? $.ctx.getCurrentTexture().createView() : frameTextureA.createView();
|
|
4933
|
+
|
|
4855
4934
|
pass = q.pass = $.encoder.beginRenderPass({
|
|
4856
4935
|
label: 'q5-webgpu',
|
|
4857
4936
|
colorAttachments: [
|
|
4858
4937
|
{
|
|
4859
4938
|
view: mainView,
|
|
4860
|
-
resolveTarget:
|
|
4939
|
+
resolveTarget: target,
|
|
4861
4940
|
loadOp: 'clear',
|
|
4862
4941
|
storeOp: 'store',
|
|
4863
4942
|
clearValue: [0, 0, 0, 0]
|
|
4864
4943
|
}
|
|
4865
4944
|
]
|
|
4866
4945
|
});
|
|
4946
|
+
|
|
4947
|
+
if (!shouldClear) {
|
|
4948
|
+
frameBindGroup = Q5.device.createBindGroup({
|
|
4949
|
+
layout: framePipeline.getBindGroupLayout(0),
|
|
4950
|
+
entries: [
|
|
4951
|
+
{ binding: 0, resource: frameSampler },
|
|
4952
|
+
{ binding: 1, resource: frameTextureB.createView() }
|
|
4953
|
+
]
|
|
4954
|
+
});
|
|
4955
|
+
|
|
4956
|
+
_drawFrame();
|
|
4957
|
+
}
|
|
4867
4958
|
};
|
|
4868
4959
|
|
|
4869
4960
|
$._render = () => {
|
|
@@ -4938,8 +5029,33 @@ Q5.renderers.webgpu.canvas = ($, q) => {
|
|
|
4938
5029
|
|
|
4939
5030
|
$._finishRender = () => {
|
|
4940
5031
|
pass.end();
|
|
4941
|
-
|
|
4942
|
-
|
|
5032
|
+
|
|
5033
|
+
if (!shouldClear) {
|
|
5034
|
+
pass = $.encoder.beginRenderPass({
|
|
5035
|
+
colorAttachments: [
|
|
5036
|
+
{
|
|
5037
|
+
view: mainView,
|
|
5038
|
+
resolveTarget: $.ctx.getCurrentTexture().createView(),
|
|
5039
|
+
loadOp: 'clear',
|
|
5040
|
+
storeOp: 'store',
|
|
5041
|
+
clearValue: [0, 0, 0, 0]
|
|
5042
|
+
}
|
|
5043
|
+
]
|
|
5044
|
+
});
|
|
5045
|
+
|
|
5046
|
+
frameBindGroup = Q5.device.createBindGroup({
|
|
5047
|
+
layout: framePipeline.getBindGroupLayout(0),
|
|
5048
|
+
entries: [
|
|
5049
|
+
{ binding: 0, resource: frameSampler },
|
|
5050
|
+
{ binding: 1, resource: frameTextureA.createView() }
|
|
5051
|
+
]
|
|
5052
|
+
});
|
|
5053
|
+
_drawFrame();
|
|
5054
|
+
pass.end();
|
|
5055
|
+
shouldClear = false;
|
|
5056
|
+
}
|
|
5057
|
+
|
|
5058
|
+
Q5.device.queue.submit([$.encoder.finish()]);
|
|
4943
5059
|
|
|
4944
5060
|
q.pass = $.encoder = null;
|
|
4945
5061
|
|
|
@@ -5734,12 +5850,10 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
|
|
|
5734
5850
|
|
|
5735
5851
|
$.smooth();
|
|
5736
5852
|
|
|
5737
|
-
let MAX_TEXTURES = 12000;
|
|
5738
|
-
|
|
5739
|
-
$._textures = [];
|
|
5740
5853
|
let tIdx = 0;
|
|
5741
5854
|
|
|
5742
5855
|
$._createTexture = (img) => {
|
|
5856
|
+
let g = img;
|
|
5743
5857
|
if (img.canvas) img = img.canvas;
|
|
5744
5858
|
|
|
5745
5859
|
let textureSize = [img.width, img.height, 1];
|
|
@@ -5759,26 +5873,18 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
|
|
|
5759
5873
|
textureSize
|
|
5760
5874
|
);
|
|
5761
5875
|
|
|
5762
|
-
|
|
5763
|
-
|
|
5876
|
+
g.texture = texture;
|
|
5877
|
+
g.textureIndex = tIdx;
|
|
5764
5878
|
|
|
5765
|
-
|
|
5879
|
+
$._textureBindGroups[tIdx] = Q5.device.createBindGroup({
|
|
5766
5880
|
layout: textureLayout,
|
|
5767
5881
|
entries: [
|
|
5768
5882
|
{ binding: 0, resource: sampler },
|
|
5769
5883
|
{ binding: 1, resource: texture.createView() }
|
|
5770
5884
|
]
|
|
5771
5885
|
});
|
|
5772
|
-
$._textureBindGroups[tIdx] = textureBindGroup;
|
|
5773
5886
|
|
|
5774
|
-
tIdx
|
|
5775
|
-
|
|
5776
|
-
// if the texture array is full, destroy the oldest texture
|
|
5777
|
-
if ($._textures[tIdx]) {
|
|
5778
|
-
$._textures[tIdx].destroy();
|
|
5779
|
-
delete $._textures[tIdx];
|
|
5780
|
-
delete $._textureBindGroups[tIdx];
|
|
5781
|
-
}
|
|
5887
|
+
tIdx++;
|
|
5782
5888
|
};
|
|
5783
5889
|
|
|
5784
5890
|
$.loadImage = (src, cb) => {
|
|
@@ -5809,23 +5915,31 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
|
|
|
5809
5915
|
};
|
|
5810
5916
|
|
|
5811
5917
|
$.image = (img, dx = 0, dy = 0, dw, dh, sx = 0, sy = 0, sw, sh) => {
|
|
5812
|
-
let g = img;
|
|
5813
|
-
if (img.canvas) img = img.canvas;
|
|
5814
5918
|
if (img.textureIndex == undefined) return;
|
|
5815
5919
|
|
|
5920
|
+
let cnv = img.canvas || img;
|
|
5921
|
+
|
|
5816
5922
|
if ($._matrixDirty) $._saveMatrix();
|
|
5817
5923
|
|
|
5818
|
-
let w =
|
|
5819
|
-
h =
|
|
5820
|
-
pd =
|
|
5924
|
+
let w = cnv.width,
|
|
5925
|
+
h = cnv.height,
|
|
5926
|
+
pd = img._pixelDensity || 1;
|
|
5821
5927
|
|
|
5822
|
-
|
|
5823
|
-
|
|
5928
|
+
if (img.modified) {
|
|
5929
|
+
Q5.device.queue.copyExternalImageToTexture(
|
|
5930
|
+
{ source: cnv },
|
|
5931
|
+
{ texture: img.texture, colorSpace: $.canvas.colorSpace },
|
|
5932
|
+
[w, h, 1]
|
|
5933
|
+
);
|
|
5934
|
+
img.modified = false;
|
|
5935
|
+
}
|
|
5936
|
+
|
|
5937
|
+
dw ??= img.defaultWidth;
|
|
5938
|
+
dh ??= img.defaultHeight;
|
|
5824
5939
|
sw ??= w;
|
|
5825
5940
|
sh ??= h;
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
dh *= pd;
|
|
5941
|
+
sx *= pd;
|
|
5942
|
+
sy *= pd;
|
|
5829
5943
|
|
|
5830
5944
|
let [l, r, t, b] = $._calcBox(dx, dy, dw, dh, $._imageMode);
|
|
5831
5945
|
|
|
@@ -5845,8 +5959,55 @@ fn fragmentMain(f: FragmentParams) -> @location(0) vec4f {
|
|
|
5845
5959
|
$.drawStack.push(1, img.textureIndex);
|
|
5846
5960
|
};
|
|
5847
5961
|
|
|
5962
|
+
$._saveCanvas = async (data, ext) => {
|
|
5963
|
+
let texture = data.texture,
|
|
5964
|
+
w = texture.width,
|
|
5965
|
+
h = texture.height,
|
|
5966
|
+
bytesPerRow = Math.ceil((w * 4) / 256) * 256;
|
|
5967
|
+
|
|
5968
|
+
let buffer = Q5.device.createBuffer({
|
|
5969
|
+
size: bytesPerRow * h,
|
|
5970
|
+
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
|
|
5971
|
+
});
|
|
5972
|
+
|
|
5973
|
+
let en = Q5.device.createCommandEncoder();
|
|
5974
|
+
|
|
5975
|
+
en.copyTextureToBuffer({ texture }, { buffer, bytesPerRow, rowsPerImage: h }, { width: w, height: h });
|
|
5976
|
+
|
|
5977
|
+
Q5.device.queue.submit([en.finish()]);
|
|
5978
|
+
|
|
5979
|
+
await buffer.mapAsync(GPUMapMode.READ);
|
|
5980
|
+
|
|
5981
|
+
let pad = new Uint8Array(buffer.getMappedRange());
|
|
5982
|
+
data = new Uint8Array(w * h * 4); // unpadded data
|
|
5983
|
+
|
|
5984
|
+
// Remove padding from each row
|
|
5985
|
+
for (let y = 0; y < h; y++) {
|
|
5986
|
+
const p = y * bytesPerRow; // padded row offset
|
|
5987
|
+
const u = y * w * 4; // unpadded row offset
|
|
5988
|
+
data.set(pad.subarray(p, p + w * 4), u);
|
|
5989
|
+
}
|
|
5990
|
+
|
|
5991
|
+
buffer.unmap();
|
|
5992
|
+
|
|
5993
|
+
let colorSpace = $.canvas.colorSpace;
|
|
5994
|
+
data = new Uint8ClampedArray(data.buffer);
|
|
5995
|
+
data = new ImageData(data, w, h, { colorSpace });
|
|
5996
|
+
let cnv = new OffscreenCanvas(w, h);
|
|
5997
|
+
let ctx = cnv.getContext('2d', { colorSpace });
|
|
5998
|
+
ctx.putImageData(data, 0, 0);
|
|
5999
|
+
|
|
6000
|
+
// Convert to blob then data URL
|
|
6001
|
+
let blob = await cnv.convertToBlob({ type: 'image/' + ext });
|
|
6002
|
+
return await new Promise((resolve) => {
|
|
6003
|
+
let r = new FileReader();
|
|
6004
|
+
r.onloadend = () => resolve(r.result);
|
|
6005
|
+
r.readAsDataURL(blob);
|
|
6006
|
+
});
|
|
6007
|
+
};
|
|
6008
|
+
|
|
5848
6009
|
$._hooks.preRender.push(() => {
|
|
5849
|
-
if (
|
|
6010
|
+
if (!vertIndex) return;
|
|
5850
6011
|
|
|
5851
6012
|
// Switch to image pipeline
|
|
5852
6013
|
$.pass.setPipeline($._pipelines[1]);
|
|
@@ -6387,20 +6548,14 @@ fn fragmentMain(f : FragmentParams) -> @location(0) vec4f {
|
|
|
6387
6548
|
}
|
|
6388
6549
|
|
|
6389
6550
|
let img = $._g.createTextImage(str, w, h);
|
|
6390
|
-
|
|
6391
|
-
if (img.canvas.textureIndex == undefined) {
|
|
6551
|
+
if (img.textureIndex == undefined) {
|
|
6392
6552
|
$._createTexture(img);
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
{ source: cnv },
|
|
6400
|
-
{ texture, colorSpace: $.canvas.colorSpace },
|
|
6401
|
-
textureSize
|
|
6402
|
-
);
|
|
6403
|
-
img.modified = false;
|
|
6553
|
+
let _copy = img.copy;
|
|
6554
|
+
img.copy = function () {
|
|
6555
|
+
let copy = _copy();
|
|
6556
|
+
$._createTexture(copy);
|
|
6557
|
+
return copy;
|
|
6558
|
+
};
|
|
6404
6559
|
}
|
|
6405
6560
|
return img;
|
|
6406
6561
|
};
|