q5 2.5.5 → 2.6.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.
@@ -1,6 +1,6 @@
1
1
  Q5.renderers.webgpu.image = ($, q) => {
2
2
  $._textureBindGroups = [];
3
- let verticesStack = [];
3
+ let vertexStack = [];
4
4
 
5
5
  let vertexShader = Q5.device.createShaderModule({
6
6
  label: 'imageVertexShader',
@@ -8,15 +8,14 @@ Q5.renderers.webgpu.image = ($, q) => {
8
8
  struct VertexOutput {
9
9
  @builtin(position) position: vec4f,
10
10
  @location(0) texCoord: vec2f
11
- };
12
-
11
+ }
13
12
  struct Uniforms {
14
13
  halfWidth: f32,
15
14
  halfHeight: f32
16
- };
15
+ }
17
16
 
18
17
  @group(0) @binding(0) var<uniform> uniforms: Uniforms;
19
- @group(0) @binding(1) var<storage, read> transforms: array<mat4x4<f32>>;
18
+ @group(0) @binding(1) var<storage> transforms: array<mat4x4<f32>>;
20
19
 
21
20
  @vertex
22
21
  fn vertexMain(@location(0) pos: vec2f, @location(1) texCoord: vec2f, @location(2) transformIndex: f32) -> VertexOutput {
@@ -77,7 +76,7 @@ fn fragmentMain(@location(0) texCoord: vec2f) -> @location(0) vec4f {
77
76
  bindGroupLayouts: [...$.bindGroupLayouts, textureLayout]
78
77
  });
79
78
 
80
- $.pipelines[1] = Q5.device.createRenderPipeline({
79
+ $._pipelineConfigs[1] = {
81
80
  label: 'imagePipeline',
82
81
  layout: pipelineLayout,
83
82
  vertex: {
@@ -88,28 +87,12 @@ fn fragmentMain(@location(0) texCoord: vec2f) -> @location(0) vec4f {
88
87
  fragment: {
89
88
  module: fragmentShader,
90
89
  entryPoint: 'fragmentMain',
91
- targets: [
92
- {
93
- format: 'bgra8unorm',
94
- blend: $.blendConfigs?.normal || {
95
- color: {
96
- srcFactor: 'src-alpha',
97
- dstFactor: 'one-minus-src-alpha',
98
- operation: 'add'
99
- },
100
- alpha: {
101
- srcFactor: 'src-alpha',
102
- dstFactor: 'one-minus-src-alpha',
103
- operation: 'add'
104
- }
105
- }
106
- }
107
- ]
90
+ targets: [{ format: 'bgra8unorm', blend: $.blendConfigs.normal }]
108
91
  },
109
- primitive: {
110
- topology: 'triangle-list'
111
- }
112
- });
92
+ primitive: { topology: 'triangle-list' }
93
+ };
94
+
95
+ $._pipelines[1] = Q5.device.createRenderPipeline($._pipelineConfigs[1]);
113
96
 
114
97
  let sampler = Q5.device.createSampler({
115
98
  magFilter: 'linear',
@@ -134,7 +117,11 @@ fn fragmentMain(@location(0) texCoord: vec2f) -> @location(0) vec4f {
134
117
 
135
118
  Q5.device.queue.copyExternalImageToTexture(
136
119
  { source: img },
137
- { texture, colorSpace: $.canvas.colorSpace },
120
+ {
121
+ texture,
122
+ colorSpace: $.canvas.colorSpace
123
+ // premultipliedAlpha: true
124
+ },
138
125
  textureSize
139
126
  );
140
127
 
@@ -187,7 +174,7 @@ fn fragmentMain(@location(0) texCoord: vec2f) -> @location(0) vec4f {
187
174
  let [l, r, t, b] = $._calcBox(x, y, w, h, $._imageMode);
188
175
 
189
176
  // prettier-ignore
190
- verticesStack.push(
177
+ vertexStack.push(
191
178
  l, t, 0, 0, ti,
192
179
  r, t, 1, 0, ti,
193
180
  l, b, 0, 1, ti,
@@ -196,29 +183,29 @@ fn fragmentMain(@location(0) texCoord: vec2f) -> @location(0) vec4f {
196
183
  r, b, 1, 1, ti
197
184
  );
198
185
 
199
- $.drawStack.push(1, img.textureIndex);
186
+ $.drawStack.push(1, img.textureIndex, 0);
200
187
  };
201
188
 
202
189
  $._hooks.preRender.push(() => {
203
190
  if (!$._textureBindGroups.length) return;
204
191
 
205
192
  // Switch to image pipeline
206
- $.pass.setPipeline($.pipelines[1]);
207
-
208
- // Create a vertex buffer for the image quads
209
- const vertices = new Float32Array(verticesStack);
193
+ $.pass.setPipeline($._pipelines[1]);
210
194
 
211
195
  const vertexBuffer = Q5.device.createBuffer({
212
- size: vertices.byteLength,
213
- usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
196
+ size: vertexStack.length * 4,
197
+ usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
198
+ mappedAtCreation: true
214
199
  });
215
200
 
216
- Q5.device.queue.writeBuffer(vertexBuffer, 0, vertices);
201
+ new Float32Array(vertexBuffer.getMappedRange()).set(vertices);
202
+ vertexBuffer.unmap();
203
+
217
204
  $.pass.setVertexBuffer(1, vertexBuffer);
218
205
  });
219
206
 
220
207
  $._hooks.postRender.push(() => {
221
- verticesStack.length = 0;
208
+ vertexStack.length = 0;
222
209
  });
223
210
  };
224
211
 
@@ -7,35 +7,35 @@ const pos = array(vec2f(0, -1), vec2f(1, -1), vec2f(0, 0), vec2f(1, 0));
7
7
 
8
8
  struct VertexInput {
9
9
  @builtin(vertex_index) vertex : u32,
10
- @builtin(instance_index) instance : u32,
11
- };
10
+ @builtin(instance_index) instance : u32
11
+ }
12
12
  struct VertexOutput {
13
13
  @builtin(position) position : vec4f,
14
- @location(0) texcoord : vec2f,
15
- @location(1) colorIndex : f32
16
- };
14
+ @location(0) texCoord : vec2f,
15
+ @location(1) fillColor : vec4f
16
+ }
17
17
  struct Char {
18
18
  texOffset: vec2f,
19
19
  texExtent: vec2f,
20
20
  size: vec2f,
21
21
  offset: vec2f,
22
- };
22
+ }
23
23
  struct Text {
24
24
  pos: vec2f,
25
25
  scale: f32,
26
26
  transformIndex: f32,
27
27
  fillIndex: f32,
28
28
  strokeIndex: f32
29
- };
29
+ }
30
30
  struct Uniforms {
31
31
  halfWidth: f32,
32
32
  halfHeight: f32
33
- };
33
+ }
34
34
 
35
35
  @group(0) @binding(0) var<uniform> uniforms: Uniforms;
36
- @group(0) @binding(1) var<storage, read> transforms: array<mat4x4<f32>>;
36
+ @group(0) @binding(1) var<storage> transforms: array<mat4x4<f32>>;
37
37
 
38
- @group(1) @binding(0) var<storage, read> colors : array<vec4f>;
38
+ @group(1) @binding(0) var<storage> colors : array<vec4f>;
39
39
 
40
40
  @group(2) @binding(0) var fontTexture: texture_2d<f32>;
41
41
  @group(2) @binding(1) var fontSampler: sampler;
@@ -61,13 +61,13 @@ fn vertexMain(input : VertexInput) -> VertexOutput {
61
61
 
62
62
  var output : VertexOutput;
63
63
  output.position = vert;
64
- output.texcoord = (pos[input.vertex] * vec2f(1, -1)) * fontChar.texExtent + fontChar.texOffset;
65
- output.colorIndex = text.fillIndex;
64
+ output.texCoord = (pos[input.vertex] * vec2f(1, -1)) * fontChar.texExtent + fontChar.texOffset;
65
+ output.fillColor = colors[i32(text.fillIndex)];
66
66
  return output;
67
67
  }
68
68
 
69
- fn sampleMsdf(texcoord: vec2f) -> f32 {
70
- let c = textureSample(fontTexture, fontSampler, texcoord);
69
+ fn sampleMsdf(texCoord: vec2f) -> f32 {
70
+ let c = textureSample(fontTexture, fontSampler, texCoord);
71
71
  return max(min(c.r, c.g), min(max(c.r, c.g), c.b));
72
72
  }
73
73
 
@@ -77,25 +77,83 @@ fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
77
77
  // uses the default which is 4.
78
78
  let pxRange = 4.0;
79
79
  let sz = vec2f(textureDimensions(fontTexture, 0));
80
- let dx = sz.x*length(vec2f(dpdxFine(input.texcoord.x), dpdyFine(input.texcoord.x)));
81
- let dy = sz.y*length(vec2f(dpdxFine(input.texcoord.y), dpdyFine(input.texcoord.y)));
80
+ let dx = sz.x*length(vec2f(dpdxFine(input.texCoord.x), dpdyFine(input.texCoord.x)));
81
+ let dy = sz.y*length(vec2f(dpdxFine(input.texCoord.y), dpdyFine(input.texCoord.y)));
82
82
  let toPixels = pxRange * inverseSqrt(dx * dx + dy * dy);
83
- let sigDist = sampleMsdf(input.texcoord) - 0.5;
83
+ let sigDist = sampleMsdf(input.texCoord) - 0.5;
84
84
  let pxDist = sigDist * toPixels;
85
85
  let edgeWidth = 0.5;
86
86
  let alpha = smoothstep(-edgeWidth, edgeWidth, pxDist);
87
87
  if (alpha < 0.001) {
88
88
  discard;
89
89
  }
90
- let fillColor = colors[i32(input.colorIndex)];
91
- return vec4f(fillColor.rgb, fillColor.a * alpha);
90
+ return vec4f(input.fillColor.rgb, input.fillColor.a * alpha);
92
91
  }
93
92
  `
94
93
  });
95
94
 
95
+ let textBindGroupLayout = Q5.device.createBindGroupLayout({
96
+ label: 'MSDF text group layout',
97
+ entries: [
98
+ {
99
+ binding: 0,
100
+ visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
101
+ buffer: { type: 'read-only-storage' }
102
+ },
103
+ {
104
+ binding: 1,
105
+ visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
106
+ buffer: { type: 'read-only-storage' }
107
+ }
108
+ ]
109
+ });
110
+
111
+ let fontSampler = Q5.device.createSampler({
112
+ minFilter: 'linear',
113
+ magFilter: 'linear',
114
+ mipmapFilter: 'linear',
115
+ maxAnisotropy: 16
116
+ });
117
+ let fontBindGroupLayout = Q5.device.createBindGroupLayout({
118
+ label: 'MSDF font group layout',
119
+ entries: [
120
+ {
121
+ binding: 0,
122
+ visibility: GPUShaderStage.FRAGMENT,
123
+ texture: {}
124
+ },
125
+ {
126
+ binding: 1,
127
+ visibility: GPUShaderStage.FRAGMENT,
128
+ sampler: {}
129
+ },
130
+ {
131
+ binding: 2,
132
+ visibility: GPUShaderStage.VERTEX,
133
+ buffer: { type: 'read-only-storage' }
134
+ }
135
+ ]
136
+ });
137
+
138
+ let fontPipelineLayout = Q5.device.createPipelineLayout({
139
+ bindGroupLayouts: [...$.bindGroupLayouts, fontBindGroupLayout, textBindGroupLayout]
140
+ });
141
+
142
+ $._pipelineConfigs[2] = {
143
+ label: 'msdf font pipeline',
144
+ layout: fontPipelineLayout,
145
+ vertex: { module: textShader, entryPoint: 'vertexMain' },
146
+ fragment: {
147
+ module: textShader,
148
+ entryPoint: 'fragmentMain',
149
+ targets: [{ format: 'bgra8unorm', blend: $.blendConfigs.normal }]
150
+ },
151
+ primitive: { topology: 'triangle-strip', stripIndexFormat: 'uint32' }
152
+ };
153
+ $._pipelines[2] = Q5.device.createRenderPipeline($._pipelineConfigs[2]);
154
+
96
155
  class MsdfFont {
97
- constructor(pipeline, bindGroup, lineHeight, chars, kernings) {
98
- this.pipeline = pipeline;
156
+ constructor(bindGroup, lineHeight, chars, kernings) {
99
157
  this.bindGroup = bindGroup;
100
158
  this.lineHeight = lineHeight;
101
159
  this.chars = chars;
@@ -121,22 +179,7 @@ fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
121
179
  }
122
180
  }
123
181
 
124
- let textBindGroupLayout = Q5.device.createBindGroupLayout({
125
- label: 'MSDF text group layout',
126
- entries: [
127
- {
128
- binding: 0,
129
- visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
130
- buffer: { type: 'read-only-storage' }
131
- },
132
- {
133
- binding: 1,
134
- visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
135
- buffer: { type: 'read-only-storage' }
136
- }
137
- ]
138
- });
139
-
182
+ $._fonts = [];
140
183
  let fonts = {};
141
184
 
142
185
  let createFont = async (fontJsonUrl, fontName, cb) => {
@@ -199,74 +242,11 @@ fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
199
242
  }
200
243
  charsBuffer.unmap();
201
244
 
202
- let fontSampler = Q5.device.createSampler({
203
- minFilter: 'linear',
204
- magFilter: 'linear',
205
- mipmapFilter: 'linear',
206
- maxAnisotropy: 16
207
- });
208
- let fontBindGroupLayout = Q5.device.createBindGroupLayout({
209
- label: 'MSDF font group layout',
210
- entries: [
211
- {
212
- binding: 0,
213
- visibility: GPUShaderStage.FRAGMENT,
214
- texture: {}
215
- },
216
- {
217
- binding: 1,
218
- visibility: GPUShaderStage.FRAGMENT,
219
- sampler: {}
220
- },
221
- {
222
- binding: 2,
223
- visibility: GPUShaderStage.VERTEX,
224
- buffer: { type: 'read-only-storage' }
225
- }
226
- ]
227
- });
228
- let fontPipeline = Q5.device.createRenderPipeline({
229
- label: 'msdf font pipeline',
230
- layout: Q5.device.createPipelineLayout({
231
- bindGroupLayouts: [...$.bindGroupLayouts, fontBindGroupLayout, textBindGroupLayout]
232
- }),
233
- vertex: {
234
- module: textShader,
235
- entryPoint: 'vertexMain'
236
- },
237
- fragment: {
238
- module: textShader,
239
- entryPoint: 'fragmentMain',
240
- targets: [
241
- {
242
- format: 'bgra8unorm',
243
- blend: {
244
- color: {
245
- srcFactor: 'src-alpha',
246
- dstFactor: 'one-minus-src-alpha'
247
- },
248
- alpha: {
249
- srcFactor: 'one',
250
- dstFactor: 'one'
251
- }
252
- }
253
- }
254
- ]
255
- },
256
- primitive: {
257
- topology: 'triangle-strip',
258
- stripIndexFormat: 'uint32'
259
- }
260
- });
261
-
262
245
  let fontBindGroup = Q5.device.createBindGroup({
263
246
  label: 'msdf font bind group',
264
247
  layout: fontBindGroupLayout,
265
248
  entries: [
266
- {
267
- binding: 0,
268
- resource: texture.createView()
269
- },
249
+ { binding: 0, resource: texture.createView() },
270
250
  { binding: 1, resource: fontSampler },
271
251
  { binding: 2, resource: { buffer: charsBuffer } }
272
252
  ]
@@ -284,10 +264,11 @@ fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
284
264
  }
285
265
  }
286
266
 
287
- $._font = new MsdfFont(fontPipeline, fontBindGroup, atlas.common.lineHeight, chars, kernings);
267
+ $._font = new MsdfFont(fontBindGroup, atlas.common.lineHeight, chars, kernings);
288
268
 
269
+ $._font.index = $._fonts.length;
270
+ $._fonts.push($._font);
289
271
  fonts[fontName] = $._font;
290
- $.pipelines[2] = $._font.pipeline;
291
272
 
292
273
  q._preloadCount--;
293
274
 
@@ -316,12 +297,6 @@ fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
316
297
 
317
298
  $.textFont = (fontName) => {
318
299
  $._font = fonts[fontName];
319
-
320
- // replay the change of font in the draw stack
321
- $.drawStack.push(-1, () => {
322
- $._font = fonts[fontName];
323
- $.pipelines[2] = $._font.pipeline;
324
- });
325
300
  };
326
301
  $.textSize = (size) => {
327
302
  $._textSize = size;
@@ -434,7 +409,7 @@ fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
434
409
  }
435
410
  }
436
411
 
437
- let charsData = new Float32Array((str.length - spaces) * 4);
412
+ let charsData = [];
438
413
 
439
414
  let ta = $._textAlign,
440
415
  tb = $._textBaseline,
@@ -480,7 +455,7 @@ fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
480
455
  }
481
456
  $._charStack.push(charsData);
482
457
 
483
- let text = new Float32Array(6);
458
+ let text = [];
484
459
 
485
460
  if ($._matrixDirty) $._saveMatrix();
486
461
 
@@ -488,11 +463,11 @@ fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
488
463
  text[1] = -y;
489
464
  text[2] = $._textSize / 44;
490
465
  text[3] = $._transformIndex;
491
- text[4] = $._fillIndex;
466
+ text[4] = $._fillSet ? $._fillIndex : 0;
492
467
  text[5] = $._strokeIndex;
493
468
 
494
469
  $._textStack.push(text);
495
- $.drawStack.push(2, measurements.printedCharCount);
470
+ $.drawStack.push(2, measurements.printedCharCount, $._font.index);
496
471
  };
497
472
 
498
473
  $.textWidth = (str) => {
@@ -505,11 +480,11 @@ fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
505
480
 
506
481
  if ($._doFill) {
507
482
  let fi = $._fillIndex * 4;
508
- g.fill(colorsStack.slice(fi, fi + 4));
483
+ g.fill(colorStack.slice(fi, fi + 4));
509
484
  }
510
485
  if ($._doStroke) {
511
486
  let si = $._strokeIndex * 4;
512
- g.stroke(colorsStack.slice(si, si + 4));
487
+ g.stroke(colorStack.slice(si, si + 4));
513
488
  }
514
489
 
515
490
  let img = g.createTextImage(str, w, h);
@@ -560,21 +535,15 @@ fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
560
535
  totalTextSize += charsData.length * 4;
561
536
  }
562
537
 
563
- // Create a single buffer for all text data
538
+ // Create a single buffer for all char data
564
539
  let charBuffer = Q5.device.createBuffer({
565
- label: 'charBuffer',
566
540
  size: totalTextSize,
567
541
  usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
568
542
  mappedAtCreation: true
569
543
  });
570
544
 
571
545
  // Copy all text data into the buffer
572
- let textArray = new Float32Array(charBuffer.getMappedRange());
573
- let o = 0;
574
- for (let array of $._charStack) {
575
- textArray.set(array, o);
576
- o += array.length;
577
- }
546
+ new Float32Array(charBuffer.getMappedRange()).set($._charStack.flat());
578
547
  charBuffer.unmap();
579
548
 
580
549
  // Calculate total buffer size for metadata
@@ -589,12 +558,7 @@ fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
589
558
  });
590
559
 
591
560
  // Copy all metadata into the buffer
592
- let metadataArray = new Float32Array(textBuffer.getMappedRange());
593
- o = 0;
594
- for (let array of $._textStack) {
595
- metadataArray.set(array, o);
596
- o += array.length;
597
- }
561
+ new Float32Array(textBuffer.getMappedRange()).set($._textStack.flat());
598
562
  textBuffer.unmap();
599
563
 
600
564
  // Create a single bind group for the text buffer and metadata buffer
@@ -602,14 +566,8 @@ fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
602
566
  label: 'msdf text bind group',
603
567
  layout: textBindGroupLayout,
604
568
  entries: [
605
- {
606
- binding: 0,
607
- resource: { buffer: charBuffer }
608
- },
609
- {
610
- binding: 1,
611
- resource: { buffer: textBuffer }
612
- }
569
+ { binding: 0, resource: { buffer: charBuffer } },
570
+ { binding: 1, resource: { buffer: textBuffer } }
613
571
  ]
614
572
  });
615
573
  });
package/src/readme.md CHANGED
@@ -62,7 +62,7 @@ WebGPU rendering modules are in development:
62
62
  - [Load a MSDF font](#load-a-msdf-font)
63
63
  - [Displaying Emojis](#displaying-emojis)
64
64
  - [Lightweight Use](#lightweight-use)
65
- - [Implemented functions](#implemented-functions)
65
+ - [Limitations](#limitations)
66
66
  - [math](#math)
67
67
  - [noisier](#noisier)
68
68
 
@@ -114,7 +114,7 @@ Image based features in this module require the q5-2d-image module.
114
114
 
115
115
  > ⚠️ Experimental features! ⚠️
116
116
 
117
- To use q5's WebGPU renderer, run `Q5.webgpu()` at the bottom of your sketch. Explicit use of `createCanvas` is required.
117
+ To use q5's WebGPU renderer, run `Q5.webgpu()` at the bottom of your sketch.
118
118
 
119
119
  ```js
120
120
  function setup() {
@@ -132,34 +132,24 @@ Q5.webgpu();
132
132
 
133
133
  WebGPU has different default settings compared to q5's q2d renderer and p5's P2D and WEBGL modes.
134
134
 
135
+ - Explicit use of `createCanvas` is required before anything can be drawn.
135
136
  - The default color mode is RGB in 0-1 "float" format: `colorMode(RGB, 1)`.
136
137
  - The origin of the canvas (0, 0) is in the center, not the top left.
137
- - Mouse and touch coordinates correspond to canvas pixels.
138
- - For now, strokes are only implemented for the `point`, `line`, and `textImage` functions.
138
+ - Mouse and touch coordinates correspond to canvas pixels (unlike in p5 WEBGL mode).
139
139
 
140
140
  The sketches you create with the q5-webgpu renderer will still display properly if WebGPU is not supported on a viewer's browser. q5 will put a warning in the console and apply a compatibility layer to display sketches with the fallback q2d renderer.
141
141
 
142
- Implemented functions:
143
-
144
- `createCanvas`, `resizeCanvas`, `fill`, `clear`, `push`, `pop`, `resetMatrix`, `translate`, `rotate`, `scale`
145
-
146
142
  ## webgpu-drawing
147
143
 
148
144
  > Uses `colorMode(RGB, 1)` by default. Changing it to 'oklch' is not supported yet for the webgpu renderer.
149
145
 
150
- All basic shapes are drawn from their center. Strokes are not implemented yet.
151
-
152
146
  q5's WebGPU renderer drawing functions like `rect` don't immediately draw on the canvas. Instead, they prepare vertex and color data to be sent to the GPU in bulk, which occurs after the user's `draw` function and any post-draw functions are run. This approach better utilizes the GPU, so it doesn't have to repeatedly wait for the CPU to send small chunks of data that describe each individual shape. It's the main reason why WebGPU is faster than Canvas2D.
153
147
 
154
- Implemented functions:
155
-
156
- `rect`, `circle`, `ellipse`, `triangle`, `beginShape`, `vertex`, `endShape`, `blendMode`
148
+ Rounded rectangles, stroke modes, and functions for drawing curves like `bezier` and `curve` are not implemented yet.
157
149
 
158
150
  ## webgpu-image
159
151
 
160
- Implemented functions:
161
-
162
- `loadImage`, `loadTexture`, `image`, `imageMode`
152
+ Using `image` to drawn a subsection of an image and most blending modes are not yet implemented.
163
153
 
164
154
  ## webgpu-text
165
155
 
@@ -238,9 +228,9 @@ For super lightweight use load <https://q5js.org/fonts/YaHei-256-msdf.json>, whi
238
228
  !@'",-.0123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
239
229
  ```
240
230
 
241
- ### Implemented functions
231
+ ### Limitations
242
232
 
243
- `loadFont`, `text`, `textFont`, `textSize`, `textAlign`, `textWidth`, `createTextImage`, `textImage`
233
+ Text strokes are not supported yet, except with `textImage`.
244
234
 
245
235
  ## math
246
236