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.
@@ -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 top of your sketch. Explicit use of `createCanvas` is required.
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.