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/src/q5-webgpu-image.js
CHANGED
|
@@ -1 +1,192 @@
|
|
|
1
|
-
Q5.renderers.webgpu.image = ($, q) => {
|
|
1
|
+
Q5.renderers.webgpu.image = ($, q) => {
|
|
2
|
+
$.imageStack = [];
|
|
3
|
+
$.textures = [];
|
|
4
|
+
|
|
5
|
+
let verticesStack = [];
|
|
6
|
+
let previousTextureCount = 0;
|
|
7
|
+
|
|
8
|
+
$._hooks.postCanvas.push(() => {
|
|
9
|
+
let vertexShader = Q5.device.createShaderModule({
|
|
10
|
+
label: 'imageVertexShader',
|
|
11
|
+
code: `
|
|
12
|
+
struct VertexOutput {
|
|
13
|
+
@builtin(position) position: vec4<f32>,
|
|
14
|
+
@location(0) texCoord: vec2<f32>,
|
|
15
|
+
@location(1) textureIndex: f32
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
struct Uniforms {
|
|
19
|
+
halfWidth: f32,
|
|
20
|
+
halfHeight: f32
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
|
24
|
+
@group(1) @binding(0) var<storage, read> transforms: array<mat4x4<f32>>;
|
|
25
|
+
|
|
26
|
+
@vertex
|
|
27
|
+
fn vertexMain(@location(0) pos: vec2<f32>, @location(1) texCoord: vec2<f32>, @location(2) transformIndex: f32, @location(3) textureIndex: f32) -> VertexOutput {
|
|
28
|
+
var vert = vec4<f32>(pos, 0.0, 1.0);
|
|
29
|
+
vert *= transforms[i32(transformIndex)];
|
|
30
|
+
vert.x /= uniforms.halfWidth;
|
|
31
|
+
vert.y /= uniforms.halfHeight;
|
|
32
|
+
|
|
33
|
+
var output: VertexOutput;
|
|
34
|
+
output.position = vert;
|
|
35
|
+
output.texCoord = texCoord;
|
|
36
|
+
output.textureIndex = textureIndex;
|
|
37
|
+
return output;
|
|
38
|
+
}
|
|
39
|
+
`
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
let fragmentShader = Q5.device.createShaderModule({
|
|
43
|
+
label: 'imageFragmentShader',
|
|
44
|
+
code: `
|
|
45
|
+
@group(0) @binding(0) var samp: sampler;
|
|
46
|
+
@group(0) @binding(1) var textures: array<texture_2d<f32>>;
|
|
47
|
+
|
|
48
|
+
@fragment
|
|
49
|
+
fn fragmentMain(@location(0) texCoord: vec2<f32>, @location(1) textureIndex: f32) -> @location(0) vec4<f32> {
|
|
50
|
+
return textureSample(textures[i32(textureIndex)], samp, texCoord);
|
|
51
|
+
}
|
|
52
|
+
`
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const bindGroupLayouts = [
|
|
56
|
+
Q5.device.createBindGroupLayout({
|
|
57
|
+
entries: [
|
|
58
|
+
{ binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: 'uniform' } },
|
|
59
|
+
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, sampler: {} },
|
|
60
|
+
{ binding: 2, visibility: GPUShaderStage.FRAGMENT, texture: { viewDimension: '2d', sampleType: 'float' } }
|
|
61
|
+
]
|
|
62
|
+
}),
|
|
63
|
+
Q5.device.createBindGroupLayout({
|
|
64
|
+
entries: [{ binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: 'read-only-storage' } }]
|
|
65
|
+
})
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
const pipelineLayout = Q5.device.createPipelineLayout({
|
|
69
|
+
bindGroupLayouts: bindGroupLayouts
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
$.pipelines[1] = Q5.device.createRenderPipeline({
|
|
73
|
+
label: 'imagePipeline',
|
|
74
|
+
layout: pipelineLayout,
|
|
75
|
+
vertex: {
|
|
76
|
+
module: vertexShader,
|
|
77
|
+
entryPoint: 'vertexMain',
|
|
78
|
+
buffers: [
|
|
79
|
+
{
|
|
80
|
+
arrayStride: 5 * 4, // 4 floats for position and texCoord, 1 float for textureIndex
|
|
81
|
+
attributes: [
|
|
82
|
+
{ shaderLocation: 0, offset: 0, format: 'float32x2' },
|
|
83
|
+
{ shaderLocation: 1, offset: 2 * 4, format: 'float32x2' },
|
|
84
|
+
{ shaderLocation: 2, offset: 4 * 4, format: 'float32' } // textureIndex
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
},
|
|
89
|
+
fragment: {
|
|
90
|
+
module: fragmentShader,
|
|
91
|
+
entryPoint: 'fragmentMain',
|
|
92
|
+
targets: [{ format: 'bgra8unorm' }]
|
|
93
|
+
},
|
|
94
|
+
primitive: {
|
|
95
|
+
topology: 'triangle-list'
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
$.sampler = Q5.device.createSampler({
|
|
100
|
+
magFilter: 'linear',
|
|
101
|
+
minFilter: 'linear'
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
$.loadImage = async (src) => {
|
|
106
|
+
const img = new Image();
|
|
107
|
+
img.onload = async () => {
|
|
108
|
+
const imageBitmap = await createImageBitmap(img);
|
|
109
|
+
const texture = Q5.device.createTexture({
|
|
110
|
+
size: [img.width, img.height, 1],
|
|
111
|
+
format: 'bgra8unorm',
|
|
112
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
Q5.device.queue.copyExternalImageToTexture({ source: imageBitmap }, { texture }, [img.width, img.height, 1]);
|
|
116
|
+
|
|
117
|
+
img.texture = texture;
|
|
118
|
+
img.index = $.textures.length;
|
|
119
|
+
$.textures.push(texture);
|
|
120
|
+
};
|
|
121
|
+
img.src = src;
|
|
122
|
+
return img;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
$._hooks.preRender.push(() => {
|
|
126
|
+
if (!$.imageStack.length) return;
|
|
127
|
+
|
|
128
|
+
// Switch to image pipeline
|
|
129
|
+
$.pass.setPipeline($.pipelines[1]);
|
|
130
|
+
|
|
131
|
+
// Create a vertex buffer for the image quads
|
|
132
|
+
const vertices = new Float32Array($.vertexStack);
|
|
133
|
+
|
|
134
|
+
const vertexBuffer = Q5.device.createBuffer({
|
|
135
|
+
size: vertices.byteLength,
|
|
136
|
+
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
Q5.device.queue.writeBuffer(vertexBuffer, 0, vertices);
|
|
140
|
+
$.pass.setVertexBuffer(0, vertexBuffer);
|
|
141
|
+
|
|
142
|
+
// Set the bind group for the sampler and textures
|
|
143
|
+
if ($.textures.length !== previousTextureCount) {
|
|
144
|
+
previousTextureCount = $.textures.length;
|
|
145
|
+
|
|
146
|
+
// Create the bind group for all textures
|
|
147
|
+
const textureViews = $.textures.map((tex) => tex.createView());
|
|
148
|
+
|
|
149
|
+
$.textureBindGroup = Q5.device.createBindGroup({
|
|
150
|
+
layout: $.pipelines[1].getBindGroupLayout(0),
|
|
151
|
+
entries: [
|
|
152
|
+
{ binding: 0, resource: $.sampler },
|
|
153
|
+
...textureViews.map((view, i) => ({ binding: i + 1, resource: view }))
|
|
154
|
+
]
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Set the bind group for the sampler and textures
|
|
159
|
+
$.pass.setBindGroup(0, $.textureBindGroup);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
$.image = (img, x, y, w, h) => {
|
|
163
|
+
if ($._matrixDirty) $._saveMatrix();
|
|
164
|
+
let ti = $._transformIndex;
|
|
165
|
+
|
|
166
|
+
$.imageStack.push(img.index);
|
|
167
|
+
|
|
168
|
+
// Calculate half-width and half-height
|
|
169
|
+
let hw = w / 2;
|
|
170
|
+
let hh = h / 2;
|
|
171
|
+
|
|
172
|
+
// Calculate vertices positions
|
|
173
|
+
let left = x - hw;
|
|
174
|
+
let right = x + hw;
|
|
175
|
+
let top = -(y - hh); // y is inverted in WebGPU
|
|
176
|
+
let bottom = -(y + hh);
|
|
177
|
+
|
|
178
|
+
let ii = img.index;
|
|
179
|
+
|
|
180
|
+
// prettier-ignore
|
|
181
|
+
verticesStack.push(
|
|
182
|
+
left, top, 0, 0, ti, ii,
|
|
183
|
+
right, top, 1, 0, ti, ii,
|
|
184
|
+
left, bottom, 0, 1, ti, ii,
|
|
185
|
+
right, top, 1, 0, ti, ii,
|
|
186
|
+
left, bottom, 0, 1, ti, ii,
|
|
187
|
+
right, bottom, 1, 1, ti, ii
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
$.drawStack.push(1, 6);
|
|
191
|
+
};
|
|
192
|
+
};
|
package/src/readme.md
CHANGED
|
@@ -109,11 +109,9 @@ Image based features in this module require the q5-2d-image module.
|
|
|
109
109
|
|
|
110
110
|
> ⚠️ Experimental features! ⚠️
|
|
111
111
|
|
|
112
|
-
To use q5's WebGPU renderer, run `Q5.webgpu()` at the
|
|
112
|
+
To use q5's WebGPU renderer, run `Q5.webgpu()` at the bottom of your sketch. Explicit use of `createCanvas` is required.
|
|
113
113
|
|
|
114
114
|
```js
|
|
115
|
-
Q5.webgpu();
|
|
116
|
-
|
|
117
115
|
function setup() {
|
|
118
116
|
createCanvas(200, 200);
|
|
119
117
|
noStroke();
|
|
@@ -123,6 +121,8 @@ function draw() {
|
|
|
123
121
|
clear();
|
|
124
122
|
rect(50, 50, 100, 100);
|
|
125
123
|
}
|
|
124
|
+
|
|
125
|
+
Q5.webgpu();
|
|
126
126
|
```
|
|
127
127
|
|
|
128
128
|
For now, be sure to set `noStroke` in your setup code and `clear` the canvas at the start of your `draw` function to match current q5 webgpu limitations.
|