q5 2.2.3 → 2.4.1
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 +8 -2
- package/package.json +3 -4
- package/q5.js +633 -447
- package/q5.min.js +2 -2
- package/src/q5-2d-canvas.js +9 -14
- package/src/q5-2d-drawing.js +3 -45
- package/src/q5-2d-image.js +8 -4
- package/src/q5-2d-text.js +4 -14
- package/src/q5-ai.js +4 -5
- package/src/q5-canvas.js +63 -2
- package/src/q5-core.js +4 -3
- package/src/q5-display.js +6 -0
- package/src/q5-math.js +27 -20
- package/src/q5-vector.js +57 -10
- package/src/q5-webgpu-canvas.js +132 -101
- package/src/q5-webgpu-drawing.js +120 -85
- package/src/q5-webgpu-image.js +161 -141
- package/src/q5-webgpu-text.js +38 -1
- package/src/readme.md +37 -6
- package/q5-q2d.js +0 -3085
- package/q5-q2d.min.js +0 -8
package/src/q5-webgpu-drawing.js
CHANGED
|
@@ -1,39 +1,16 @@
|
|
|
1
1
|
Q5.renderers.webgpu.drawing = ($, q) => {
|
|
2
|
-
|
|
2
|
+
let c = $.canvas;
|
|
3
3
|
|
|
4
|
-
let
|
|
4
|
+
let drawStack = $.drawStack;
|
|
5
|
+
let colorsStack = $.colorsStack;
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
let colorsLayout = Q5.device.createBindGroupLayout({
|
|
8
|
-
entries: [
|
|
9
|
-
{
|
|
10
|
-
binding: 0,
|
|
11
|
-
visibility: GPUShaderStage.FRAGMENT,
|
|
12
|
-
buffer: {
|
|
13
|
-
type: 'read-only-storage',
|
|
14
|
-
hasDynamicOffset: false
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
]
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
$.bindGroupLayouts.push(colorsLayout);
|
|
21
|
-
|
|
22
|
-
verticesStack = $.verticesStack;
|
|
23
|
-
drawStack = $.drawStack;
|
|
24
|
-
colorsStack = $.colorsStack;
|
|
7
|
+
let verticesStack = [];
|
|
25
8
|
|
|
26
|
-
|
|
27
|
-
arrayStride: 16, // 2 coordinates + 1 color index + 1 transform index * 4 bytes each
|
|
28
|
-
attributes: [
|
|
29
|
-
{ format: 'float32x2', offset: 0, shaderLocation: 0 }, // position
|
|
30
|
-
{ format: 'float32', offset: 8, shaderLocation: 1 }, // colorIndex
|
|
31
|
-
{ format: 'float32', offset: 12, shaderLocation: 2 } // transformIndex
|
|
32
|
-
]
|
|
33
|
-
};
|
|
9
|
+
let colorIndex, colorsLayout;
|
|
34
10
|
|
|
35
|
-
|
|
36
|
-
|
|
11
|
+
let vertexShader = Q5.device.createShaderModule({
|
|
12
|
+
label: 'drawingVertexShader',
|
|
13
|
+
code: `
|
|
37
14
|
struct VertexOutput {
|
|
38
15
|
@builtin(position) position: vec4<f32>,
|
|
39
16
|
@location(1) colorIndex: f32
|
|
@@ -60,10 +37,11 @@ fn vertexMain(@location(0) pos: vec2<f32>, @location(1) colorIndex: f32, @locati
|
|
|
60
37
|
return output;
|
|
61
38
|
}
|
|
62
39
|
`
|
|
63
|
-
|
|
40
|
+
});
|
|
64
41
|
|
|
65
|
-
|
|
66
|
-
|
|
42
|
+
let fragmentShader = Q5.device.createShaderModule({
|
|
43
|
+
label: 'drawingFragmentShader',
|
|
44
|
+
code: `
|
|
67
45
|
@group(2) @binding(0) var<storage, read> uColors : array<vec4<f32>>;
|
|
68
46
|
|
|
69
47
|
@fragment
|
|
@@ -72,31 +50,31 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
|
72
50
|
return mix(uColors[index], uColors[index + 1u], fract(colorIndex));
|
|
73
51
|
}
|
|
74
52
|
`
|
|
75
|
-
|
|
53
|
+
});
|
|
76
54
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
55
|
+
colorsLayout = Q5.device.createBindGroupLayout({
|
|
56
|
+
entries: [
|
|
57
|
+
{
|
|
58
|
+
binding: 0,
|
|
59
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
60
|
+
buffer: {
|
|
61
|
+
type: 'read-only-storage',
|
|
62
|
+
hasDynamicOffset: false
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
});
|
|
80
67
|
|
|
81
|
-
|
|
82
|
-
return Q5.device.createRenderPipeline({
|
|
83
|
-
layout: pipelineLayout,
|
|
84
|
-
vertex: {
|
|
85
|
-
module: vertexShader,
|
|
86
|
-
entryPoint: 'vertexMain',
|
|
87
|
-
buffers: [vertexBufferLayout]
|
|
88
|
-
},
|
|
89
|
-
fragment: {
|
|
90
|
-
module: fragmentShader,
|
|
91
|
-
entryPoint: 'fragmentMain',
|
|
92
|
-
targets: [{ format: 'bgra8unorm', blend: blendConfig }]
|
|
93
|
-
},
|
|
94
|
-
primitive: { topology: 'triangle-list' }
|
|
95
|
-
});
|
|
96
|
-
};
|
|
68
|
+
$.bindGroupLayouts.push(colorsLayout);
|
|
97
69
|
|
|
98
|
-
|
|
99
|
-
|
|
70
|
+
let vertexBufferLayout = {
|
|
71
|
+
arrayStride: 16, // 2 coordinates + 1 color index + 1 transform index * 4 bytes each
|
|
72
|
+
attributes: [
|
|
73
|
+
{ format: 'float32x2', offset: 0, shaderLocation: 0 }, // position
|
|
74
|
+
{ format: 'float32', offset: 8, shaderLocation: 1 }, // colorIndex
|
|
75
|
+
{ format: 'float32', offset: 12, shaderLocation: 2 } // transformIndex
|
|
76
|
+
]
|
|
77
|
+
};
|
|
100
78
|
|
|
101
79
|
// prettier-ignore
|
|
102
80
|
let blendFactors = [
|
|
@@ -141,7 +119,7 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
|
141
119
|
|
|
142
120
|
$.blendConfigs = {};
|
|
143
121
|
|
|
144
|
-
|
|
122
|
+
for (const [name, mode] of Object.entries(blendModes)) {
|
|
145
123
|
$.blendConfigs[name] = {
|
|
146
124
|
color: {
|
|
147
125
|
srcFactor: blendFactors[mode[0]],
|
|
@@ -154,7 +132,7 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
|
154
132
|
operation: blendOps[mode[5]]
|
|
155
133
|
}
|
|
156
134
|
};
|
|
157
|
-
}
|
|
135
|
+
}
|
|
158
136
|
|
|
159
137
|
$._blendMode = 'normal';
|
|
160
138
|
$.blendMode = (mode) => {
|
|
@@ -165,6 +143,31 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
|
165
143
|
$.pipelines[0] = $._createPipeline($.blendConfigs[mode]);
|
|
166
144
|
};
|
|
167
145
|
|
|
146
|
+
let pipelineLayout = Q5.device.createPipelineLayout({
|
|
147
|
+
label: 'drawingPipelineLayout',
|
|
148
|
+
bindGroupLayouts: $.bindGroupLayouts
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
$._createPipeline = (blendConfig) => {
|
|
152
|
+
return Q5.device.createRenderPipeline({
|
|
153
|
+
label: 'drawingPipeline',
|
|
154
|
+
layout: pipelineLayout,
|
|
155
|
+
vertex: {
|
|
156
|
+
module: vertexShader,
|
|
157
|
+
entryPoint: 'vertexMain',
|
|
158
|
+
buffers: [vertexBufferLayout]
|
|
159
|
+
},
|
|
160
|
+
fragment: {
|
|
161
|
+
module: fragmentShader,
|
|
162
|
+
entryPoint: 'fragmentMain',
|
|
163
|
+
targets: [{ format: 'bgra8unorm', blend: blendConfig }]
|
|
164
|
+
},
|
|
165
|
+
primitive: { topology: 'triangle-list' }
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
$.pipelines[0] = $._createPipeline($.blendConfigs.normal);
|
|
170
|
+
|
|
168
171
|
let shapeVertices;
|
|
169
172
|
|
|
170
173
|
$.beginShape = () => {
|
|
@@ -173,7 +176,7 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
|
173
176
|
|
|
174
177
|
$.vertex = (x, y) => {
|
|
175
178
|
if ($._matrixDirty) $._saveMatrix();
|
|
176
|
-
shapeVertices.push(x, -y, $.
|
|
179
|
+
shapeVertices.push(x, -y, $._fillIndex, $._transformIndex);
|
|
177
180
|
};
|
|
178
181
|
|
|
179
182
|
$.endShape = (close) => {
|
|
@@ -217,38 +220,61 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
|
217
220
|
$.endShape(1);
|
|
218
221
|
};
|
|
219
222
|
|
|
220
|
-
$.
|
|
221
|
-
let hw = w / 2;
|
|
222
|
-
let hh = h / 2;
|
|
223
|
+
$.rectMode = (x) => ($._rectMode = x);
|
|
223
224
|
|
|
224
|
-
|
|
225
|
-
let
|
|
226
|
-
let top = -(y - hh); // y is inverted in WebGPU
|
|
227
|
-
let bottom = -(y + hh);
|
|
225
|
+
$.rect = (x, y, w, h) => {
|
|
226
|
+
let [l, r, t, b] = $._calcBox(x, y, w, h, $._rectMode);
|
|
228
227
|
|
|
229
|
-
let ci = $.
|
|
228
|
+
let ci = colorIndex ?? $._fillIndex;
|
|
230
229
|
if ($._matrixDirty) $._saveMatrix();
|
|
231
230
|
let ti = $._transformIndex;
|
|
232
231
|
// two triangles make a rectangle
|
|
233
232
|
// prettier-ignore
|
|
234
233
|
verticesStack.push(
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
234
|
+
l, t, ci, ti,
|
|
235
|
+
r, t, ci, ti,
|
|
236
|
+
l, b, ci, ti,
|
|
237
|
+
r, t, ci, ti,
|
|
238
|
+
l, b, ci, ti,
|
|
239
|
+
r, b, ci, ti
|
|
241
240
|
);
|
|
242
241
|
drawStack.push(0, 6);
|
|
243
242
|
};
|
|
244
243
|
|
|
245
244
|
$.point = (x, y) => {
|
|
245
|
+
colorIndex = $._strokeIndex;
|
|
246
246
|
let sw = $._strokeWeight;
|
|
247
|
-
if (sw
|
|
248
|
-
|
|
247
|
+
if (sw < 2) {
|
|
248
|
+
sw = Math.round(sw);
|
|
249
|
+
$.rect(x, y, sw, sw);
|
|
250
|
+
} else $.ellipse(x, y, sw, sw);
|
|
251
|
+
colorIndex = null;
|
|
249
252
|
};
|
|
250
253
|
|
|
251
|
-
$.
|
|
254
|
+
$.line = (x1, y1, x2, y2) => {
|
|
255
|
+
colorIndex = $._strokeIndex;
|
|
256
|
+
|
|
257
|
+
$.push();
|
|
258
|
+
$.translate(x1, y1);
|
|
259
|
+
$.rotate($.atan2(y2 - y1, x2 - x1));
|
|
260
|
+
let length = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
|
|
261
|
+
let sw = $._strokeWeight;
|
|
262
|
+
$.rect(0, -sw / 2, length, sw);
|
|
263
|
+
$.pop();
|
|
264
|
+
|
|
265
|
+
colorIndex = null;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
$.background = (r, g, b, a) => {
|
|
269
|
+
$.push();
|
|
270
|
+
$.resetMatrix();
|
|
271
|
+
if (r.src) $.image(r, -c.hw, -c.hh, c.w, c.h);
|
|
272
|
+
else {
|
|
273
|
+
$.fill(r, g, b, a);
|
|
274
|
+
$.rect(-c.hw, -c.hh, c.w, c.h);
|
|
275
|
+
}
|
|
276
|
+
$.pop();
|
|
277
|
+
};
|
|
252
278
|
|
|
253
279
|
/**
|
|
254
280
|
* Derived from: ceil(Math.log(d) * 7) * 2 - ceil(28)
|
|
@@ -258,9 +284,10 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
|
258
284
|
*/
|
|
259
285
|
// prettier-ignore
|
|
260
286
|
const getArcSegments = (d) =>
|
|
261
|
-
|
|
262
|
-
d <
|
|
263
|
-
d <
|
|
287
|
+
d < 4 ? 6 :
|
|
288
|
+
d < 6 ? 8 :
|
|
289
|
+
d < 10 ? 10 :
|
|
290
|
+
d < 16 ? 12 :
|
|
264
291
|
d < 20 ? 14 :
|
|
265
292
|
d < 22 ? 16 :
|
|
266
293
|
d < 24 ? 18 :
|
|
@@ -294,7 +321,7 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
|
294
321
|
|
|
295
322
|
let t = 0; // theta
|
|
296
323
|
const angleIncrement = $.TAU / n;
|
|
297
|
-
const ci = $.
|
|
324
|
+
const ci = colorIndex ?? $._fillIndex;
|
|
298
325
|
if ($._matrixDirty) $._saveMatrix();
|
|
299
326
|
const ti = $._transformIndex;
|
|
300
327
|
let vx1, vy1, vx2, vy2;
|
|
@@ -316,12 +343,16 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
|
316
343
|
$.circle = (x, y, d) => $.ellipse(x, y, d, d);
|
|
317
344
|
|
|
318
345
|
$._hooks.preRender.push(() => {
|
|
346
|
+
$.pass.setPipeline($.pipelines[0]);
|
|
347
|
+
|
|
348
|
+
const vertices = new Float32Array(verticesStack);
|
|
349
|
+
|
|
319
350
|
const vertexBuffer = Q5.device.createBuffer({
|
|
320
|
-
size:
|
|
351
|
+
size: vertices.byteLength,
|
|
321
352
|
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
|
|
322
353
|
});
|
|
323
354
|
|
|
324
|
-
Q5.device.queue.writeBuffer(vertexBuffer, 0,
|
|
355
|
+
Q5.device.queue.writeBuffer(vertexBuffer, 0, vertices);
|
|
325
356
|
$.pass.setVertexBuffer(0, vertexBuffer);
|
|
326
357
|
|
|
327
358
|
const colorsBuffer = Q5.device.createBuffer({
|
|
@@ -331,8 +362,8 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
|
331
362
|
|
|
332
363
|
Q5.device.queue.writeBuffer(colorsBuffer, 0, new Float32Array(colorsStack));
|
|
333
364
|
|
|
334
|
-
|
|
335
|
-
layout:
|
|
365
|
+
$._colorsBindGroup = Q5.device.createBindGroup({
|
|
366
|
+
layout: colorsLayout,
|
|
336
367
|
entries: [
|
|
337
368
|
{
|
|
338
369
|
binding: 0,
|
|
@@ -346,6 +377,10 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
|
|
|
346
377
|
});
|
|
347
378
|
|
|
348
379
|
// set the bind group once before rendering
|
|
349
|
-
$.pass.setBindGroup(2,
|
|
380
|
+
$.pass.setBindGroup(2, $._colorsBindGroup);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
$._hooks.postRender.push(() => {
|
|
384
|
+
verticesStack.length = 0;
|
|
350
385
|
});
|
|
351
386
|
};
|
package/src/q5-webgpu-image.js
CHANGED
|
@@ -1,130 +1,196 @@
|
|
|
1
1
|
Q5.renderers.webgpu.image = ($, q) => {
|
|
2
|
-
$.
|
|
3
|
-
|
|
2
|
+
$._textureBindGroups = [];
|
|
3
|
+
let verticesStack = [];
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
let vertexShader = Q5.device.createShaderModule({
|
|
6
|
+
label: 'imageVertexShader',
|
|
7
|
+
code: `
|
|
8
8
|
struct VertexOutput {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@location(1) textureIndex: f32
|
|
9
|
+
@builtin(position) position: vec4<f32>,
|
|
10
|
+
@location(0) texCoord: vec2<f32>
|
|
12
11
|
};
|
|
13
12
|
|
|
14
13
|
struct Uniforms {
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
halfWidth: f32,
|
|
15
|
+
halfHeight: f32
|
|
17
16
|
};
|
|
18
17
|
|
|
19
18
|
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
|
20
19
|
@group(1) @binding(0) var<storage, read> transforms: array<mat4x4<f32>>;
|
|
21
20
|
|
|
22
21
|
@vertex
|
|
23
|
-
fn vertexMain(@location(0) pos: vec2<f32>, @location(1) texCoord: vec2<f32>, @location(2) transformIndex: f32
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return output;
|
|
22
|
+
fn vertexMain(@location(0) pos: vec2<f32>, @location(1) texCoord: vec2<f32>, @location(2) transformIndex: f32) -> VertexOutput {
|
|
23
|
+
var vert = vec4<f32>(pos, 0.0, 1.0);
|
|
24
|
+
vert *= transforms[i32(transformIndex)];
|
|
25
|
+
vert.x /= uniforms.halfWidth;
|
|
26
|
+
vert.y /= uniforms.halfHeight;
|
|
27
|
+
|
|
28
|
+
var output: VertexOutput;
|
|
29
|
+
output.position = vert;
|
|
30
|
+
output.texCoord = texCoord;
|
|
31
|
+
return output;
|
|
34
32
|
}
|
|
35
|
-
`
|
|
36
|
-
|
|
33
|
+
`
|
|
34
|
+
});
|
|
37
35
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
@group(
|
|
36
|
+
let fragmentShader = Q5.device.createShaderModule({
|
|
37
|
+
label: 'imageFragmentShader',
|
|
38
|
+
code: `
|
|
39
|
+
@group(3) @binding(0) var samp: sampler;
|
|
40
|
+
@group(3) @binding(1) var texture: texture_2d<f32>;
|
|
42
41
|
|
|
43
42
|
@fragment
|
|
44
|
-
fn fragmentMain(@location(0) texCoord: vec2<f32
|
|
45
|
-
|
|
43
|
+
fn fragmentMain(@location(0) texCoord: vec2<f32>) -> @location(0) vec4<f32> {
|
|
44
|
+
// Sample the texture using the interpolated texture coordinate
|
|
45
|
+
return textureSample(texture, samp, texCoord);
|
|
46
46
|
}
|
|
47
|
-
`
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const bindGroupLayouts = [
|
|
51
|
-
Q5.device.createBindGroupLayout({
|
|
52
|
-
entries: [
|
|
53
|
-
{ binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: 'uniform' } },
|
|
54
|
-
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, sampler: {} },
|
|
55
|
-
{ binding: 2, visibility: GPUShaderStage.FRAGMENT, texture: { viewDimension: '2d', sampleType: 'float' } }
|
|
56
|
-
]
|
|
57
|
-
}),
|
|
58
|
-
Q5.device.createBindGroupLayout({
|
|
59
|
-
entries: [{ binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: 'read-only-storage' } }]
|
|
60
|
-
})
|
|
61
|
-
];
|
|
62
|
-
|
|
63
|
-
const pipelineLayout = Q5.device.createPipelineLayout({
|
|
64
|
-
bindGroupLayouts: bindGroupLayouts
|
|
65
|
-
});
|
|
47
|
+
`
|
|
48
|
+
});
|
|
66
49
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
arrayStride: 5 * 4, // 4 floats for position and texCoord, 1 float for textureIndex
|
|
75
|
-
attributes: [
|
|
76
|
-
{ shaderLocation: 0, offset: 0, format: 'float32x2' },
|
|
77
|
-
{ shaderLocation: 1, offset: 2 * 4, format: 'float32x2' },
|
|
78
|
-
{ shaderLocation: 2, offset: 4 * 4, format: 'float32' } // textureIndex
|
|
79
|
-
]
|
|
80
|
-
}
|
|
81
|
-
]
|
|
50
|
+
let textureLayout = Q5.device.createBindGroupLayout({
|
|
51
|
+
label: 'textureLayout',
|
|
52
|
+
entries: [
|
|
53
|
+
{
|
|
54
|
+
binding: 0,
|
|
55
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
56
|
+
sampler: { type: 'filtering' }
|
|
82
57
|
},
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
},
|
|
88
|
-
primitive: {
|
|
89
|
-
topology: 'triangle-list'
|
|
58
|
+
{
|
|
59
|
+
binding: 1,
|
|
60
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
61
|
+
texture: { viewDimension: '2d', sampleType: 'float' }
|
|
90
62
|
}
|
|
63
|
+
]
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const vertexBufferLayout = {
|
|
67
|
+
arrayStride: 20,
|
|
68
|
+
attributes: [
|
|
69
|
+
{ shaderLocation: 0, offset: 0, format: 'float32x2' },
|
|
70
|
+
{ shaderLocation: 1, offset: 8, format: 'float32x2' },
|
|
71
|
+
{ shaderLocation: 2, offset: 16, format: 'float32' } // transformIndex
|
|
72
|
+
]
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
$.bindGroupLayouts.push(textureLayout);
|
|
76
|
+
|
|
77
|
+
const pipelineLayout = Q5.device.createPipelineLayout({
|
|
78
|
+
label: 'imagePipelineLayout',
|
|
79
|
+
bindGroupLayouts: $.bindGroupLayouts
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
$.pipelines[1] = Q5.device.createRenderPipeline({
|
|
83
|
+
label: 'imagePipeline',
|
|
84
|
+
layout: pipelineLayout,
|
|
85
|
+
vertex: {
|
|
86
|
+
module: vertexShader,
|
|
87
|
+
entryPoint: 'vertexMain',
|
|
88
|
+
buffers: [{ arrayStride: 0, attributes: [] }, vertexBufferLayout]
|
|
89
|
+
},
|
|
90
|
+
fragment: {
|
|
91
|
+
module: fragmentShader,
|
|
92
|
+
entryPoint: 'fragmentMain',
|
|
93
|
+
targets: [
|
|
94
|
+
{
|
|
95
|
+
format: 'bgra8unorm',
|
|
96
|
+
blend: $.blendConfigs?.normal || {
|
|
97
|
+
color: {
|
|
98
|
+
srcFactor: 'src-alpha',
|
|
99
|
+
dstFactor: 'one-minus-src-alpha',
|
|
100
|
+
operation: 'add'
|
|
101
|
+
},
|
|
102
|
+
alpha: {
|
|
103
|
+
srcFactor: 'src-alpha',
|
|
104
|
+
dstFactor: 'one-minus-src-alpha',
|
|
105
|
+
operation: 'add'
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
]
|
|
110
|
+
},
|
|
111
|
+
primitive: {
|
|
112
|
+
topology: 'triangle-list'
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
let sampler = Q5.device.createSampler({
|
|
117
|
+
magFilter: 'linear',
|
|
118
|
+
minFilter: 'linear'
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
$._createTexture = (img) => {
|
|
122
|
+
let textureSize = [img.width, img.height, 1];
|
|
123
|
+
|
|
124
|
+
const texture = Q5.device.createTexture({
|
|
125
|
+
size: textureSize,
|
|
126
|
+
format: 'bgra8unorm',
|
|
127
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
|
|
91
128
|
});
|
|
92
129
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
130
|
+
Q5.device.queue.copyExternalImageToTexture({ source: img }, { texture }, textureSize);
|
|
131
|
+
|
|
132
|
+
img.textureIndex = $._textureBindGroups.length;
|
|
133
|
+
|
|
134
|
+
const textureBindGroup = Q5.device.createBindGroup({
|
|
135
|
+
layout: textureLayout,
|
|
136
|
+
entries: [
|
|
137
|
+
{ binding: 0, resource: sampler },
|
|
138
|
+
{ binding: 1, resource: texture.createView() }
|
|
139
|
+
]
|
|
96
140
|
});
|
|
97
|
-
|
|
141
|
+
$._textureBindGroups.push(textureBindGroup);
|
|
142
|
+
};
|
|
98
143
|
|
|
99
|
-
$.loadImage =
|
|
144
|
+
$.loadImage = $.loadTexture = (src) => {
|
|
145
|
+
q._preloadCount++;
|
|
100
146
|
const img = new Image();
|
|
101
|
-
img.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
format: 'bgra8unorm',
|
|
106
|
-
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
Q5.device.queue.copyExternalImageToTexture({ source: imageBitmap }, { texture }, [img.width, img.height, 1]);
|
|
110
|
-
|
|
111
|
-
img.texture = texture;
|
|
112
|
-
img.index = $.textures.length;
|
|
113
|
-
$.textures.push(texture);
|
|
147
|
+
img.crossOrigin = 'Anonymous';
|
|
148
|
+
img.onload = () => {
|
|
149
|
+
$._createTexture(img);
|
|
150
|
+
q._preloadCount--;
|
|
114
151
|
};
|
|
115
|
-
img.onerror = reject;
|
|
116
152
|
img.src = src;
|
|
117
153
|
return img;
|
|
118
154
|
};
|
|
119
155
|
|
|
156
|
+
$.imageMode = (x) => ($._imageMode = x);
|
|
157
|
+
|
|
158
|
+
$.image = (img, x, y, w, h) => {
|
|
159
|
+
if (img.canvas) img = img.canvas;
|
|
160
|
+
if (img.textureIndex == undefined) return;
|
|
161
|
+
|
|
162
|
+
if ($._matrixDirty) $._saveMatrix();
|
|
163
|
+
let ti = $._transformIndex;
|
|
164
|
+
|
|
165
|
+
w ??= img.width;
|
|
166
|
+
h ??= img.height;
|
|
167
|
+
|
|
168
|
+
w /= $._pixelDensity;
|
|
169
|
+
h /= $._pixelDensity;
|
|
170
|
+
|
|
171
|
+
let [l, r, t, b] = $._calcBox(x, y, w, h, $._imageMode);
|
|
172
|
+
|
|
173
|
+
// prettier-ignore
|
|
174
|
+
verticesStack.push(
|
|
175
|
+
l, t, 0, 0, ti,
|
|
176
|
+
r, t, 1, 0, ti,
|
|
177
|
+
l, b, 0, 1, ti,
|
|
178
|
+
r, t, 1, 0, ti,
|
|
179
|
+
l, b, 0, 1, ti,
|
|
180
|
+
r, b, 1, 1, ti
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
$.drawStack.push(1, img.textureIndex);
|
|
184
|
+
};
|
|
185
|
+
|
|
120
186
|
$._hooks.preRender.push(() => {
|
|
121
|
-
if (!$.
|
|
187
|
+
if (!$._textureBindGroups.length) return;
|
|
122
188
|
|
|
123
189
|
// Switch to image pipeline
|
|
124
190
|
$.pass.setPipeline($.pipelines[1]);
|
|
125
191
|
|
|
126
192
|
// Create a vertex buffer for the image quads
|
|
127
|
-
const vertices = new Float32Array(
|
|
193
|
+
const vertices = new Float32Array(verticesStack);
|
|
128
194
|
|
|
129
195
|
const vertexBuffer = Q5.device.createBuffer({
|
|
130
196
|
size: vertices.byteLength,
|
|
@@ -132,56 +198,10 @@ fn fragmentMain(@location(0) texCoord: vec2<f32>, @location(1) textureIndex: f32
|
|
|
132
198
|
});
|
|
133
199
|
|
|
134
200
|
Q5.device.queue.writeBuffer(vertexBuffer, 0, vertices);
|
|
135
|
-
$.pass.setVertexBuffer(
|
|
136
|
-
|
|
137
|
-
// Set the bind group for the sampler and textures
|
|
138
|
-
if ($.textures.length !== previousTextureCount) {
|
|
139
|
-
previousTextureCount = $.textures.length;
|
|
140
|
-
|
|
141
|
-
// Create the bind group for all textures
|
|
142
|
-
const textureViews = $.textures.map((tex) => tex.createView());
|
|
143
|
-
|
|
144
|
-
$.textureBindGroup = Q5.device.createBindGroup({
|
|
145
|
-
layout: $.pipelines[1].getBindGroupLayout(0),
|
|
146
|
-
entries: [
|
|
147
|
-
{ binding: 0, resource: $.sampler },
|
|
148
|
-
...textureViews.map((view, i) => ({ binding: i + 1, resource: view }))
|
|
149
|
-
]
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Set the bind group for the sampler and textures
|
|
154
|
-
$.pass.setBindGroup(0, $.textureBindGroup);
|
|
201
|
+
$.pass.setVertexBuffer(1, vertexBuffer);
|
|
155
202
|
});
|
|
156
203
|
|
|
157
|
-
$.
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
$.imageStack.push(img.index);
|
|
162
|
-
|
|
163
|
-
// Calculate half-width and half-height
|
|
164
|
-
let hw = w / 2;
|
|
165
|
-
let hh = h / 2;
|
|
166
|
-
|
|
167
|
-
// Calculate vertices positions
|
|
168
|
-
let left = x - hw;
|
|
169
|
-
let right = x + hw;
|
|
170
|
-
let top = -(y - hh); // y is inverted in WebGPU
|
|
171
|
-
let bottom = -(y + hh);
|
|
172
|
-
|
|
173
|
-
let ii = img.index;
|
|
174
|
-
|
|
175
|
-
// prettier-ignore
|
|
176
|
-
$.vertexStack.push(
|
|
177
|
-
left, top, 0, 0, ti, ii,
|
|
178
|
-
right, top, 1, 0, ti, ii,
|
|
179
|
-
left, bottom, 0, 1, ti, ii,
|
|
180
|
-
right, top, 1, 0, ti, ii,
|
|
181
|
-
left, bottom, 0, 1, ti, ii,
|
|
182
|
-
right, bottom, 1, 1, ti, ii
|
|
183
|
-
);
|
|
184
|
-
|
|
185
|
-
$.drawStack.push(6);
|
|
186
|
-
};
|
|
204
|
+
$._hooks.postRender.push(() => {
|
|
205
|
+
verticesStack.length = 0;
|
|
206
|
+
});
|
|
187
207
|
};
|