q5 2.1.0 → 2.2.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.
@@ -13,13 +13,12 @@ Q5.renderers.webgpu.canvas = ($, q) => {
13
13
 
14
14
  if ($.colorMode) $.colorMode('rgb', 'float');
15
15
 
16
- let colorsStack;
16
+ let colorsStack, envBindGroup, transformBindGroup;
17
17
 
18
18
  $._createCanvas = (w, h, opt) => {
19
19
  q.ctx = q.drawingContext = c.getContext('webgpu');
20
20
 
21
- $._canvasFormat = navigator.gpu.getPreferredCanvasFormat();
22
- opt.format = $._canvasFormat;
21
+ opt.format = navigator.gpu.getPreferredCanvasFormat();
23
22
  opt.device = Q5.device;
24
23
 
25
24
  $.ctx.configure(opt);
@@ -36,18 +35,153 @@ Q5.renderers.webgpu.canvas = ($, q) => {
36
35
  $.drawStack = [];
37
36
 
38
37
  // colors used for each draw call
39
- colorsStack = $.colorsStack = [];
38
+ colorsStack = $.colorsStack = [1, 1, 1, 1];
40
39
 
41
40
  // current color index, used to associate a vertex with a color
42
- $._colorIndex = -1;
41
+ $._colorIndex = 0;
42
+
43
+ let envLayout = Q5.device.createBindGroupLayout({
44
+ entries: [
45
+ {
46
+ binding: 0,
47
+ visibility: GPUShaderStage.VERTEX,
48
+ buffer: {
49
+ type: 'uniform',
50
+ hasDynamicOffset: false
51
+ }
52
+ }
53
+ ]
54
+ });
55
+
56
+ let transformLayout = Q5.device.createBindGroupLayout({
57
+ entries: [
58
+ {
59
+ binding: 0,
60
+ visibility: GPUShaderStage.VERTEX,
61
+ buffer: {
62
+ type: 'read-only-storage',
63
+ hasDynamicOffset: false
64
+ }
65
+ }
66
+ ]
67
+ });
68
+
69
+ $.bindGroupLayouts = [envLayout, transformLayout];
70
+
71
+ const uniformBuffer = Q5.device.createBuffer({
72
+ size: 8, // Size of two floats
73
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
74
+ });
75
+
76
+ Q5.device.queue.writeBuffer(uniformBuffer, 0, new Float32Array([$.canvas.hw, $.canvas.hh]));
77
+
78
+ envBindGroup = Q5.device.createBindGroup({
79
+ layout: envLayout,
80
+ entries: [
81
+ {
82
+ binding: 0,
83
+ resource: {
84
+ buffer: uniformBuffer
85
+ }
86
+ }
87
+ ]
88
+ });
43
89
  };
44
90
 
45
91
  $._resizeCanvas = (w, h) => {
46
92
  $._setCanvasSize(w, h);
47
93
  };
48
94
 
49
- $.resetMatrix = () => {};
50
- $.translate = () => {};
95
+ $.resetMatrix = () => {
96
+ // Initialize the transformation matrix as 4x4 identity matrix
97
+
98
+ // prettier-ignore
99
+ $._matrix = [
100
+ 1, 0, 0, 0,
101
+ 0, 1, 0, 0,
102
+ 0, 0, 1, 0,
103
+ 0, 0, 0, 1
104
+ ];
105
+ $._transformIndex = 0;
106
+ };
107
+ $.resetMatrix();
108
+
109
+ // Boolean to track if the matrix has been modified
110
+ $._matrixDirty = false;
111
+
112
+ // Array to store transformation matrices for the render pass
113
+ $.transformStates = [$._matrix.slice()];
114
+
115
+ // Stack to keep track of transformation matrix indexes
116
+ $._transformIndexStack = [];
117
+
118
+ $.push = () => {
119
+ // Push the current matrix index onto the stack
120
+ $._transformIndexStack.push($._transformIndex);
121
+ };
122
+
123
+ $.pop = () => {
124
+ if ($._transformIndexStack.length > 0) {
125
+ // Pop the last matrix index from the stack and set it as the current matrix index
126
+ let idx = $._transformIndexStack.pop();
127
+ $._matrix = $.transformStates[idx].slice();
128
+ $._transformIndex = idx;
129
+ } else {
130
+ console.warn('Matrix index stack is empty!');
131
+ }
132
+ };
133
+
134
+ $.translate = (x, y, z) => {
135
+ if (!x && !y && !z) return;
136
+ // Update the translation values
137
+ $._matrix[3] += x;
138
+ $._matrix[7] += y;
139
+ $._matrix[11] += z || 0;
140
+ $._matrixDirty = true;
141
+ };
142
+
143
+ $.rotate = (r) => {
144
+ if (!r) return;
145
+ if ($._angleMode == 'degrees') r = $.radians(r);
146
+
147
+ let cosR = Math.cos(r);
148
+ let sinR = Math.sin(r);
149
+
150
+ let m0 = $._matrix[0],
151
+ m1 = $._matrix[1],
152
+ m4 = $._matrix[4],
153
+ m5 = $._matrix[5];
154
+ if (!m0 && !m1 && !m4 && !m5) {
155
+ $._matrix[0] = cosR;
156
+ $._matrix[1] = sinR;
157
+ $._matrix[4] = -sinR;
158
+ $._matrix[5] = cosR;
159
+ } else {
160
+ $._matrix[0] = m0 * cosR + m4 * sinR;
161
+ $._matrix[1] = m1 * cosR + m5 * sinR;
162
+ $._matrix[4] = m0 * -sinR + m4 * cosR;
163
+ $._matrix[5] = m1 * -sinR + m5 * cosR;
164
+ }
165
+
166
+ $._matrixDirty = true;
167
+ };
168
+
169
+ $.scale = (sx = 1, sy, sz = 1) => {
170
+ sy ??= sx;
171
+
172
+ $._matrix[0] *= sx;
173
+ $._matrix[5] *= sy;
174
+ $._matrix[10] *= sz;
175
+
176
+ $._matrixDirty = true;
177
+ };
178
+
179
+ // Function to save the current matrix state if dirty
180
+ $._saveMatrix = () => {
181
+ $.transformStates.push($._matrix.slice());
182
+ $._transformIndex = $.transformStates.length - 1;
183
+ $._matrixDirty = false;
184
+ };
51
185
 
52
186
  $._beginRender = () => {
53
187
  $.encoder = Q5.device.createCommandEncoder();
@@ -64,12 +198,38 @@ Q5.renderers.webgpu.canvas = ($, q) => {
64
198
  };
65
199
 
66
200
  $._render = () => {
201
+ $.pass.setBindGroup(0, envBindGroup);
202
+
203
+ if (transformStates.length > 1 || !transformBindGroup) {
204
+ const transformBuffer = Q5.device.createBuffer({
205
+ size: transformStates.length * 64, // Size of 16 floats
206
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
207
+ });
208
+
209
+ Q5.device.queue.writeBuffer(transformBuffer, 0, new Float32Array(transformStates.flat()));
210
+
211
+ transformBindGroup = Q5.device.createBindGroup({
212
+ layout: $.bindGroupLayouts[1],
213
+ entries: [
214
+ {
215
+ binding: 0,
216
+ resource: {
217
+ buffer: transformBuffer
218
+ }
219
+ }
220
+ ]
221
+ });
222
+ }
223
+
224
+ $.pass.setBindGroup(1, transformBindGroup);
225
+
67
226
  // run pre-render methods
68
227
  for (let m of $._hooks.preRender) m();
69
228
 
70
229
  $.pass.setPipeline($.pipelines[0]);
71
230
 
72
- let drawStack = $.drawStack; // local variables used for performance
231
+ // local variables used for performance
232
+ let drawStack = $.drawStack;
73
233
  let o = 0; // vertex offset
74
234
  for (let i = 0; i < drawStack.length; i++) {
75
235
  $.pass.draw(drawStack[i], 1, o, 0);
@@ -86,30 +246,41 @@ Q5.renderers.webgpu.canvas = ($, q) => {
86
246
  // clear the stacks for the next frame
87
247
  $.verticesStack.length = 0;
88
248
  $.drawStack.length = 0;
89
- $.colorsStack.length = 0;
249
+ $.colorsStack.length = 4;
90
250
  $.pipelinesStack.length = 0;
91
- $._colorIndex = -1;
251
+ $._colorIndex = 0;
252
+ rotation = 0;
253
+ $.resetMatrix();
254
+ $._matrixDirty = false;
255
+ $.transformStates.length = 1;
256
+ $._transformIndexStack.length = 0;
92
257
  };
93
258
 
94
259
  $.fill = (r, g, b, a = 1) => {
260
+ if (typeof r == 'string') r = Q5.color(r);
95
261
  // grayscale mode `fill(1, 0.5)`
96
262
  if (b == undefined) {
97
263
  a = g;
98
264
  g = b = r;
99
265
  }
100
- let levels;
101
- if (r._q5Color) levels = r.levels;
102
- else levels = [r, g, b, a];
103
-
104
- colorsStack.push(...levels);
266
+ if (r._q5Color) colorsStack.push(...r.levels);
267
+ else colorsStack.push(r, g, b, a);
105
268
  $._colorIndex++;
106
269
  };
270
+ $.noFill = () => colorsStack.push(0, 0, 0, 0);
271
+ $.stroke = () => {};
272
+ $.noStroke = () => {};
273
+
274
+ $.clear = () => {};
107
275
  };
108
276
 
109
277
  Q5.webgpu = async function (scope, parent) {
110
278
  if (!navigator.gpu) {
111
279
  console.error('q5 WebGPU not supported on this browser!');
112
- return new Q5(scope, parent);
280
+ let q = new Q5(scope, parent);
281
+ q.colorMode('rgb', 1);
282
+ q._beginRender = () => q.translate(q.canvas.hw, q.canvas.hh);
283
+ return q;
113
284
  }
114
285
  let adapter = await navigator.gpu.requestAdapter();
115
286
  if (!adapter) throw new Error('No appropriate GPUAdapter found.');
@@ -4,23 +4,31 @@ Q5.renderers.webgpu.drawing = ($, q) => {
4
4
  let verticesStack, drawStack, colorsStack;
5
5
 
6
6
  $._hooks.postCanvas.push(() => {
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
+
7
22
  verticesStack = $.verticesStack;
8
23
  drawStack = $.drawStack;
9
24
  colorsStack = $.colorsStack;
10
25
 
11
26
  let vertexBufferLayout = {
12
- arrayStride: 12, // 2 coordinates + 1 color index * 4 bytes each
27
+ arrayStride: 16, // 2 coordinates + 1 color index + 1 transform index * 4 bytes each
13
28
  attributes: [
14
- {
15
- format: 'float32x2',
16
- offset: 0,
17
- shaderLocation: 0 // position
18
- },
19
- {
20
- format: 'float32',
21
- offset: 8,
22
- shaderLocation: 1 // colorIndex
23
- }
29
+ { format: 'float32x2', offset: 0, shaderLocation: 0 }, // position
30
+ { format: 'float32', offset: 8, shaderLocation: 1 }, // colorIndex
31
+ { format: 'float32', offset: 12, shaderLocation: 2 } // transformIndex
24
32
  ]
25
33
  };
26
34
 
@@ -31,10 +39,23 @@ struct VertexOutput {
31
39
  @location(1) colorIndex: f32
32
40
  };
33
41
 
42
+ struct Uniforms {
43
+ halfWidth: f32,
44
+ halfHeight: f32
45
+ };
46
+
47
+ @group(0) @binding(0) var<uniform> uniforms: Uniforms;
48
+ @group(1) @binding(0) var<storage, read> transforms: array<mat4x4<f32>>;
49
+
34
50
  @vertex
35
- fn vertexMain(@location(0) pos: vec2<f32>, @location(1) colorIndex: f32) -> VertexOutput {
51
+ fn vertexMain(@location(0) pos: vec2<f32>, @location(1) colorIndex: f32, @location(2) transformIndex: f32) -> VertexOutput {
52
+ var vert = vec4<f32>(pos, 0.0, 1.0);
53
+ vert *= transforms[i32(transformIndex)];
54
+ vert.x /= uniforms.halfWidth;
55
+ vert.y /= uniforms.halfHeight;
56
+
36
57
  var output: VertexOutput;
37
- output.position = vec4<f32>(pos, 0.0, 1.0);
58
+ output.position = vert;
38
59
  output.colorIndex = colorIndex;
39
60
  return output;
40
61
  }
@@ -43,7 +64,7 @@ fn vertexMain(@location(0) pos: vec2<f32>, @location(1) colorIndex: f32) -> Vert
43
64
 
44
65
  let fragmentShader = Q5.device.createShaderModule({
45
66
  code: `
46
- @group(0) @binding(0) var<storage, read> uColors : array<vec4<f32>>;
67
+ @group(2) @binding(0) var<storage, read> uColors : array<vec4<f32>>;
47
68
 
48
69
  @fragment
49
70
  fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
@@ -53,45 +74,104 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
53
74
  `
54
75
  });
55
76
 
56
- let bindGroupLayout = Q5.device.createBindGroupLayout({
57
- entries: [
58
- {
59
- binding: 0,
60
- visibility: GPUShaderStage.FRAGMENT,
61
- buffer: {
62
- type: 'read-only-storage',
63
- hasDynamicOffset: false
64
- }
65
- }
66
- ]
67
- });
68
-
69
77
  let pipelineLayout = Q5.device.createPipelineLayout({
70
- bindGroupLayouts: [bindGroupLayout]
78
+ bindGroupLayouts: $.bindGroupLayouts
71
79
  });
72
80
 
73
- $.pipelines[0] = Q5.device.createRenderPipeline({
74
- layout: pipelineLayout,
75
- vertex: {
76
- module: vertexShader,
77
- entryPoint: 'vertexMain',
78
- buffers: [vertexBufferLayout]
79
- },
80
- fragment: {
81
- module: fragmentShader,
82
- entryPoint: 'fragmentMain',
83
- targets: [
84
- {
85
- format: $._canvasFormat
86
- }
87
- ]
81
+ $._createPipeline = (blendConfig) => {
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: [
93
+ {
94
+ format: 'bgra8unorm',
95
+ blend: blendConfig
96
+ }
97
+ ]
98
+ },
99
+ primitive: {
100
+ topology: 'triangle-list'
101
+ }
102
+ });
103
+ };
104
+
105
+ $.pipelines[0] = $._createPipeline(blendConfigs.normal);
106
+ });
107
+
108
+ // prettier-ignore
109
+ let blendFactors = [
110
+ 'zero', // 0
111
+ 'one', // 1
112
+ 'src-alpha', // 2
113
+ 'one-minus-src-alpha', // 3
114
+ 'dst', // 4
115
+ 'dst-alpha', // 5
116
+ 'one-minus-dst-alpha', // 6
117
+ 'one-minus-src' // 7
118
+ ];
119
+ let blendOps = [
120
+ 'add', // 0
121
+ 'subtract', // 1
122
+ 'reverse-subtract', // 2
123
+ 'min', // 3
124
+ 'max' // 4
125
+ ];
126
+
127
+ const blendModes = {
128
+ normal: [2, 3, 0, 2, 3, 0],
129
+ lighter: [2, 1, 0, 2, 1, 0],
130
+ subtract: [2, 1, 2, 2, 1, 2],
131
+ multiply: [4, 0, 0, 5, 0, 0],
132
+ screen: [1, 3, 0, 1, 3, 0],
133
+ darken: [1, 3, 3, 1, 3, 3],
134
+ lighten: [1, 3, 4, 1, 3, 4],
135
+ overlay: [2, 3, 0, 2, 3, 0],
136
+ hard_light: [2, 3, 0, 2, 3, 0],
137
+ soft_light: [2, 3, 0, 2, 3, 0],
138
+ difference: [2, 3, 2, 2, 3, 2],
139
+ exclusion: [2, 3, 0, 2, 3, 0],
140
+ color_dodge: [1, 7, 0, 1, 7, 0],
141
+ color_burn: [6, 1, 0, 6, 1, 0],
142
+ linear_dodge: [2, 1, 0, 2, 1, 0],
143
+ linear_burn: [2, 7, 1, 2, 7, 1],
144
+ vivid_light: [2, 7, 0, 2, 7, 0],
145
+ pin_light: [2, 7, 0, 2, 7, 0],
146
+ hard_mix: [2, 7, 0, 2, 7, 0]
147
+ };
148
+
149
+ $.blendConfigs = {};
150
+
151
+ Object.entries(blendModes).forEach(([name, mode]) => {
152
+ $.blendConfigs[name] = {
153
+ color: {
154
+ srcFactor: blendFactors[mode[0]],
155
+ dstFactor: blendFactors[mode[1]],
156
+ operation: blendOps[mode[2]]
88
157
  },
89
- primitive: {
90
- topology: 'triangle-list'
158
+ alpha: {
159
+ srcFactor: blendFactors[mode[3]],
160
+ dstFactor: blendFactors[mode[4]],
161
+ operation: blendOps[mode[5]]
91
162
  }
92
- });
163
+ };
93
164
  });
94
165
 
166
+ $._blendMode = 'normal';
167
+ $.blendMode = (mode) => {
168
+ if (mode == $._blendMode) return;
169
+ if (mode == 'source-over') mode = 'normal';
170
+ mode = mode.toLowerCase().replace(/[ -]/g, '_');
171
+ $._blendMode = mode;
172
+ $.pipelines[0] = $._createPipeline($.blendConfigs[mode]);
173
+ };
174
+
95
175
  let shapeVertices;
96
176
 
97
177
  $.beginShape = () => {
@@ -99,35 +179,40 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
99
179
  };
100
180
 
101
181
  $.vertex = (x, y) => {
102
- shapeVertices.push(x / $.canvas.hw, -y / $.canvas.hh, $._colorIndex);
182
+ if ($._matrixDirty) $._saveMatrix();
183
+ shapeVertices.push(x, -y, $._colorIndex, $._transformIndex);
103
184
  };
104
185
 
105
186
  $.endShape = (close) => {
106
- if (shapeVertices.length < 6) {
187
+ let v = shapeVertices;
188
+ if (v.length < 12) {
107
189
  throw new Error('A shape must have at least 3 vertices.');
108
190
  }
109
191
  if (close) {
110
192
  // Close the shape by adding the first vertex at the end
111
- shapeVertices.push(shapeVertices[0], shapeVertices[1], shapeVertices[2]);
193
+ v.push(v[0], v[1], v[2], v[3]);
112
194
  }
113
195
  // Convert the shape to triangles
114
196
  let triangles = [];
115
- for (let i = 3; i < shapeVertices.length; i += 3) {
197
+ for (let i = 4; i < v.length; i += 4) {
116
198
  triangles.push(
117
- shapeVertices[0],
118
- shapeVertices[1],
119
- shapeVertices[2], // First vertex
120
- shapeVertices[i - 3],
121
- shapeVertices[i - 2],
122
- shapeVertices[i - 1], // Previous vertex
123
- shapeVertices[i],
124
- shapeVertices[i + 1],
125
- shapeVertices[i + 2] // Current vertex
199
+ v[0], // First vertex
200
+ v[1],
201
+ v[2],
202
+ v[3],
203
+ v[i - 4], // Previous vertex
204
+ v[i - 3],
205
+ v[i - 2],
206
+ v[i - 1],
207
+ v[i], // Current vertex
208
+ v[i + 1],
209
+ v[i + 2],
210
+ v[i + 3]
126
211
  );
127
212
  }
128
213
 
129
214
  verticesStack.push(...triangles);
130
- drawStack.push(triangles.length / 3);
215
+ drawStack.push(triangles.length / 4);
131
216
  shapeVertices = [];
132
217
  };
133
218
 
@@ -142,37 +227,47 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
142
227
  $.rect = (x, y, w, h) => {
143
228
  let hw = w / 2;
144
229
  let hh = h / 2;
145
- // convert the coordinates from pixel space to NDC space
146
- let left = (x - hw) / $.canvas.hw;
147
- let right = (x + hw) / $.canvas.hw;
148
- let top = -(y - hh) / $.canvas.hh; // y is inverted in WebGPU
149
- let bottom = -(y + hh) / $.canvas.hh; // y is inverted in WebGPU
230
+
231
+ let left = x - hw;
232
+ let right = x + hw;
233
+ let top = -(y - hh); // y is inverted in WebGPU
234
+ let bottom = -(y + hh);
150
235
 
151
236
  let ci = $._colorIndex;
237
+ if ($._matrixDirty) $._saveMatrix();
238
+ let ti = $._transformIndex;
152
239
  // two triangles make a rectangle
153
240
  verticesStack.push(
154
241
  left,
155
242
  top,
156
243
  ci,
244
+ ti,
157
245
  right,
158
246
  top,
159
247
  ci,
248
+ ti,
160
249
  left,
161
250
  bottom,
162
251
  ci,
252
+ ti,
163
253
  right,
164
254
  top,
165
255
  ci,
256
+ ti,
166
257
  left,
167
258
  bottom,
168
259
  ci,
260
+ ti,
169
261
  right,
170
262
  bottom,
171
- ci
263
+ ci,
264
+ ti
172
265
  );
173
266
  drawStack.push(6);
174
267
  };
175
268
 
269
+ $.background = () => {};
270
+
176
271
  /**
177
272
  * Derived from: ceil(Math.log(d) * 7) * 2 - ceil(28)
178
273
  * This lookup table is used for better performance.
@@ -215,14 +310,11 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
215
310
  let a = Math.max(w, 1) / 2;
216
311
  let b = w == h ? a : Math.max(h, 1) / 2;
217
312
 
218
- x /= $.canvas.hw;
219
- y /= -$.canvas.hh;
220
- a /= $.canvas.hw;
221
- b /= -$.canvas.hh;
222
-
223
313
  let t = 0; // theta
224
314
  const angleIncrement = $.TAU / n;
225
315
  const ci = $._colorIndex;
316
+ if ($._matrixDirty) $._saveMatrix();
317
+ const ti = $._transformIndex;
226
318
  let vx1, vy1, vx2, vy2;
227
319
  for (let i = 0; i <= n; i++) {
228
320
  vx1 = vx2;
@@ -233,7 +325,7 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
233
325
 
234
326
  if (i == 0) continue;
235
327
 
236
- verticesStack.push(x, y, ci, vx1, vy1, ci, vx2, vy2, ci);
328
+ verticesStack.push(x, y, ci, ti, vx1, vy1, ci, ti, vx2, vy2, ci, ti);
237
329
  }
238
330
 
239
331
  drawStack.push(n * 3);
@@ -241,10 +333,6 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
241
333
 
242
334
  $.circle = (x, y, d) => $.ellipse(x, y, d, d);
243
335
 
244
- $.noStroke = () => {};
245
-
246
- $.background = () => {};
247
-
248
336
  $._hooks.preRender.push(() => {
249
337
  const vertexBuffer = Q5.device.createBuffer({
250
338
  size: verticesStack.length * 6,
@@ -254,20 +342,20 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
254
342
  Q5.device.queue.writeBuffer(vertexBuffer, 0, new Float32Array(verticesStack));
255
343
  $.pass.setVertexBuffer(0, vertexBuffer);
256
344
 
257
- const colorBuffer = Q5.device.createBuffer({
345
+ const colorsBuffer = Q5.device.createBuffer({
258
346
  size: colorsStack.length * 4,
259
347
  usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
260
348
  });
261
349
 
262
- Q5.device.queue.writeBuffer(colorBuffer, 0, new Float32Array(colorsStack));
350
+ Q5.device.queue.writeBuffer(colorsBuffer, 0, new Float32Array(colorsStack));
263
351
 
264
- const bindGroup = Q5.device.createBindGroup({
265
- layout: $.pipelines[0].getBindGroupLayout(0),
352
+ const colorsBindGroup = Q5.device.createBindGroup({
353
+ layout: $.bindGroupLayouts[2],
266
354
  entries: [
267
355
  {
268
356
  binding: 0,
269
357
  resource: {
270
- buffer: colorBuffer,
358
+ buffer: colorsBuffer,
271
359
  offset: 0,
272
360
  size: colorsStack.length * 4
273
361
  }
@@ -276,6 +364,6 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
276
364
  });
277
365
 
278
366
  // set the bind group once before rendering
279
- $.pass.setBindGroup(0, bindGroup);
367
+ $.pass.setBindGroup(2, colorsBindGroup);
280
368
  });
281
369
  };