q5 2.4.5 → 2.5.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.
@@ -24,7 +24,8 @@ Q5.renderers.webgpu.canvas = ($, q) => {
24
24
  // colors used for each draw call
25
25
  let colorsStack = ($.colorsStack = [1, 1, 1, 1]);
26
26
 
27
- $._envLayout = Q5.device.createBindGroupLayout({
27
+ $._transformLayout = Q5.device.createBindGroupLayout({
28
+ label: 'transformLayout',
28
29
  entries: [
29
30
  {
30
31
  binding: 0,
@@ -33,14 +34,9 @@ Q5.renderers.webgpu.canvas = ($, q) => {
33
34
  type: 'uniform',
34
35
  hasDynamicOffset: false
35
36
  }
36
- }
37
- ]
38
- });
39
-
40
- $._transformLayout = Q5.device.createBindGroupLayout({
41
- entries: [
37
+ },
42
38
  {
43
- binding: 0,
39
+ binding: 1,
44
40
  visibility: GPUShaderStage.VERTEX,
45
41
  buffer: {
46
42
  type: 'read-only-storage',
@@ -50,9 +46,9 @@ Q5.renderers.webgpu.canvas = ($, q) => {
50
46
  ]
51
47
  });
52
48
 
53
- $.bindGroupLayouts = [$._envLayout, $._transformLayout];
49
+ $.bindGroupLayouts = [$._transformLayout];
54
50
 
55
- const uniformBuffer = Q5.device.createBuffer({
51
+ let uniformBuffer = Q5.device.createBuffer({
56
52
  size: 8, // Size of two floats
57
53
  usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
58
54
  });
@@ -60,25 +56,13 @@ Q5.renderers.webgpu.canvas = ($, q) => {
60
56
  $._createCanvas = (w, h, opt) => {
61
57
  q.ctx = q.drawingContext = c.getContext('webgpu');
62
58
 
63
- opt.format = navigator.gpu.getPreferredCanvasFormat();
64
- opt.device = Q5.device;
59
+ opt.format ??= navigator.gpu.getPreferredCanvasFormat();
60
+ opt.device ??= Q5.device;
65
61
 
66
62
  $.ctx.configure(opt);
67
63
 
68
64
  Q5.device.queue.writeBuffer(uniformBuffer, 0, new Float32Array([$.canvas.hw, $.canvas.hh]));
69
65
 
70
- $._envBindGroup = Q5.device.createBindGroup({
71
- layout: $._envLayout,
72
- entries: [
73
- {
74
- binding: 0,
75
- resource: {
76
- buffer: uniformBuffer
77
- }
78
- }
79
- ]
80
- });
81
-
82
66
  return c;
83
67
  };
84
68
 
@@ -88,7 +72,7 @@ Q5.renderers.webgpu.canvas = ($, q) => {
88
72
 
89
73
  // current color index, used to associate a vertex with a color
90
74
  let colorIndex = 0;
91
- const addColor = (r, g, b, a = 1) => {
75
+ let addColor = (r, g, b, a = 1) => {
92
76
  if (typeof r == 'string') r = $.color(r);
93
77
  else if (b == undefined) {
94
78
  // grayscale mode `fill(1, 0.5)`
@@ -100,6 +84,8 @@ Q5.renderers.webgpu.canvas = ($, q) => {
100
84
  colorIndex++;
101
85
  };
102
86
 
87
+ $._fillIndex = $._strokeIndex = -1;
88
+
103
89
  $.fill = (r, g, b, a) => {
104
90
  addColor(r, g, b, a);
105
91
  $._doFill = true;
@@ -131,56 +117,70 @@ Q5.renderers.webgpu.canvas = ($, q) => {
131
117
  };
132
118
  $.resetMatrix();
133
119
 
134
- // Boolean to track if the matrix has been modified
120
+ // tracks if the matrix has been modified
135
121
  $._matrixDirty = false;
136
122
 
137
- // Array to store transformation matrices for the render pass
123
+ // array to store transformation matrices for the render pass
138
124
  $.transformStates = [$._matrix.slice()];
139
125
 
140
- // Stack to keep track of transformation matrix indexes
126
+ // stack to keep track of transformation matrix indexes
141
127
  $._transformIndexStack = [];
142
128
 
143
129
  $.translate = (x, y, z) => {
144
130
  if (!x && !y && !z) return;
145
131
  // Update the translation values
146
- $._matrix[3] += x;
147
- $._matrix[7] -= y;
148
- $._matrix[11] += z || 0;
132
+ $._matrix[12] += x;
133
+ $._matrix[13] -= y;
134
+ $._matrix[14] += z || 0;
149
135
  $._matrixDirty = true;
150
136
  };
151
137
 
152
- $.rotate = (r) => {
153
- if (!r) return;
154
- if ($._angleMode) r *= $._DEGTORAD;
138
+ $.rotate = (a) => {
139
+ if (!a) return;
140
+ if ($._angleMode) a *= $._DEGTORAD;
155
141
 
156
- let cosR = Math.cos(r);
157
- let sinR = Math.sin(r);
142
+ let cosR = Math.cos(a);
143
+ let sinR = Math.sin(a);
144
+
145
+ let m = $._matrix;
146
+
147
+ let m0 = m[0],
148
+ m1 = m[1],
149
+ m4 = m[4],
150
+ m5 = m[5];
158
151
 
159
- let m0 = $._matrix[0],
160
- m1 = $._matrix[1],
161
- m4 = $._matrix[4],
162
- m5 = $._matrix[5];
163
152
  if (!m0 && !m1 && !m4 && !m5) {
164
- $._matrix[0] = cosR;
165
- $._matrix[1] = sinR;
166
- $._matrix[4] = -sinR;
167
- $._matrix[5] = cosR;
153
+ m[0] = cosR;
154
+ m[1] = sinR;
155
+ m[4] = -sinR;
156
+ m[5] = cosR;
168
157
  } else {
169
- $._matrix[0] = m0 * cosR + m4 * sinR;
170
- $._matrix[1] = m1 * cosR + m5 * sinR;
171
- $._matrix[4] = m0 * -sinR + m4 * cosR;
172
- $._matrix[5] = m1 * -sinR + m5 * cosR;
158
+ m[0] = m0 * cosR + m4 * sinR;
159
+ m[1] = m1 * cosR + m5 * sinR;
160
+ m[4] = m4 * cosR - m0 * sinR;
161
+ m[5] = m5 * cosR - m1 * sinR;
173
162
  }
174
163
 
175
164
  $._matrixDirty = true;
176
165
  };
177
166
 
178
- $.scale = (sx = 1, sy, sz = 1) => {
179
- sy ??= sx;
167
+ $.scale = (x = 1, y, z = 1) => {
168
+ y ??= x;
169
+
170
+ let m = $._matrix;
180
171
 
181
- $._matrix[0] *= sx;
182
- $._matrix[5] *= sy;
183
- $._matrix[10] *= sz;
172
+ m[0] *= x;
173
+ m[1] *= x;
174
+ m[2] *= x;
175
+ m[3] *= x;
176
+ m[4] *= y;
177
+ m[5] *= y;
178
+ m[6] *= y;
179
+ m[7] *= y;
180
+ m[8] *= z;
181
+ m[9] *= z;
182
+ m[10] *= z;
183
+ m[11] *= z;
184
184
 
185
185
  $._matrixDirty = true;
186
186
  };
@@ -252,7 +252,7 @@ Q5.renderers.webgpu.canvas = ($, q) => {
252
252
  if (!$._transformIndexStack.length) {
253
253
  return console.warn('Matrix index stack is empty!');
254
254
  }
255
- // Pop the last matrix index from the stack and set it as the current matrix index
255
+ // Pop the last matrix index and set it as the current matrix index
256
256
  let idx = $._transformIndexStack.pop();
257
257
  $._matrix = $.transformStates[idx].slice();
258
258
  $._transformIndex = idx;
@@ -275,7 +275,6 @@ Q5.renderers.webgpu.canvas = ($, q) => {
275
275
  // left, right, top, bottom
276
276
  let l, r, t, b;
277
277
  if (!mode || mode == 'corner') {
278
- // CORNER
279
278
  l = x;
280
279
  r = x + w;
281
280
  t = -y;
@@ -315,7 +314,7 @@ Q5.renderers.webgpu.canvas = ($, q) => {
315
314
 
316
315
  $._render = () => {
317
316
  if (transformStates.length > 1 || !$._transformBindGroup) {
318
- const transformBuffer = Q5.device.createBuffer({
317
+ let transformBuffer = Q5.device.createBuffer({
319
318
  size: transformStates.length * 64, // Size of 16 floats
320
319
  usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
321
320
  });
@@ -327,6 +326,12 @@ Q5.renderers.webgpu.canvas = ($, q) => {
327
326
  entries: [
328
327
  {
329
328
  binding: 0,
329
+ resource: {
330
+ buffer: uniformBuffer
331
+ }
332
+ },
333
+ {
334
+ binding: 1,
330
335
  resource: {
331
336
  buffer: transformBuffer
332
337
  }
@@ -335,35 +340,47 @@ Q5.renderers.webgpu.canvas = ($, q) => {
335
340
  });
336
341
  }
337
342
 
338
- pass.setBindGroup(0, $._envBindGroup);
339
- pass.setBindGroup(1, $._transformBindGroup);
343
+ pass.setBindGroup(0, $._transformBindGroup);
340
344
 
341
345
  for (let m of $._hooks.preRender) m();
342
346
 
343
347
  let drawVertOffset = 0;
344
348
  let imageVertOffset = 0;
349
+ let textCharOffset = 0;
345
350
  let curPipelineIndex = -1;
346
351
  let curTextureIndex = -1;
347
352
 
348
- pass.setPipeline($.pipelines[0]);
349
-
350
353
  for (let i = 0; i < drawStack.length; i += 2) {
351
354
  let v = drawStack[i + 1];
352
355
 
356
+ if (drawStack[i] == -1) {
357
+ v();
358
+ continue;
359
+ }
360
+
353
361
  if (curPipelineIndex != drawStack[i]) {
354
362
  curPipelineIndex = drawStack[i];
355
363
  pass.setPipeline($.pipelines[curPipelineIndex]);
356
364
  }
357
365
 
358
366
  if (curPipelineIndex == 0) {
359
- pass.draw(v, 1, drawVertOffset, 0);
367
+ // v is the number of vertices
368
+ pass.draw(v, 1, drawVertOffset);
360
369
  drawVertOffset += v;
361
370
  } else if (curPipelineIndex == 1) {
362
371
  if (curTextureIndex != v) {
363
- pass.setBindGroup(3, $._textureBindGroups[v]);
372
+ // v is the texture index
373
+ pass.setBindGroup(2, $._textureBindGroups[v]);
364
374
  }
365
- pass.draw(6, 1, imageVertOffset, 0);
375
+ pass.draw(6, 1, imageVertOffset);
366
376
  imageVertOffset += 6;
377
+ } else if (curPipelineIndex == 2) {
378
+ pass.setBindGroup(2, $._font.bindGroup);
379
+ pass.setBindGroup(3, $._textBindGroup);
380
+
381
+ // v is the number of characters in the text
382
+ pass.draw(4, v, 0, textCharOffset);
383
+ textCharOffset += v;
367
384
  }
368
385
  }
369
386
 
@@ -372,7 +389,7 @@ Q5.renderers.webgpu.canvas = ($, q) => {
372
389
 
373
390
  $._finishRender = () => {
374
391
  pass.end();
375
- const commandBuffer = $.encoder.finish();
392
+ let commandBuffer = $.encoder.finish();
376
393
  Q5.device.queue.submit([commandBuffer]);
377
394
  q.pass = $.encoder = null;
378
395
 
@@ -12,8 +12,8 @@ Q5.renderers.webgpu.drawing = ($, q) => {
12
12
  label: 'drawingVertexShader',
13
13
  code: `
14
14
  struct VertexOutput {
15
- @builtin(position) position: vec4<f32>,
16
- @location(1) colorIndex: f32
15
+ @builtin(position) position: vec4f,
16
+ @location(0) colorIndex: f32
17
17
  };
18
18
 
19
19
  struct Uniforms {
@@ -22,12 +22,12 @@ struct Uniforms {
22
22
  };
23
23
 
24
24
  @group(0) @binding(0) var<uniform> uniforms: Uniforms;
25
- @group(1) @binding(0) var<storage, read> transforms: array<mat4x4<f32>>;
25
+ @group(0) @binding(1) var<storage, read> transforms: array<mat4x4<f32>>;
26
26
 
27
27
  @vertex
28
- fn vertexMain(@location(0) pos: vec2<f32>, @location(1) colorIndex: f32, @location(2) transformIndex: f32) -> VertexOutput {
29
- var vert = vec4<f32>(pos, 0.0, 1.0);
30
- vert *= transforms[i32(transformIndex)];
28
+ fn vertexMain(@location(0) pos: vec2f, @location(1) colorIndex: f32, @location(2) transformIndex: f32) -> VertexOutput {
29
+ var vert = vec4f(pos, 0.0, 1.0);
30
+ vert = transforms[i32(transformIndex)] * vert;
31
31
  vert.x /= uniforms.halfWidth;
32
32
  vert.y /= uniforms.halfHeight;
33
33
 
@@ -42,17 +42,18 @@ fn vertexMain(@location(0) pos: vec2<f32>, @location(1) colorIndex: f32, @locati
42
42
  let fragmentShader = Q5.device.createShaderModule({
43
43
  label: 'drawingFragmentShader',
44
44
  code: `
45
- @group(2) @binding(0) var<storage, read> uColors : array<vec4<f32>>;
45
+ @group(1) @binding(0) var<storage, read> colors : array<vec4f>;
46
46
 
47
47
  @fragment
48
- fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
49
- let index = u32(colorIndex);
50
- return mix(uColors[index], uColors[index + 1u], fract(colorIndex));
48
+ fn fragmentMain(@location(0) colorIndex: f32) -> @location(0) vec4f {
49
+ let index = i32(colorIndex);
50
+ return mix(colors[index], colors[index + 1], fract(colorIndex));
51
51
  }
52
52
  `
53
53
  });
54
54
 
55
55
  colorsLayout = Q5.device.createBindGroupLayout({
56
+ label: 'colorsLayout',
56
57
  entries: [
57
58
  {
58
59
  binding: 0,
@@ -283,10 +284,17 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
283
284
  $.background = (r, g, b, a) => {
284
285
  $.push();
285
286
  $.resetMatrix();
286
- if (r.src) $.image(r, -c.hw, -c.hh, c.w, c.h);
287
- else {
287
+ if (r.src) {
288
+ let og = $._imageMode;
289
+ $._imageMode = 'corner';
290
+ $.image(r, -c.hw, -c.hh, c.w, c.h);
291
+ $._imageMode = og;
292
+ } else {
293
+ let og = $._rectMode;
294
+ $._rectMode = 'corner';
288
295
  $.fill(r, g, b, a);
289
296
  $.rect(-c.hw, -c.hh, c.w, c.h);
297
+ $._rectMode = og;
290
298
  }
291
299
  $.pop();
292
300
  };
@@ -394,7 +402,7 @@ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
394
402
  });
395
403
 
396
404
  // set the bind group once before rendering
397
- $.pass.setBindGroup(2, $._colorsBindGroup);
405
+ $.pass.setBindGroup(1, $._colorsBindGroup);
398
406
  });
399
407
 
400
408
  $._hooks.postRender.push(() => {
@@ -6,8 +6,8 @@ Q5.renderers.webgpu.image = ($, q) => {
6
6
  label: 'imageVertexShader',
7
7
  code: `
8
8
  struct VertexOutput {
9
- @builtin(position) position: vec4<f32>,
10
- @location(0) texCoord: vec2<f32>
9
+ @builtin(position) position: vec4f,
10
+ @location(0) texCoord: vec2f
11
11
  };
12
12
 
13
13
  struct Uniforms {
@@ -16,12 +16,12 @@ struct Uniforms {
16
16
  };
17
17
 
18
18
  @group(0) @binding(0) var<uniform> uniforms: Uniforms;
19
- @group(1) @binding(0) var<storage, read> transforms: array<mat4x4<f32>>;
19
+ @group(0) @binding(1) var<storage, read> transforms: array<mat4x4<f32>>;
20
20
 
21
21
  @vertex
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)];
22
+ fn vertexMain(@location(0) pos: vec2f, @location(1) texCoord: vec2f, @location(2) transformIndex: f32) -> VertexOutput {
23
+ var vert = vec4f(pos, 0.0, 1.0);
24
+ vert = transforms[i32(transformIndex)] * vert;
25
25
  vert.x /= uniforms.halfWidth;
26
26
  vert.y /= uniforms.halfHeight;
27
27
 
@@ -36,11 +36,11 @@ fn vertexMain(@location(0) pos: vec2<f32>, @location(1) texCoord: vec2<f32>, @lo
36
36
  let fragmentShader = Q5.device.createShaderModule({
37
37
  label: 'imageFragmentShader',
38
38
  code: `
39
- @group(3) @binding(0) var samp: sampler;
40
- @group(3) @binding(1) var texture: texture_2d<f32>;
39
+ @group(2) @binding(0) var samp: sampler;
40
+ @group(2) @binding(1) var texture: texture_2d<f32>;
41
41
 
42
42
  @fragment
43
- fn fragmentMain(@location(0) texCoord: vec2<f32>) -> @location(0) vec4<f32> {
43
+ fn fragmentMain(@location(0) texCoord: vec2f) -> @location(0) vec4f {
44
44
  // Sample the texture using the interpolated texture coordinate
45
45
  return textureSample(texture, samp, texCoord);
46
46
  }
@@ -72,11 +72,9 @@ fn fragmentMain(@location(0) texCoord: vec2<f32>) -> @location(0) vec4<f32> {
72
72
  ]
73
73
  };
74
74
 
75
- $.bindGroupLayouts.push(textureLayout);
76
-
77
75
  const pipelineLayout = Q5.device.createPipelineLayout({
78
76
  label: 'imagePipelineLayout',
79
- bindGroupLayouts: $.bindGroupLayouts
77
+ bindGroupLayouts: [...$.bindGroupLayouts, textureLayout]
80
78
  });
81
79
 
82
80
  $.pipelines[1] = Q5.device.createRenderPipeline({
@@ -118,20 +116,30 @@ fn fragmentMain(@location(0) texCoord: vec2<f32>) -> @location(0) vec4<f32> {
118
116
  minFilter: 'linear'
119
117
  });
120
118
 
119
+ let MAX_TEXTURES = 12000;
120
+
121
+ $._textures = [];
122
+ let tIdx = 0;
123
+
121
124
  $._createTexture = (img) => {
122
125
  if (img.canvas) img = img.canvas;
123
126
 
124
127
  let textureSize = [img.width, img.height, 1];
125
128
 
126
- const texture = Q5.device.createTexture({
129
+ let texture = Q5.device.createTexture({
127
130
  size: textureSize,
128
131
  format: 'bgra8unorm',
129
132
  usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
130
133
  });
131
134
 
132
- Q5.device.queue.copyExternalImageToTexture({ source: img }, { texture }, textureSize);
135
+ Q5.device.queue.copyExternalImageToTexture(
136
+ { source: img },
137
+ { texture, colorSpace: $.canvas.colorSpace },
138
+ textureSize
139
+ );
133
140
 
134
- img.textureIndex = $._textureBindGroups.length;
141
+ $._textures[tIdx] = texture;
142
+ img.textureIndex = tIdx;
135
143
 
136
144
  const textureBindGroup = Q5.device.createBindGroup({
137
145
  layout: textureLayout,
@@ -140,7 +148,16 @@ fn fragmentMain(@location(0) texCoord: vec2<f32>) -> @location(0) vec4<f32> {
140
148
  { binding: 1, resource: texture.createView() }
141
149
  ]
142
150
  });
143
- $._textureBindGroups.push(textureBindGroup);
151
+ $._textureBindGroups[tIdx] = textureBindGroup;
152
+
153
+ tIdx = (tIdx + 1) % MAX_TEXTURES;
154
+
155
+ // If the texture array is full, destroy the oldest texture
156
+ if ($._textures[tIdx]) {
157
+ $._textures[tIdx].destroy();
158
+ delete $._textures[tIdx];
159
+ delete $._textureBindGroups[tIdx];
160
+ }
144
161
  };
145
162
 
146
163
  $.loadImage = $.loadTexture = (src) => {