@bloopjs/toodle 0.1.3 → 0.1.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/dist/mod.js CHANGED
@@ -17219,386 +17219,120 @@ class WebGPUBackend {
17219
17219
  return this.#renderPass;
17220
17220
  }
17221
17221
  }
17222
- // src/backends/webgl2/WebGLTextShader.ts
17223
- class WebGLTextShader {
17224
- label = "text";
17222
+ // src/backends/webgl2/WebGLFontPipeline.ts
17223
+ class WebGLFontPipeline {
17225
17224
  font;
17225
+ fontTexture;
17226
+ charDataTexture;
17227
+ textBufferTexture;
17226
17228
  maxCharCount;
17227
- constructor(font, maxCharCount) {
17229
+ lineHeight;
17230
+ #gl;
17231
+ constructor(gl, font, fontTexture, charDataTexture, textBufferTexture, maxCharCount) {
17232
+ this.#gl = gl;
17228
17233
  this.font = font;
17234
+ this.fontTexture = fontTexture;
17235
+ this.charDataTexture = charDataTexture;
17236
+ this.textBufferTexture = textBufferTexture;
17229
17237
  this.maxCharCount = maxCharCount;
17238
+ this.lineHeight = font.lineHeight;
17239
+ }
17240
+ static create(gl, font, maxCharCount) {
17241
+ const fontTexture = gl.createTexture();
17242
+ assert(fontTexture, "Failed to create font texture");
17243
+ gl.bindTexture(gl.TEXTURE_2D, fontTexture);
17244
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
17245
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
17246
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
17247
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
17248
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, font.imageBitmap);
17249
+ const charDataTexture = gl.createTexture();
17250
+ assert(charDataTexture, "Failed to create char data texture");
17251
+ gl.bindTexture(gl.TEXTURE_2D, charDataTexture);
17252
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
17253
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
17254
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
17255
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
17256
+ const charCount = font.charCount;
17257
+ const charTextureWidth = charCount * 2;
17258
+ const charTextureData = new Float32Array(charTextureWidth * 4);
17259
+ for (let i3 = 0;i3 < charCount; i3++) {
17260
+ const srcOffset = i3 * 8;
17261
+ const dstOffset0 = i3 * 2 * 4;
17262
+ const dstOffset1 = (i3 * 2 + 1) * 4;
17263
+ charTextureData[dstOffset0] = font.charBuffer[srcOffset];
17264
+ charTextureData[dstOffset0 + 1] = font.charBuffer[srcOffset + 1];
17265
+ charTextureData[dstOffset0 + 2] = font.charBuffer[srcOffset + 2];
17266
+ charTextureData[dstOffset0 + 3] = font.charBuffer[srcOffset + 3];
17267
+ charTextureData[dstOffset1] = font.charBuffer[srcOffset + 4];
17268
+ charTextureData[dstOffset1 + 1] = font.charBuffer[srcOffset + 5];
17269
+ charTextureData[dstOffset1 + 2] = font.charBuffer[srcOffset + 6];
17270
+ charTextureData[dstOffset1 + 3] = font.charBuffer[srcOffset + 7];
17271
+ }
17272
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, charTextureWidth, 1, 0, gl.RGBA, gl.FLOAT, charTextureData);
17273
+ const textBufferTexture = gl.createTexture();
17274
+ assert(textBufferTexture, "Failed to create text buffer texture");
17275
+ gl.bindTexture(gl.TEXTURE_2D, textBufferTexture);
17276
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
17277
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
17278
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
17279
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
17280
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, maxCharCount, 1, 0, gl.RGBA, gl.FLOAT, null);
17281
+ gl.bindTexture(gl.TEXTURE_2D, null);
17282
+ return new WebGLFontPipeline(gl, font, fontTexture, charDataTexture, textBufferTexture, maxCharCount);
17283
+ }
17284
+ updateTextBuffer(data, glyphCount) {
17285
+ const gl = this.#gl;
17286
+ gl.bindTexture(gl.TEXTURE_2D, this.textBufferTexture);
17287
+ gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, glyphCount, 1, gl.RGBA, gl.FLOAT, data);
17230
17288
  }
17231
- startFrame(_uniform) {}
17232
- processBatch(_nodes) {
17233
- throw new Error("Text rendering is not supported in WebGL mode. Use WebGPU backend for text rendering.");
17289
+ destroy() {
17290
+ const gl = this.#gl;
17291
+ gl.deleteTexture(this.fontTexture);
17292
+ gl.deleteTexture(this.charDataTexture);
17293
+ gl.deleteTexture(this.textBufferTexture);
17234
17294
  }
17235
- endFrame() {}
17236
- }
17237
-
17238
- // src/backends/webgpu/wgsl/text.wgsl.ts
17239
- var text_wgsl_default = `
17240
- // Adapted from: https://webgpu.github.io/webgpu-samples/?sample=textRenderingMsdf
17241
-
17242
- // Quad vertex positions for a character
17243
- const pos = array(
17244
- vec2f(0, -1),
17245
- vec2f(1, -1),
17246
- vec2f(0, 0),
17247
- vec2f(1, 0),
17248
- );
17249
-
17250
- // Debug colors for visualization
17251
- const debugColors = array(
17252
- vec4f(1, 0, 0, 1),
17253
- vec4f(0, 1, 0, 1),
17254
- vec4f(0, 0, 1, 1),
17255
- vec4f(1, 1, 1, 1),
17256
- );
17257
-
17258
- // Vertex input from GPU
17259
- struct VertexInput {
17260
- @builtin(vertex_index) vertex: u32,
17261
- @builtin(instance_index) instance: u32,
17262
- };
17263
-
17264
- // Output from vertex shader to fragment shader
17265
- struct VertexOutput {
17266
- @builtin(position) position: vec4f,
17267
- @location(0) texcoord: vec2f,
17268
- @location(1) debugColor: vec4f,
17269
- @location(2) @interpolate(flat) instanceIndex: u32,
17270
- };
17271
-
17272
- // Metadata for a single character glyph
17273
- struct Char {
17274
- texOffset: vec2f, // Offset to top-left in MSDF texture (pixels)
17275
- texExtent: vec2f, // Size in texture (pixels)
17276
- size: vec2f, // Glyph size in ems
17277
- offset: vec2f, // Position offset in ems
17278
- };
17279
-
17280
- // Metadata for a text block
17281
- struct TextBlockDescriptor {
17282
- transform: mat3x3f, // Text transform matrix (model matrix)
17283
- color: vec4f, // Text color
17284
- fontSize: f32, // Font size
17285
- blockWidth: f32, // Total width of text block
17286
- blockHeight: f32, // Total height of text block
17287
- bufferPosition: f32 // Index and length in textBuffer
17288
- };
17289
-
17290
- // Font bindings
17291
- @group(0) @binding(0) var fontTexture: texture_2d<f32>;
17292
- @group(0) @binding(1) var fontSampler: sampler;
17293
- @group(0) @binding(2) var<storage> chars: array<Char>;
17294
- @group(0) @binding(3) var<uniform> fontData: vec4f; // Contains line height (x)
17295
-
17296
- // Text bindings
17297
- @group(1) @binding(0) var<storage> texts: array<TextBlockDescriptor>;
17298
- @group(1) @binding(1) var<storage> textBuffer: array<vec4f>; // Each vec4: xy = glyph pos, z = char index
17299
-
17300
- // Global uniforms
17301
- @group(2) @binding(0) var<uniform> viewProjectionMatrix: mat3x3f;
17302
-
17303
- // Vertex shader
17304
- @vertex
17305
- fn vertexMain(input: VertexInput) -> VertexOutput {
17306
- // Because the instance index is used for character indexing, we are
17307
- // overloading the vertex index to store the instance of the text metadata.
17308
- //
17309
- // I.e...
17310
- // Vertex 0-4 = Instance 0, Vertex 0-4
17311
- // Vertex 4-8 = Instance 1, Vertex 0-4
17312
- // Vertex 8-12 = Instance 2, Vertex 0-4
17313
- let vertexIndex = input.vertex % 4;
17314
- let textIndex = input.vertex / 4;
17315
-
17316
- let text = texts[textIndex];
17317
- let textElement = textBuffer[u32(text.bufferPosition) + input.instance];
17318
- let char = chars[u32(textElement.z)];
17319
-
17320
- let lineHeight = fontData.x;
17321
- let textWidth = text.blockWidth;
17322
- let textHeight = text.blockHeight;
17323
-
17324
- // Center text vertically; origin is mid-height
17325
- let offset = vec2f(0, -textHeight / 2);
17326
-
17327
- // Glyph position in ems (quad pos * size + per-char offset)
17328
- let emPos = pos[vertexIndex] * char.size + char.offset + textElement.xy - offset;
17329
- let charPos = emPos * (text.fontSize / lineHeight);
17330
-
17331
- var output: VertexOutput;
17332
- let transformedPosition = viewProjectionMatrix * text.transform * vec3f(charPos, 1);
17333
-
17334
- output.position = vec4f(transformedPosition, 1);
17335
- output.texcoord = pos[vertexIndex] * vec2f(1, -1);
17336
- output.texcoord *= char.texExtent;
17337
- output.texcoord += char.texOffset;
17338
- output.debugColor = debugColors[vertexIndex];
17339
- output.instanceIndex = textIndex;
17340
- return output;
17341
-
17342
- // To debug - hardcode quad in bottom right quarter of the screen:
17343
- // output.position = vec4f(pos[input.vertex], 0, 1);
17344
- }
17345
-
17346
- // Signed distance function sampling for MSDF font rendering
17347
- fn sampleMsdf(texcoord: vec2f) -> f32 {
17348
- let c = textureSample(fontTexture, fontSampler, texcoord);
17349
- return max(min(c.r, c.g), min(max(c.r, c.g), c.b));
17350
17295
  }
17351
17296
 
17352
- // Fragment shader
17353
- // Anti-aliasing technique by Paul Houx
17354
- // more details here:
17355
- // https://github.com/Chlumsky/msdfgen/issues/22#issuecomment-234958005
17356
- @fragment
17357
- fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {
17358
- let text = texts[input.instanceIndex];
17359
-
17360
- // pxRange (AKA distanceRange) comes from the msdfgen tool.
17361
- let pxRange = 4.0;
17362
- let texSize = vec2f(textureDimensions(fontTexture, 0));
17363
-
17364
- let dx = texSize.x * length(vec2f(dpdxFine(input.texcoord.x), dpdyFine(input.texcoord.x)));
17365
- let dy = texSize.y * length(vec2f(dpdxFine(input.texcoord.y), dpdyFine(input.texcoord.y)));
17366
-
17367
- let toPixels = pxRange * inverseSqrt(dx * dx + dy * dy);
17368
- let sigDist = sampleMsdf(input.texcoord) - 0.5;
17369
- let pxDist = sigDist * toPixels;
17370
-
17371
- let edgeWidth = 0.5;
17372
- let alpha = smoothstep(-edgeWidth, edgeWidth, pxDist);
17373
-
17374
- if (alpha < 0.001) {
17375
- discard;
17297
+ // src/utils/error.ts
17298
+ var warnings = new Map;
17299
+ function warnOnce(key, msg) {
17300
+ if (warnings.has(key)) {
17301
+ return;
17376
17302
  }
17377
-
17378
- let msdfColor = vec4f(text.color.rgb, text.color.a * alpha);
17379
- return msdfColor;
17380
-
17381
- // Debug options:
17382
- // return text.color;
17383
- // return input.debugColor;
17384
- // return vec4f(1, 0, 1, 1); // hardcoded magenta
17385
- // return textureSample(fontTexture, fontSampler, input.texcoord);
17303
+ warnings.set(key, true);
17304
+ console.warn(msg ?? key);
17386
17305
  }
17387
- `;
17388
17306
 
17389
- // src/backends/webgpu/FontPipeline.ts
17390
- class FontPipeline {
17391
- pipeline;
17392
- font;
17393
- fontBindGroup;
17394
- maxCharCount;
17395
- constructor(pipeline, font, fontBindGroup, maxCharCount) {
17396
- this.pipeline = pipeline;
17397
- this.font = font;
17398
- this.fontBindGroup = fontBindGroup;
17399
- this.maxCharCount = maxCharCount;
17400
- }
17401
- static async create(device, font, colorFormat, maxCharCount) {
17402
- const pipeline = await pipelinePromise(device, colorFormat, font.name);
17403
- const texture = device.createTexture({
17404
- label: `MSDF font ${font.name}`,
17405
- size: [font.imageBitmap.width, font.imageBitmap.height, 1],
17406
- format: "rgba8unorm",
17407
- usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
17408
- });
17409
- device.queue.copyExternalImageToTexture({ source: font.imageBitmap }, { texture }, [font.imageBitmap.width, font.imageBitmap.height]);
17410
- const charsGpuBuffer = device.createBuffer({
17411
- label: `MSDF font ${font.name} character layout buffer`,
17412
- size: font.charCount * Float32Array.BYTES_PER_ELEMENT * 8,
17413
- usage: GPUBufferUsage.STORAGE,
17414
- mappedAtCreation: true
17415
- });
17416
- const charsArray = new Float32Array(charsGpuBuffer.getMappedRange());
17417
- charsArray.set(font.charBuffer, 0);
17418
- charsGpuBuffer.unmap();
17419
- const fontDataBuffer = device.createBuffer({
17420
- label: `MSDF font ${font.name} metadata buffer`,
17421
- size: Float32Array.BYTES_PER_ELEMENT * 4,
17422
- usage: GPUBufferUsage.UNIFORM,
17423
- mappedAtCreation: true
17424
- });
17425
- const fontDataArray = new Float32Array(fontDataBuffer.getMappedRange());
17426
- fontDataArray[0] = font.lineHeight;
17427
- fontDataBuffer.unmap();
17428
- const fontBindGroup = device.createBindGroup({
17429
- layout: pipeline.getBindGroupLayout(0),
17430
- entries: [
17431
- {
17432
- binding: 0,
17433
- resource: texture.createView()
17434
- },
17435
- {
17436
- binding: 1,
17437
- resource: device.createSampler(sampler)
17438
- },
17439
- {
17440
- binding: 2,
17441
- resource: {
17442
- buffer: charsGpuBuffer
17443
- }
17444
- },
17445
- {
17446
- binding: 3,
17447
- resource: {
17448
- buffer: fontDataBuffer
17449
- }
17450
- }
17451
- ]
17452
- });
17453
- return new FontPipeline(pipeline, font, fontBindGroup, maxCharCount);
17454
- }
17455
- }
17456
- function pipelinePromise(device, colorFormat, label) {
17457
- const shader = device.createShaderModule({
17458
- label: `${label} shader`,
17459
- code: text_wgsl_default
17460
- });
17461
- return device.createRenderPipelineAsync({
17462
- label: `${label} pipeline`,
17463
- layout: device.createPipelineLayout({
17464
- bindGroupLayouts: [
17465
- device.createBindGroupLayout(fontBindGroupLayout),
17466
- device.createBindGroupLayout(textUniformBindGroupLayout),
17467
- device.createBindGroupLayout(engineUniformBindGroupLayout)
17468
- ]
17469
- }),
17470
- vertex: {
17471
- module: shader,
17472
- entryPoint: "vertexMain"
17473
- },
17474
- fragment: {
17475
- module: shader,
17476
- entryPoint: "fragmentMain",
17477
- targets: [
17478
- {
17479
- format: colorFormat,
17480
- blend: {
17481
- color: {
17482
- srcFactor: "src-alpha",
17483
- dstFactor: "one-minus-src-alpha"
17484
- },
17485
- alpha: {
17486
- srcFactor: "one",
17487
- dstFactor: "one"
17488
- }
17489
- }
17490
- }
17491
- ]
17492
- },
17493
- primitive: {
17494
- topology: "triangle-strip",
17495
- stripIndexFormat: "uint32"
17496
- }
17497
- });
17498
- }
17499
- if (typeof GPUShaderStage === "undefined") {
17500
- globalThis.GPUShaderStage = {
17501
- VERTEX: 1,
17502
- FRAGMENT: 2,
17503
- COMPUTE: 4
17504
- };
17505
- }
17506
- var fontBindGroupLayout = {
17507
- label: "MSDF font group layout",
17508
- entries: [
17509
- {
17510
- binding: 0,
17511
- visibility: GPUShaderStage.FRAGMENT,
17512
- texture: {}
17513
- },
17514
- {
17515
- binding: 1,
17516
- visibility: GPUShaderStage.FRAGMENT,
17517
- sampler: {}
17518
- },
17519
- {
17520
- binding: 2,
17521
- visibility: GPUShaderStage.VERTEX,
17522
- buffer: { type: "read-only-storage" }
17523
- },
17524
- {
17525
- binding: 3,
17526
- visibility: GPUShaderStage.VERTEX,
17527
- buffer: {}
17528
- }
17529
- ]
17530
- };
17531
- var engineUniformBindGroupLayout = {
17532
- label: "Uniform bind group",
17533
- entries: [
17534
- {
17535
- binding: 0,
17536
- visibility: GPUShaderStage.VERTEX,
17537
- buffer: {}
17538
- }
17539
- ]
17540
- };
17541
- var sampler = {
17542
- label: "MSDF text sampler",
17543
- minFilter: "linear",
17544
- magFilter: "linear",
17545
- mipmapFilter: "linear",
17546
- maxAnisotropy: 16
17547
- };
17548
- var textUniformBindGroupLayout = {
17549
- label: "MSDF text block uniform",
17550
- entries: [
17551
- {
17552
- binding: 0,
17553
- visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
17554
- buffer: { type: "read-only-storage" }
17555
- },
17556
- {
17557
- binding: 1,
17558
- visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
17559
- buffer: { type: "read-only-storage" }
17560
- }
17561
- ]
17562
- };
17563
- // src/utils/error.ts
17564
- var warnings = new Map;
17565
- function warnOnce(key, msg) {
17566
- if (warnings.has(key)) {
17567
- return;
17568
- }
17569
- warnings.set(key, true);
17570
- console.warn(msg ?? key);
17571
- }
17572
-
17573
- // src/text/MsdfFont.ts
17574
- class MsdfFont {
17575
- id;
17576
- json;
17577
- imageBitmap;
17578
- name;
17579
- charset;
17580
- charCount;
17581
- lineHeight;
17582
- charBuffer;
17583
- #kernings;
17584
- #chars;
17585
- #fallbackCharCode;
17586
- constructor(id, json, imageBitmap) {
17587
- this.id = id;
17588
- this.json = json;
17589
- this.imageBitmap = imageBitmap;
17590
- const charArray = Object.values(json.chars);
17591
- this.charCount = charArray.length;
17592
- this.lineHeight = json.common.lineHeight;
17593
- this.charset = json.info.charset;
17594
- this.name = json.info.face;
17595
- this.#kernings = new Map;
17596
- if (json.kernings) {
17597
- for (const kearning of json.kernings) {
17598
- let charKerning = this.#kernings.get(kearning.first);
17599
- if (!charKerning) {
17600
- charKerning = new Map;
17601
- this.#kernings.set(kearning.first, charKerning);
17307
+ // src/text/MsdfFont.ts
17308
+ class MsdfFont {
17309
+ id;
17310
+ json;
17311
+ imageBitmap;
17312
+ name;
17313
+ charset;
17314
+ charCount;
17315
+ lineHeight;
17316
+ charBuffer;
17317
+ #kernings;
17318
+ #chars;
17319
+ #fallbackCharCode;
17320
+ constructor(id, json, imageBitmap) {
17321
+ this.id = id;
17322
+ this.json = json;
17323
+ this.imageBitmap = imageBitmap;
17324
+ const charArray = Object.values(json.chars);
17325
+ this.charCount = charArray.length;
17326
+ this.lineHeight = json.common.lineHeight;
17327
+ this.charset = json.info.charset;
17328
+ this.name = json.info.face;
17329
+ this.#kernings = new Map;
17330
+ if (json.kernings) {
17331
+ for (const kearning of json.kernings) {
17332
+ let charKerning = this.#kernings.get(kearning.first);
17333
+ if (!charKerning) {
17334
+ charKerning = new Map;
17335
+ this.#kernings.set(kearning.first, charKerning);
17602
17336
  }
17603
17337
  charKerning.set(kearning.second, kearning.amount);
17604
17338
  }
@@ -18294,6 +18028,624 @@ class TextNode extends SceneNode {
18294
18028
  }
18295
18029
  }
18296
18030
 
18031
+ // src/backends/webgl2/glsl/text.glsl.ts
18032
+ var vertexShader2 = `#version 300 es
18033
+ precision highp float;
18034
+
18035
+ // Engine uniforms
18036
+ uniform mat3 u_viewProjection;
18037
+
18038
+ // Per-text-block uniforms
18039
+ uniform mat3 u_textTransform;
18040
+ uniform vec4 u_textColor;
18041
+ uniform float u_fontSize;
18042
+ uniform float u_blockWidth;
18043
+ uniform float u_blockHeight;
18044
+ uniform float u_lineHeight;
18045
+
18046
+ // Character data texture (RGBA32F, 2 texels per character)
18047
+ // Texel 0: texOffset.xy, texExtent.xy
18048
+ // Texel 1: size.xy, offset.xy
18049
+ uniform sampler2D u_charData;
18050
+
18051
+ // Text buffer texture (RGBA32F, 1 texel per glyph)
18052
+ // Each texel: xy = glyph position, z = char index
18053
+ uniform sampler2D u_textBuffer;
18054
+
18055
+ // Outputs to fragment shader
18056
+ out vec2 v_texcoord;
18057
+
18058
+ // Quad vertex positions for a character (matches WGSL)
18059
+ const vec2 pos[4] = vec2[4](
18060
+ vec2(0.0, -1.0),
18061
+ vec2(1.0, -1.0),
18062
+ vec2(0.0, 0.0),
18063
+ vec2(1.0, 0.0)
18064
+ );
18065
+
18066
+ void main() {
18067
+ // gl_VertexID gives us 0-3 for the quad vertices
18068
+ // gl_InstanceID gives us which glyph we're rendering
18069
+ int vertexIndex = gl_VertexID;
18070
+ int glyphIndex = gl_InstanceID;
18071
+
18072
+ // Fetch glyph data from text buffer texture
18073
+ vec4 glyphData = texelFetch(u_textBuffer, ivec2(glyphIndex, 0), 0);
18074
+ vec2 glyphPos = glyphData.xy;
18075
+ int charIndex = int(glyphData.z);
18076
+
18077
+ // Fetch character metrics (2 texels per char)
18078
+ // Texel 0: texOffset.x, texOffset.y, texExtent.x, texExtent.y
18079
+ // Texel 1: size.x, size.y, offset.x, offset.y
18080
+ vec4 charData0 = texelFetch(u_charData, ivec2(charIndex * 2, 0), 0);
18081
+ vec4 charData1 = texelFetch(u_charData, ivec2(charIndex * 2 + 1, 0), 0);
18082
+
18083
+ vec2 texOffset = charData0.xy;
18084
+ vec2 texExtent = charData0.zw;
18085
+ vec2 charSize = charData1.xy;
18086
+ vec2 charOffset = charData1.zw;
18087
+
18088
+ // Center text vertically; origin is mid-height
18089
+ vec2 offset = vec2(0.0, -u_blockHeight / 2.0);
18090
+
18091
+ // Glyph position in ems (quad pos * size + per-char offset)
18092
+ vec2 emPos = pos[vertexIndex] * charSize + charOffset + glyphPos - offset;
18093
+ vec2 charPos = emPos * (u_fontSize / u_lineHeight);
18094
+
18095
+ // Transform position through model and view-projection matrices
18096
+ vec3 worldPos = u_textTransform * vec3(charPos, 1.0);
18097
+ vec3 clipPos = u_viewProjection * worldPos;
18098
+
18099
+ gl_Position = vec4(clipPos.xy, 0.0, 1.0);
18100
+
18101
+ // Calculate texture coordinates
18102
+ v_texcoord = pos[vertexIndex] * vec2(1.0, -1.0);
18103
+ v_texcoord *= texExtent;
18104
+ v_texcoord += texOffset;
18105
+ }
18106
+ `;
18107
+ var fragmentShader2 = `#version 300 es
18108
+ precision highp float;
18109
+
18110
+ // Font texture (MSDF atlas)
18111
+ uniform sampler2D u_fontTexture;
18112
+
18113
+ // Text color
18114
+ uniform vec4 u_textColor;
18115
+
18116
+ // Input from vertex shader
18117
+ in vec2 v_texcoord;
18118
+
18119
+ // Output color
18120
+ out vec4 fragColor;
18121
+
18122
+ // Signed distance function sampling for MSDF font rendering
18123
+ // Median of three: max(min(r,g), min(max(r,g), b))
18124
+ float sampleMsdf(vec2 texcoord) {
18125
+ vec4 c = texture(u_fontTexture, texcoord);
18126
+ return max(min(c.r, c.g), min(max(c.r, c.g), c.b));
18127
+ }
18128
+
18129
+ void main() {
18130
+ // pxRange (AKA distanceRange) comes from the msdfgen tool
18131
+ float pxRange = 4.0;
18132
+ vec2 texSize = vec2(textureSize(u_fontTexture, 0));
18133
+
18134
+ // Anti-aliasing technique by Paul Houx
18135
+ // https://github.com/Chlumsky/msdfgen/issues/22#issuecomment-234958005
18136
+ float dx = texSize.x * length(vec2(dFdx(v_texcoord.x), dFdy(v_texcoord.x)));
18137
+ float dy = texSize.y * length(vec2(dFdx(v_texcoord.y), dFdy(v_texcoord.y)));
18138
+
18139
+ float toPixels = pxRange * inversesqrt(dx * dx + dy * dy);
18140
+ float sigDist = sampleMsdf(v_texcoord) - 0.5;
18141
+ float pxDist = sigDist * toPixels;
18142
+
18143
+ float edgeWidth = 0.5;
18144
+ float alpha = smoothstep(-edgeWidth, edgeWidth, pxDist);
18145
+
18146
+ if (alpha < 0.001) {
18147
+ discard;
18148
+ }
18149
+
18150
+ fragColor = vec4(u_textColor.rgb, u_textColor.a * alpha);
18151
+ }
18152
+ `;
18153
+
18154
+ // src/backends/webgl2/WebGLTextShader.ts
18155
+ class WebGLTextShader {
18156
+ label = "text";
18157
+ font;
18158
+ maxCharCount;
18159
+ #backend;
18160
+ #pipeline;
18161
+ #program;
18162
+ #vao;
18163
+ #cpuTextBuffer;
18164
+ #cachedUniform = null;
18165
+ #uViewProjection = null;
18166
+ #uTextTransform = null;
18167
+ #uTextColor = null;
18168
+ #uFontSize = null;
18169
+ #uBlockWidth = null;
18170
+ #uBlockHeight = null;
18171
+ #uLineHeight = null;
18172
+ #uCharData = null;
18173
+ #uTextBuffer = null;
18174
+ #uFontTexture = null;
18175
+ constructor(backend, pipeline) {
18176
+ this.#backend = backend;
18177
+ this.#pipeline = pipeline;
18178
+ this.font = pipeline.font;
18179
+ this.maxCharCount = pipeline.maxCharCount;
18180
+ const gl = backend.gl;
18181
+ const vs = this.#compileShader(gl, gl.VERTEX_SHADER, vertexShader2);
18182
+ const fs = this.#compileShader(gl, gl.FRAGMENT_SHADER, fragmentShader2);
18183
+ const program = gl.createProgram();
18184
+ assert(program, "Failed to create WebGL program");
18185
+ gl.attachShader(program, vs);
18186
+ gl.attachShader(program, fs);
18187
+ gl.linkProgram(program);
18188
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
18189
+ const info = gl.getProgramInfoLog(program);
18190
+ throw new Error(`Failed to link text shader program: ${info}`);
18191
+ }
18192
+ this.#program = program;
18193
+ this.#uViewProjection = gl.getUniformLocation(program, "u_viewProjection");
18194
+ this.#uTextTransform = gl.getUniformLocation(program, "u_textTransform");
18195
+ this.#uTextColor = gl.getUniformLocation(program, "u_textColor");
18196
+ this.#uFontSize = gl.getUniformLocation(program, "u_fontSize");
18197
+ this.#uBlockWidth = gl.getUniformLocation(program, "u_blockWidth");
18198
+ this.#uBlockHeight = gl.getUniformLocation(program, "u_blockHeight");
18199
+ this.#uLineHeight = gl.getUniformLocation(program, "u_lineHeight");
18200
+ this.#uCharData = gl.getUniformLocation(program, "u_charData");
18201
+ this.#uTextBuffer = gl.getUniformLocation(program, "u_textBuffer");
18202
+ this.#uFontTexture = gl.getUniformLocation(program, "u_fontTexture");
18203
+ const vao = gl.createVertexArray();
18204
+ assert(vao, "Failed to create WebGL VAO");
18205
+ this.#vao = vao;
18206
+ this.#cpuTextBuffer = new Float32Array(this.maxCharCount * 4);
18207
+ gl.deleteShader(vs);
18208
+ gl.deleteShader(fs);
18209
+ }
18210
+ startFrame(uniform) {
18211
+ this.#cachedUniform = uniform;
18212
+ }
18213
+ processBatch(nodes) {
18214
+ if (nodes.length === 0)
18215
+ return 0;
18216
+ const gl = this.#backend.gl;
18217
+ const uniform = this.#cachedUniform;
18218
+ if (!uniform) {
18219
+ throw new Error("Tried to process batch but engine uniform is not set");
18220
+ }
18221
+ gl.useProgram(this.#program);
18222
+ gl.bindVertexArray(this.#vao);
18223
+ if (this.#uViewProjection) {
18224
+ const m3 = uniform.viewProjectionMatrix;
18225
+ const mat3x3 = new Float32Array([
18226
+ m3[0],
18227
+ m3[1],
18228
+ m3[2],
18229
+ m3[4],
18230
+ m3[5],
18231
+ m3[6],
18232
+ m3[8],
18233
+ m3[9],
18234
+ m3[10]
18235
+ ]);
18236
+ gl.uniformMatrix3fv(this.#uViewProjection, false, mat3x3);
18237
+ }
18238
+ if (this.#uLineHeight) {
18239
+ gl.uniform1f(this.#uLineHeight, this.#pipeline.lineHeight);
18240
+ }
18241
+ gl.activeTexture(gl.TEXTURE0);
18242
+ gl.bindTexture(gl.TEXTURE_2D, this.#pipeline.fontTexture);
18243
+ if (this.#uFontTexture) {
18244
+ gl.uniform1i(this.#uFontTexture, 0);
18245
+ }
18246
+ gl.activeTexture(gl.TEXTURE1);
18247
+ gl.bindTexture(gl.TEXTURE_2D, this.#pipeline.charDataTexture);
18248
+ if (this.#uCharData) {
18249
+ gl.uniform1i(this.#uCharData, 1);
18250
+ }
18251
+ gl.activeTexture(gl.TEXTURE2);
18252
+ gl.bindTexture(gl.TEXTURE_2D, this.#pipeline.textBufferTexture);
18253
+ if (this.#uTextBuffer) {
18254
+ gl.uniform1i(this.#uTextBuffer, 2);
18255
+ }
18256
+ for (const node of nodes) {
18257
+ if (!(node instanceof TextNode)) {
18258
+ console.error(node);
18259
+ throw new Error(`Tried to use WebGLTextShader on something that isn't a TextNode: ${node}`);
18260
+ }
18261
+ const text = node.text;
18262
+ const formatting = node.formatting;
18263
+ const measurements = measureText(this.font, text, formatting.wordWrap);
18264
+ const size = node.size ?? measurements;
18265
+ const fontSize = formatting.shrinkToFit ? findLargestFontSize(this.font, text, size, formatting) : formatting.fontSize;
18266
+ const actualFontSize = fontSize || DEFAULT_FONT_SIZE;
18267
+ shapeText(this.font, text, size, actualFontSize, formatting, this.#cpuTextBuffer, 0);
18268
+ this.#pipeline.updateTextBuffer(this.#cpuTextBuffer, measurements.printedCharCount);
18269
+ if (this.#uTextTransform) {
18270
+ const m3 = node.matrix;
18271
+ const mat3x3 = new Float32Array([
18272
+ m3[0],
18273
+ m3[1],
18274
+ m3[2],
18275
+ m3[4],
18276
+ m3[5],
18277
+ m3[6],
18278
+ m3[8],
18279
+ m3[9],
18280
+ m3[10]
18281
+ ]);
18282
+ gl.uniformMatrix3fv(this.#uTextTransform, false, mat3x3);
18283
+ }
18284
+ if (this.#uTextColor) {
18285
+ const tint = node.tint;
18286
+ gl.uniform4f(this.#uTextColor, tint.r, tint.g, tint.b, tint.a);
18287
+ }
18288
+ if (this.#uFontSize) {
18289
+ gl.uniform1f(this.#uFontSize, actualFontSize);
18290
+ }
18291
+ if (this.#uBlockWidth) {
18292
+ gl.uniform1f(this.#uBlockWidth, formatting.align === "center" ? 0 : measurements.width);
18293
+ }
18294
+ if (this.#uBlockHeight) {
18295
+ gl.uniform1f(this.#uBlockHeight, measurements.height);
18296
+ }
18297
+ gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, measurements.printedCharCount);
18298
+ }
18299
+ gl.bindVertexArray(null);
18300
+ return nodes.length;
18301
+ }
18302
+ endFrame() {}
18303
+ #compileShader(gl, type, source) {
18304
+ const shader = gl.createShader(type);
18305
+ assert(shader, "Failed to create WebGL shader");
18306
+ gl.shaderSource(shader, source);
18307
+ gl.compileShader(shader);
18308
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
18309
+ const info = gl.getShaderInfoLog(shader);
18310
+ const typeStr = type === gl.VERTEX_SHADER ? "vertex" : "fragment";
18311
+ gl.deleteShader(shader);
18312
+ throw new Error(`Failed to compile ${typeStr} shader: ${info}`);
18313
+ }
18314
+ return shader;
18315
+ }
18316
+ destroy() {
18317
+ const gl = this.#backend.gl;
18318
+ gl.deleteProgram(this.#program);
18319
+ gl.deleteVertexArray(this.#vao);
18320
+ this.#pipeline.destroy();
18321
+ }
18322
+ }
18323
+
18324
+ // src/backends/webgpu/wgsl/text.wgsl.ts
18325
+ var text_wgsl_default = `
18326
+ // Adapted from: https://webgpu.github.io/webgpu-samples/?sample=textRenderingMsdf
18327
+
18328
+ // Quad vertex positions for a character
18329
+ const pos = array(
18330
+ vec2f(0, -1),
18331
+ vec2f(1, -1),
18332
+ vec2f(0, 0),
18333
+ vec2f(1, 0),
18334
+ );
18335
+
18336
+ // Debug colors for visualization
18337
+ const debugColors = array(
18338
+ vec4f(1, 0, 0, 1),
18339
+ vec4f(0, 1, 0, 1),
18340
+ vec4f(0, 0, 1, 1),
18341
+ vec4f(1, 1, 1, 1),
18342
+ );
18343
+
18344
+ // Vertex input from GPU
18345
+ struct VertexInput {
18346
+ @builtin(vertex_index) vertex: u32,
18347
+ @builtin(instance_index) instance: u32,
18348
+ };
18349
+
18350
+ // Output from vertex shader to fragment shader
18351
+ struct VertexOutput {
18352
+ @builtin(position) position: vec4f,
18353
+ @location(0) texcoord: vec2f,
18354
+ @location(1) debugColor: vec4f,
18355
+ @location(2) @interpolate(flat) instanceIndex: u32,
18356
+ };
18357
+
18358
+ // Metadata for a single character glyph
18359
+ struct Char {
18360
+ texOffset: vec2f, // Offset to top-left in MSDF texture (pixels)
18361
+ texExtent: vec2f, // Size in texture (pixels)
18362
+ size: vec2f, // Glyph size in ems
18363
+ offset: vec2f, // Position offset in ems
18364
+ };
18365
+
18366
+ // Metadata for a text block
18367
+ struct TextBlockDescriptor {
18368
+ transform: mat3x3f, // Text transform matrix (model matrix)
18369
+ color: vec4f, // Text color
18370
+ fontSize: f32, // Font size
18371
+ blockWidth: f32, // Total width of text block
18372
+ blockHeight: f32, // Total height of text block
18373
+ bufferPosition: f32 // Index and length in textBuffer
18374
+ };
18375
+
18376
+ // Font bindings
18377
+ @group(0) @binding(0) var fontTexture: texture_2d<f32>;
18378
+ @group(0) @binding(1) var fontSampler: sampler;
18379
+ @group(0) @binding(2) var<storage> chars: array<Char>;
18380
+ @group(0) @binding(3) var<uniform> fontData: vec4f; // Contains line height (x)
18381
+
18382
+ // Text bindings
18383
+ @group(1) @binding(0) var<storage> texts: array<TextBlockDescriptor>;
18384
+ @group(1) @binding(1) var<storage> textBuffer: array<vec4f>; // Each vec4: xy = glyph pos, z = char index
18385
+
18386
+ // Global uniforms
18387
+ @group(2) @binding(0) var<uniform> viewProjectionMatrix: mat3x3f;
18388
+
18389
+ // Vertex shader
18390
+ @vertex
18391
+ fn vertexMain(input: VertexInput) -> VertexOutput {
18392
+ // Because the instance index is used for character indexing, we are
18393
+ // overloading the vertex index to store the instance of the text metadata.
18394
+ //
18395
+ // I.e...
18396
+ // Vertex 0-4 = Instance 0, Vertex 0-4
18397
+ // Vertex 4-8 = Instance 1, Vertex 0-4
18398
+ // Vertex 8-12 = Instance 2, Vertex 0-4
18399
+ let vertexIndex = input.vertex % 4;
18400
+ let textIndex = input.vertex / 4;
18401
+
18402
+ let text = texts[textIndex];
18403
+ let textElement = textBuffer[u32(text.bufferPosition) + input.instance];
18404
+ let char = chars[u32(textElement.z)];
18405
+
18406
+ let lineHeight = fontData.x;
18407
+ let textWidth = text.blockWidth;
18408
+ let textHeight = text.blockHeight;
18409
+
18410
+ // Center text vertically; origin is mid-height
18411
+ let offset = vec2f(0, -textHeight / 2);
18412
+
18413
+ // Glyph position in ems (quad pos * size + per-char offset)
18414
+ let emPos = pos[vertexIndex] * char.size + char.offset + textElement.xy - offset;
18415
+ let charPos = emPos * (text.fontSize / lineHeight);
18416
+
18417
+ var output: VertexOutput;
18418
+ let transformedPosition = viewProjectionMatrix * text.transform * vec3f(charPos, 1);
18419
+
18420
+ output.position = vec4f(transformedPosition, 1);
18421
+ output.texcoord = pos[vertexIndex] * vec2f(1, -1);
18422
+ output.texcoord *= char.texExtent;
18423
+ output.texcoord += char.texOffset;
18424
+ output.debugColor = debugColors[vertexIndex];
18425
+ output.instanceIndex = textIndex;
18426
+ return output;
18427
+
18428
+ // To debug - hardcode quad in bottom right quarter of the screen:
18429
+ // output.position = vec4f(pos[input.vertex], 0, 1);
18430
+ }
18431
+
18432
+ // Signed distance function sampling for MSDF font rendering
18433
+ fn sampleMsdf(texcoord: vec2f) -> f32 {
18434
+ let c = textureSample(fontTexture, fontSampler, texcoord);
18435
+ return max(min(c.r, c.g), min(max(c.r, c.g), c.b));
18436
+ }
18437
+
18438
+ // Fragment shader
18439
+ // Anti-aliasing technique by Paul Houx
18440
+ // more details here:
18441
+ // https://github.com/Chlumsky/msdfgen/issues/22#issuecomment-234958005
18442
+ @fragment
18443
+ fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {
18444
+ let text = texts[input.instanceIndex];
18445
+
18446
+ // pxRange (AKA distanceRange) comes from the msdfgen tool.
18447
+ let pxRange = 4.0;
18448
+ let texSize = vec2f(textureDimensions(fontTexture, 0));
18449
+
18450
+ let dx = texSize.x * length(vec2f(dpdxFine(input.texcoord.x), dpdyFine(input.texcoord.x)));
18451
+ let dy = texSize.y * length(vec2f(dpdxFine(input.texcoord.y), dpdyFine(input.texcoord.y)));
18452
+
18453
+ let toPixels = pxRange * inverseSqrt(dx * dx + dy * dy);
18454
+ let sigDist = sampleMsdf(input.texcoord) - 0.5;
18455
+ let pxDist = sigDist * toPixels;
18456
+
18457
+ let edgeWidth = 0.5;
18458
+ let alpha = smoothstep(-edgeWidth, edgeWidth, pxDist);
18459
+
18460
+ if (alpha < 0.001) {
18461
+ discard;
18462
+ }
18463
+
18464
+ let msdfColor = vec4f(text.color.rgb, text.color.a * alpha);
18465
+ return msdfColor;
18466
+
18467
+ // Debug options:
18468
+ // return text.color;
18469
+ // return input.debugColor;
18470
+ // return vec4f(1, 0, 1, 1); // hardcoded magenta
18471
+ // return textureSample(fontTexture, fontSampler, input.texcoord);
18472
+ }
18473
+ `;
18474
+
18475
+ // src/backends/webgpu/FontPipeline.ts
18476
+ class FontPipeline {
18477
+ pipeline;
18478
+ font;
18479
+ fontBindGroup;
18480
+ maxCharCount;
18481
+ constructor(pipeline, font, fontBindGroup, maxCharCount) {
18482
+ this.pipeline = pipeline;
18483
+ this.font = font;
18484
+ this.fontBindGroup = fontBindGroup;
18485
+ this.maxCharCount = maxCharCount;
18486
+ }
18487
+ static async create(device, font, colorFormat, maxCharCount) {
18488
+ const pipeline = await pipelinePromise(device, colorFormat, font.name);
18489
+ const texture = device.createTexture({
18490
+ label: `MSDF font ${font.name}`,
18491
+ size: [font.imageBitmap.width, font.imageBitmap.height, 1],
18492
+ format: "rgba8unorm",
18493
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
18494
+ });
18495
+ device.queue.copyExternalImageToTexture({ source: font.imageBitmap }, { texture }, [font.imageBitmap.width, font.imageBitmap.height]);
18496
+ const charsGpuBuffer = device.createBuffer({
18497
+ label: `MSDF font ${font.name} character layout buffer`,
18498
+ size: font.charCount * Float32Array.BYTES_PER_ELEMENT * 8,
18499
+ usage: GPUBufferUsage.STORAGE,
18500
+ mappedAtCreation: true
18501
+ });
18502
+ const charsArray = new Float32Array(charsGpuBuffer.getMappedRange());
18503
+ charsArray.set(font.charBuffer, 0);
18504
+ charsGpuBuffer.unmap();
18505
+ const fontDataBuffer = device.createBuffer({
18506
+ label: `MSDF font ${font.name} metadata buffer`,
18507
+ size: Float32Array.BYTES_PER_ELEMENT * 4,
18508
+ usage: GPUBufferUsage.UNIFORM,
18509
+ mappedAtCreation: true
18510
+ });
18511
+ const fontDataArray = new Float32Array(fontDataBuffer.getMappedRange());
18512
+ fontDataArray[0] = font.lineHeight;
18513
+ fontDataBuffer.unmap();
18514
+ const fontBindGroup = device.createBindGroup({
18515
+ layout: pipeline.getBindGroupLayout(0),
18516
+ entries: [
18517
+ {
18518
+ binding: 0,
18519
+ resource: texture.createView()
18520
+ },
18521
+ {
18522
+ binding: 1,
18523
+ resource: device.createSampler(sampler)
18524
+ },
18525
+ {
18526
+ binding: 2,
18527
+ resource: {
18528
+ buffer: charsGpuBuffer
18529
+ }
18530
+ },
18531
+ {
18532
+ binding: 3,
18533
+ resource: {
18534
+ buffer: fontDataBuffer
18535
+ }
18536
+ }
18537
+ ]
18538
+ });
18539
+ return new FontPipeline(pipeline, font, fontBindGroup, maxCharCount);
18540
+ }
18541
+ }
18542
+ function pipelinePromise(device, colorFormat, label) {
18543
+ const shader = device.createShaderModule({
18544
+ label: `${label} shader`,
18545
+ code: text_wgsl_default
18546
+ });
18547
+ return device.createRenderPipelineAsync({
18548
+ label: `${label} pipeline`,
18549
+ layout: device.createPipelineLayout({
18550
+ bindGroupLayouts: [
18551
+ device.createBindGroupLayout(fontBindGroupLayout),
18552
+ device.createBindGroupLayout(textUniformBindGroupLayout),
18553
+ device.createBindGroupLayout(engineUniformBindGroupLayout)
18554
+ ]
18555
+ }),
18556
+ vertex: {
18557
+ module: shader,
18558
+ entryPoint: "vertexMain"
18559
+ },
18560
+ fragment: {
18561
+ module: shader,
18562
+ entryPoint: "fragmentMain",
18563
+ targets: [
18564
+ {
18565
+ format: colorFormat,
18566
+ blend: {
18567
+ color: {
18568
+ srcFactor: "src-alpha",
18569
+ dstFactor: "one-minus-src-alpha"
18570
+ },
18571
+ alpha: {
18572
+ srcFactor: "one",
18573
+ dstFactor: "one"
18574
+ }
18575
+ }
18576
+ }
18577
+ ]
18578
+ },
18579
+ primitive: {
18580
+ topology: "triangle-strip",
18581
+ stripIndexFormat: "uint32"
18582
+ }
18583
+ });
18584
+ }
18585
+ if (typeof GPUShaderStage === "undefined") {
18586
+ globalThis.GPUShaderStage = {
18587
+ VERTEX: 1,
18588
+ FRAGMENT: 2,
18589
+ COMPUTE: 4
18590
+ };
18591
+ }
18592
+ var fontBindGroupLayout = {
18593
+ label: "MSDF font group layout",
18594
+ entries: [
18595
+ {
18596
+ binding: 0,
18597
+ visibility: GPUShaderStage.FRAGMENT,
18598
+ texture: {}
18599
+ },
18600
+ {
18601
+ binding: 1,
18602
+ visibility: GPUShaderStage.FRAGMENT,
18603
+ sampler: {}
18604
+ },
18605
+ {
18606
+ binding: 2,
18607
+ visibility: GPUShaderStage.VERTEX,
18608
+ buffer: { type: "read-only-storage" }
18609
+ },
18610
+ {
18611
+ binding: 3,
18612
+ visibility: GPUShaderStage.VERTEX,
18613
+ buffer: {}
18614
+ }
18615
+ ]
18616
+ };
18617
+ var engineUniformBindGroupLayout = {
18618
+ label: "Uniform bind group",
18619
+ entries: [
18620
+ {
18621
+ binding: 0,
18622
+ visibility: GPUShaderStage.VERTEX,
18623
+ buffer: {}
18624
+ }
18625
+ ]
18626
+ };
18627
+ var sampler = {
18628
+ label: "MSDF text sampler",
18629
+ minFilter: "linear",
18630
+ magFilter: "linear",
18631
+ mipmapFilter: "linear",
18632
+ maxAnisotropy: 16
18633
+ };
18634
+ var textUniformBindGroupLayout = {
18635
+ label: "MSDF text block uniform",
18636
+ entries: [
18637
+ {
18638
+ binding: 0,
18639
+ visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
18640
+ buffer: { type: "read-only-storage" }
18641
+ },
18642
+ {
18643
+ binding: 1,
18644
+ visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
18645
+ buffer: { type: "read-only-storage" }
18646
+ }
18647
+ ]
18648
+ };
18297
18649
  // src/backends/webgpu/WebGPUTextShader.ts
18298
18650
  var deets = new _t2(text_wgsl_default);
18299
18651
  var struct = deets.structs.find((s3) => s3.name === "TextBlockDescriptor");
@@ -19723,7 +20075,9 @@ class AssetManager {
19723
20075
  const textShader = new WebGPUTextShader(webgpuBackend, fontPipeline, font, presentationFormat, limits.instanceCount);
19724
20076
  this.#fonts.set(id, textShader);
19725
20077
  } else {
19726
- const textShader = new WebGLTextShader(font, limits.maxTextLength);
20078
+ const webglBackend = this.#backend;
20079
+ const fontPipeline = WebGLFontPipeline.create(webglBackend.gl, font, limits.maxTextLength);
20080
+ const textShader = new WebGLTextShader(webglBackend, fontPipeline);
19727
20081
  this.#fonts.set(id, textShader);
19728
20082
  }
19729
20083
  return id;
@@ -20444,5 +20798,5 @@ export {
20444
20798
  AssetManager
20445
20799
  };
20446
20800
 
20447
- //# debugId=7918C9E3F46FD35864756E2164756E21
20801
+ //# debugId=4A88BA8B6CA05C3D64756E2164756E21
20448
20802
  //# sourceMappingURL=mod.js.map