@bloopjs/toodle 0.1.5 → 0.1.7
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/backends/IBackendShader.d.ts +2 -0
- package/dist/backends/IBackendShader.d.ts.map +1 -1
- package/dist/backends/webgl2/WebGLQuadShader.d.ts +1 -0
- package/dist/backends/webgl2/WebGLQuadShader.d.ts.map +1 -1
- package/dist/backends/webgl2/WebGLTextShader.d.ts +1 -0
- package/dist/backends/webgl2/WebGLTextShader.d.ts.map +1 -1
- package/dist/backends/webgpu/WebGPUTextShader.d.ts +1 -0
- package/dist/backends/webgpu/WebGPUTextShader.d.ts.map +1 -1
- package/dist/mod.js +1474 -1746
- package/dist/mod.js.map +18 -20
- package/dist/textures/Bundles.d.ts.map +1 -1
- package/dist/textures/util.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/backends/IBackendShader.ts +3 -0
- package/src/backends/webgl2/WebGLQuadShader.ts +2 -0
- package/src/backends/webgl2/WebGLTextShader.ts +1 -0
- package/src/backends/webgpu/WebGPUTextShader.ts +1 -0
- package/src/textures/Bundles.ts +9 -4
- package/src/textures/util.ts +8 -4
package/dist/mod.js
CHANGED
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
var __defProp = Object.defineProperty;
|
|
2
|
-
var __export = (target, all) => {
|
|
3
|
-
for (var name in all)
|
|
4
|
-
__defProp(target, name, {
|
|
5
|
-
get: all[name],
|
|
6
|
-
enumerable: true,
|
|
7
|
-
configurable: true,
|
|
8
|
-
set: (newValue) => all[name] = () => newValue
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
|
|
12
1
|
// src/limits.ts
|
|
13
2
|
var DEFAULT_LIMITS = {
|
|
14
3
|
instanceCount: 1024 * 2,
|
|
@@ -3445,18 +3434,6 @@ var {
|
|
|
3445
3434
|
vec4: vec4n
|
|
3446
3435
|
} = wgpuMatrixAPI(ZeroArray, Array, Array, Array, Array, Array);
|
|
3447
3436
|
|
|
3448
|
-
// src/backends/mod.ts
|
|
3449
|
-
var exports_mod = {};
|
|
3450
|
-
__export(exports_mod, {
|
|
3451
|
-
isWebGPUAvailable: () => isWebGPUAvailable,
|
|
3452
|
-
isWebGL2Available: () => isWebGL2Available,
|
|
3453
|
-
detectBackend: () => detectBackend,
|
|
3454
|
-
defaultGLSLFragmentShader: () => defaultFragmentShader,
|
|
3455
|
-
WebGPUBackend: () => WebGPUBackend,
|
|
3456
|
-
WebGLBackend: () => WebGLBackend,
|
|
3457
|
-
PostProcessDefaults: () => PostProcessDefaults
|
|
3458
|
-
});
|
|
3459
|
-
|
|
3460
3437
|
// src/backends/detection.ts
|
|
3461
3438
|
async function detectBackend() {
|
|
3462
3439
|
if (typeof navigator !== "undefined" && "gpu" in navigator) {
|
|
@@ -3469,21 +3446,13 @@ async function detectBackend() {
|
|
|
3469
3446
|
}
|
|
3470
3447
|
return "webgl2";
|
|
3471
3448
|
}
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
if (typeof document === "undefined") {
|
|
3477
|
-
return false;
|
|
3478
|
-
}
|
|
3479
|
-
try {
|
|
3480
|
-
const canvas = document.createElement("canvas");
|
|
3481
|
-
const gl = canvas.getContext("webgl2");
|
|
3482
|
-
return gl !== null;
|
|
3483
|
-
} catch {
|
|
3484
|
-
return false;
|
|
3449
|
+
// src/utils/assert.ts
|
|
3450
|
+
function assert(condition, message) {
|
|
3451
|
+
if (!condition) {
|
|
3452
|
+
throw new Error(message);
|
|
3485
3453
|
}
|
|
3486
3454
|
}
|
|
3455
|
+
|
|
3487
3456
|
// src/backends/webgl2/glsl/quad.glsl.ts
|
|
3488
3457
|
var vertexShader = `#version 300 es
|
|
3489
3458
|
precision highp float;
|
|
@@ -3588,13 +3557,6 @@ void main() {
|
|
|
3588
3557
|
}
|
|
3589
3558
|
}
|
|
3590
3559
|
`;
|
|
3591
|
-
var defaultFragmentShader = fragmentShader;
|
|
3592
|
-
// src/utils/assert.ts
|
|
3593
|
-
function assert(condition, message) {
|
|
3594
|
-
if (!condition) {
|
|
3595
|
-
throw new Error(message);
|
|
3596
|
-
}
|
|
3597
|
-
}
|
|
3598
3560
|
|
|
3599
3561
|
// src/backends/webgl2/WebGLQuadShader.ts
|
|
3600
3562
|
var INSTANCE_FLOATS = 28;
|
|
@@ -3602,6 +3564,7 @@ var INSTANCE_BYTES = INSTANCE_FLOATS * Float32Array.BYTES_PER_ELEMENT;
|
|
|
3602
3564
|
|
|
3603
3565
|
class WebGLQuadShader {
|
|
3604
3566
|
label;
|
|
3567
|
+
code;
|
|
3605
3568
|
#backend;
|
|
3606
3569
|
#atlas;
|
|
3607
3570
|
#program;
|
|
@@ -3620,6 +3583,7 @@ class WebGLQuadShader {
|
|
|
3620
3583
|
}
|
|
3621
3584
|
this.#atlas = atlas;
|
|
3622
3585
|
this.label = label;
|
|
3586
|
+
this.code = userFragmentShader ?? fragmentShader;
|
|
3623
3587
|
this.#backend = backend;
|
|
3624
3588
|
this.#instanceCount = instanceCount;
|
|
3625
3589
|
const gl = backend.gl;
|
|
@@ -3752,7 +3716,7 @@ class WebGLQuadShader {
|
|
|
3752
3716
|
}
|
|
3753
3717
|
|
|
3754
3718
|
// src/backends/webgl2/WebGLBackend.ts
|
|
3755
|
-
class
|
|
3719
|
+
class WebGLBackend2 {
|
|
3756
3720
|
type = "webgl2";
|
|
3757
3721
|
limits;
|
|
3758
3722
|
atlasSize;
|
|
@@ -3782,7 +3746,7 @@ class WebGLBackend {
|
|
|
3782
3746
|
...DEFAULT_LIMITS,
|
|
3783
3747
|
...options.limits
|
|
3784
3748
|
};
|
|
3785
|
-
const backend = new
|
|
3749
|
+
const backend = new WebGLBackend2(gl, canvas, limits);
|
|
3786
3750
|
backend.createTextureAtlas("default", {
|
|
3787
3751
|
format: options.format ?? "rgba8unorm",
|
|
3788
3752
|
layers: limits.textureArrayLayers,
|
|
@@ -3871,58 +3835,6 @@ class WebGLBackend {
|
|
|
3871
3835
|
return this.getTextureAtlas("default")?.format ?? "rgba8unorm";
|
|
3872
3836
|
}
|
|
3873
3837
|
}
|
|
3874
|
-
// src/backends/webgpu/postprocess/mod.ts
|
|
3875
|
-
var PostProcessDefaults = {
|
|
3876
|
-
sampler(device) {
|
|
3877
|
-
return device.createSampler({
|
|
3878
|
-
label: "toodle post process sampler",
|
|
3879
|
-
magFilter: "linear",
|
|
3880
|
-
minFilter: "linear",
|
|
3881
|
-
mipmapFilter: "linear",
|
|
3882
|
-
addressModeU: "clamp-to-edge",
|
|
3883
|
-
addressModeV: "clamp-to-edge"
|
|
3884
|
-
});
|
|
3885
|
-
},
|
|
3886
|
-
vertexBufferLayout(_device) {
|
|
3887
|
-
return {
|
|
3888
|
-
arrayStride: 4 * 4,
|
|
3889
|
-
attributes: [{ shaderLocation: 0, offset: 0, format: "float32x2" }]
|
|
3890
|
-
};
|
|
3891
|
-
},
|
|
3892
|
-
vertexShader(device) {
|
|
3893
|
-
return device.createShaderModule({
|
|
3894
|
-
label: "toodle post process vertex shader",
|
|
3895
|
-
code: `
|
|
3896
|
-
struct VertexOut {
|
|
3897
|
-
@builtin(position) position: vec4<f32>,
|
|
3898
|
-
@location(0) uv: vec2<f32>,
|
|
3899
|
-
};
|
|
3900
|
-
|
|
3901
|
-
const enginePosLookup = array(vec2f(-1, 1), vec2f(-1, -1), vec2f(1, 1), vec2f(1, -1));
|
|
3902
|
-
const engineUvLookup = array(vec2f(0, 0), vec2f(0, 1), vec2f(1, 0), vec2f(1, 1));
|
|
3903
|
-
|
|
3904
|
-
@vertex
|
|
3905
|
-
fn vs_main(@builtin(vertex_index) vertexIndex: u32) -> VertexOut {
|
|
3906
|
-
var out: VertexOut;
|
|
3907
|
-
out.position = vec4(enginePosLookup[vertexIndex], 0.0, 1.0);
|
|
3908
|
-
out.uv = engineUvLookup[vertexIndex];
|
|
3909
|
-
return out;
|
|
3910
|
-
}
|
|
3911
|
-
`
|
|
3912
|
-
});
|
|
3913
|
-
},
|
|
3914
|
-
pipelineDescriptor(device) {
|
|
3915
|
-
return {
|
|
3916
|
-
label: "toodle post process pipeline descriptor",
|
|
3917
|
-
layout: "auto",
|
|
3918
|
-
primitive: { topology: "triangle-strip" },
|
|
3919
|
-
vertex: {
|
|
3920
|
-
buffers: [PostProcessDefaults.vertexBufferLayout(device)],
|
|
3921
|
-
module: PostProcessDefaults.vertexShader(device)
|
|
3922
|
-
}
|
|
3923
|
-
};
|
|
3924
|
-
}
|
|
3925
|
-
};
|
|
3926
3838
|
// node_modules/webgpu-utils/dist/1.x/webgpu-utils.module.js
|
|
3927
3839
|
var roundUpToMultipleOf = (v, multiple) => ((v + multiple - 1) / multiple | 0) * multiple;
|
|
3928
3840
|
function keysOf(obj) {
|
|
@@ -17029,7 +16941,7 @@ function convertBlendMode(mode) {
|
|
|
17029
16941
|
}
|
|
17030
16942
|
|
|
17031
16943
|
// src/backends/webgpu/WebGPUBackend.ts
|
|
17032
|
-
class
|
|
16944
|
+
class WebGPUBackend2 {
|
|
17033
16945
|
type = "webgpu";
|
|
17034
16946
|
limits;
|
|
17035
16947
|
atlasSize;
|
|
@@ -17074,7 +16986,7 @@ class WebGPUBackend {
|
|
|
17074
16986
|
...DEFAULT_LIMITS,
|
|
17075
16987
|
...options.limits
|
|
17076
16988
|
};
|
|
17077
|
-
const backend = new
|
|
16989
|
+
const backend = new WebGPUBackend2(device, context, presentationFormat, limits, canvas);
|
|
17078
16990
|
backend.createTextureAtlas("default", {
|
|
17079
16991
|
format: options.format ?? "rgba8unorm",
|
|
17080
16992
|
layers: limits.textureArrayLayers,
|
|
@@ -17219,414 +17131,150 @@ class WebGPUBackend {
|
|
|
17219
17131
|
return this.#renderPass;
|
|
17220
17132
|
}
|
|
17221
17133
|
}
|
|
17222
|
-
// src/
|
|
17223
|
-
|
|
17224
|
-
|
|
17225
|
-
|
|
17226
|
-
charDataTexture;
|
|
17227
|
-
textBufferTexture;
|
|
17228
|
-
maxCharCount;
|
|
17229
|
-
lineHeight;
|
|
17230
|
-
#gl;
|
|
17231
|
-
constructor(gl, font, fontTexture, charDataTexture, textBufferTexture, maxCharCount) {
|
|
17232
|
-
this.#gl = gl;
|
|
17233
|
-
this.font = font;
|
|
17234
|
-
this.fontTexture = fontTexture;
|
|
17235
|
-
this.charDataTexture = charDataTexture;
|
|
17236
|
-
this.textBufferTexture = textBufferTexture;
|
|
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);
|
|
17288
|
-
}
|
|
17289
|
-
destroy() {
|
|
17290
|
-
const gl = this.#gl;
|
|
17291
|
-
gl.deleteTexture(this.fontTexture);
|
|
17292
|
-
gl.deleteTexture(this.charDataTexture);
|
|
17293
|
-
gl.deleteTexture(this.textBufferTexture);
|
|
17294
|
-
}
|
|
17134
|
+
// src/math/matrix.ts
|
|
17135
|
+
function createProjectionMatrix(resolution, dst) {
|
|
17136
|
+
const { width, height } = resolution;
|
|
17137
|
+
return mat3.scaling([2 / width, 2 / height], dst);
|
|
17295
17138
|
}
|
|
17296
|
-
|
|
17297
|
-
|
|
17298
|
-
|
|
17299
|
-
|
|
17300
|
-
|
|
17301
|
-
|
|
17302
|
-
|
|
17303
|
-
|
|
17304
|
-
|
|
17139
|
+
function createViewMatrix(camera, target) {
|
|
17140
|
+
const matrix = mat3.identity(target);
|
|
17141
|
+
mat3.scale(matrix, [camera.zoom, camera.zoom], matrix);
|
|
17142
|
+
mat3.rotate(matrix, camera.rotationRadians, matrix);
|
|
17143
|
+
mat3.translate(matrix, [-camera.x, -camera.y], matrix);
|
|
17144
|
+
return matrix;
|
|
17145
|
+
}
|
|
17146
|
+
function createModelMatrix(transform, base) {
|
|
17147
|
+
mat3.translate(base, [transform.position.x, transform.position.y], base);
|
|
17148
|
+
mat3.rotate(base, transform.rotation, base);
|
|
17149
|
+
mat3.scale(base, [transform.scale.x, transform.scale.y], base);
|
|
17150
|
+
return base;
|
|
17151
|
+
}
|
|
17152
|
+
function convertScreenToWorld(screenCoordinates, camera, projectionMatrix, resolution) {
|
|
17153
|
+
const inverseViewProjectionMatrix = mat3.mul(mat3.inverse(camera.matrix), mat3.inverse(projectionMatrix));
|
|
17154
|
+
const normalizedDeviceCoordinates = {
|
|
17155
|
+
x: 2 * screenCoordinates.x / resolution.width - 1,
|
|
17156
|
+
y: 1 - 2 * screenCoordinates.y / resolution.height
|
|
17157
|
+
};
|
|
17158
|
+
return transformPoint(normalizedDeviceCoordinates, inverseViewProjectionMatrix);
|
|
17159
|
+
}
|
|
17160
|
+
function convertWorldToScreen(worldCoordinates, camera, projectionMatrix, resolution) {
|
|
17161
|
+
const viewProjectionMatrix = mat3.mul(projectionMatrix, camera.matrix);
|
|
17162
|
+
const ndcPoint = transformPoint(worldCoordinates, viewProjectionMatrix);
|
|
17163
|
+
return {
|
|
17164
|
+
x: (ndcPoint.x + 1) * resolution.width / 2,
|
|
17165
|
+
y: (1 - ndcPoint.y) * resolution.height / 2
|
|
17166
|
+
};
|
|
17167
|
+
}
|
|
17168
|
+
function transformPoint(point, matrix) {
|
|
17169
|
+
const result = vec2.transformMat3([point.x, point.y], matrix);
|
|
17170
|
+
return {
|
|
17171
|
+
x: result[0],
|
|
17172
|
+
y: result[1]
|
|
17173
|
+
};
|
|
17305
17174
|
}
|
|
17306
17175
|
|
|
17307
|
-
// src/
|
|
17308
|
-
class
|
|
17309
|
-
|
|
17310
|
-
|
|
17311
|
-
|
|
17312
|
-
|
|
17313
|
-
|
|
17314
|
-
|
|
17315
|
-
|
|
17316
|
-
|
|
17317
|
-
|
|
17318
|
-
|
|
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);
|
|
17336
|
-
}
|
|
17337
|
-
charKerning.set(kearning.second, kearning.amount);
|
|
17338
|
-
}
|
|
17176
|
+
// src/scene/Batcher.ts
|
|
17177
|
+
class Batcher {
|
|
17178
|
+
nodes = [];
|
|
17179
|
+
layers = [];
|
|
17180
|
+
pipelines = [];
|
|
17181
|
+
enqueue(node) {
|
|
17182
|
+
if (node.renderComponent && node.isActive) {
|
|
17183
|
+
this.nodes.push(node);
|
|
17184
|
+
const z3 = node.layer;
|
|
17185
|
+
const layer = this.#findOrCreateLayer(z3);
|
|
17186
|
+
const pipeline = this.#findOrCreatePipeline(layer, node.renderComponent.shader);
|
|
17187
|
+
pipeline.nodes.push(node);
|
|
17339
17188
|
}
|
|
17340
|
-
|
|
17341
|
-
|
|
17342
|
-
this.charBuffer = new Float32Array(charCount * 8);
|
|
17343
|
-
let offset = 0;
|
|
17344
|
-
const u3 = 1 / json.common.scaleW;
|
|
17345
|
-
const v3 = 1 / json.common.scaleH;
|
|
17346
|
-
for (const [i3, char] of json.chars.entries()) {
|
|
17347
|
-
this.#chars.set(char.id, char);
|
|
17348
|
-
this.#chars.get(char.id).charIndex = i3;
|
|
17349
|
-
this.charBuffer[offset] = char.x * u3;
|
|
17350
|
-
this.charBuffer[offset + 1] = char.y * v3;
|
|
17351
|
-
this.charBuffer[offset + 2] = char.width * u3;
|
|
17352
|
-
this.charBuffer[offset + 3] = char.height * v3;
|
|
17353
|
-
this.charBuffer[offset + 4] = char.width;
|
|
17354
|
-
this.charBuffer[offset + 5] = char.height;
|
|
17355
|
-
this.charBuffer[offset + 6] = char.xoffset;
|
|
17356
|
-
this.charBuffer[offset + 7] = -char.yoffset;
|
|
17357
|
-
offset += 8;
|
|
17189
|
+
for (const kid of node.kids) {
|
|
17190
|
+
this.enqueue(kid);
|
|
17358
17191
|
}
|
|
17359
17192
|
}
|
|
17360
|
-
|
|
17361
|
-
|
|
17362
|
-
|
|
17363
|
-
|
|
17364
|
-
|
|
17365
|
-
|
|
17193
|
+
flush() {
|
|
17194
|
+
this.nodes = [];
|
|
17195
|
+
this.layers = [];
|
|
17196
|
+
this.pipelines = [];
|
|
17197
|
+
}
|
|
17198
|
+
#findOrCreateLayer(z3) {
|
|
17199
|
+
let layer = this.layers.find((l3) => l3.z === z3);
|
|
17200
|
+
if (!layer) {
|
|
17201
|
+
layer = { z: z3, pipelines: [] };
|
|
17202
|
+
this.layers.push(layer);
|
|
17203
|
+
this.layers.sort((a3, b3) => a3.z - b3.z);
|
|
17366
17204
|
}
|
|
17367
|
-
return
|
|
17205
|
+
return layer;
|
|
17368
17206
|
}
|
|
17369
|
-
|
|
17370
|
-
|
|
17371
|
-
if (
|
|
17372
|
-
|
|
17373
|
-
|
|
17374
|
-
|
|
17375
|
-
}
|
|
17376
|
-
}
|
|
17377
|
-
return char.xadvance;
|
|
17378
|
-
}
|
|
17379
|
-
static async create(id, fontJsonUrl) {
|
|
17380
|
-
const response = await fetch(fontJsonUrl);
|
|
17381
|
-
const json = await response.json();
|
|
17382
|
-
const i3 = fontJsonUrl.href.lastIndexOf("/");
|
|
17383
|
-
const baseUrl = i3 !== -1 ? fontJsonUrl.href.substring(0, i3 + 1) : undefined;
|
|
17384
|
-
if (json.pages.length < 1) {
|
|
17385
|
-
throw new Error(`Can't create an msdf font without a reference to the page url in the json`);
|
|
17386
|
-
}
|
|
17387
|
-
if (json.pages.length > 1) {
|
|
17388
|
-
throw new Error(`Can't create an msdf font with more than one page`);
|
|
17389
|
-
}
|
|
17390
|
-
const textureUrl = baseUrl + json.pages[0];
|
|
17391
|
-
const textureResponse = await fetch(textureUrl);
|
|
17392
|
-
const bitmap = await createImageBitmap(await textureResponse.blob());
|
|
17393
|
-
return new MsdfFont(id, json, bitmap);
|
|
17394
|
-
}
|
|
17395
|
-
set fallbackCharacter(character) {
|
|
17396
|
-
const charCode = character.charCodeAt(0);
|
|
17397
|
-
if (this.#chars.has(charCode)) {
|
|
17398
|
-
this.#fallbackCharCode = charCode;
|
|
17399
|
-
} else {
|
|
17400
|
-
const fallbackCode = this.#chars.keys().toArray()[0];
|
|
17401
|
-
console.warn(`${character} character does not exist in font ${this.name} defaulting to "${this.#chars.get(fallbackCode)?.char}".`);
|
|
17402
|
-
this.#fallbackCharCode = fallbackCode;
|
|
17207
|
+
#findOrCreatePipeline(layer, shader) {
|
|
17208
|
+
let pipeline = layer.pipelines.find((p3) => p3.shader === shader);
|
|
17209
|
+
if (!pipeline) {
|
|
17210
|
+
pipeline = { shader, nodes: [] };
|
|
17211
|
+
layer.pipelines.push(pipeline);
|
|
17212
|
+
this.pipelines.push(pipeline);
|
|
17403
17213
|
}
|
|
17214
|
+
return pipeline;
|
|
17404
17215
|
}
|
|
17405
17216
|
}
|
|
17406
17217
|
|
|
17407
|
-
// src/
|
|
17408
|
-
|
|
17409
|
-
|
|
17410
|
-
|
|
17411
|
-
|
|
17412
|
-
|
|
17413
|
-
|
|
17414
|
-
|
|
17415
|
-
|
|
17416
|
-
|
|
17417
|
-
|
|
17218
|
+
// src/math/angle.ts
|
|
17219
|
+
function deg2rad(degrees) {
|
|
17220
|
+
return degrees * (Math.PI / 180);
|
|
17221
|
+
}
|
|
17222
|
+
function rad2deg(radians) {
|
|
17223
|
+
return radians * (180 / Math.PI);
|
|
17224
|
+
}
|
|
17225
|
+
|
|
17226
|
+
// src/scene/Camera.ts
|
|
17227
|
+
class Camera {
|
|
17228
|
+
#position = { x: 0, y: 0 };
|
|
17229
|
+
#zoom = 1;
|
|
17230
|
+
#rotation = 0;
|
|
17231
|
+
#isDirty = true;
|
|
17232
|
+
#matrix = mat3.create();
|
|
17233
|
+
get zoom() {
|
|
17234
|
+
return this.#zoom;
|
|
17418
17235
|
}
|
|
17419
|
-
|
|
17420
|
-
|
|
17421
|
-
|
|
17422
|
-
if (alignment === "center") {
|
|
17423
|
-
lineOffset = measurements.width * -0.5 - (measurements.width - measurements.lineWidths[glyph.line]) * -0.5;
|
|
17424
|
-
} else if (alignment === "right") {
|
|
17425
|
-
const blockSizeEm = blockSize.width / em2px;
|
|
17426
|
-
const delta = measurements.width - measurements.lineWidths[glyph.line];
|
|
17427
|
-
lineOffset = (hackHasExplicitBlock ? blockSizeEm / 2 : measurements.width / 2) - measurements.width + delta;
|
|
17428
|
-
} else if (alignment === "left") {
|
|
17429
|
-
const blockSizeEm = blockSize.width / em2px;
|
|
17430
|
-
lineOffset = hackHasExplicitBlock ? -blockSizeEm / 2 : -measurements.width / 2;
|
|
17431
|
-
}
|
|
17432
|
-
if (debug && debugData) {
|
|
17433
|
-
debugData.push({
|
|
17434
|
-
line: glyph.line,
|
|
17435
|
-
word: word.glyphs.map((g3) => g3.char.char).join(""),
|
|
17436
|
-
glyph: glyph.char.char,
|
|
17437
|
-
startX: word.startX,
|
|
17438
|
-
glyphX: glyph.offset[0],
|
|
17439
|
-
advance: glyph.char.xadvance,
|
|
17440
|
-
lineOffset,
|
|
17441
|
-
startY: word.startY,
|
|
17442
|
-
glyphY: glyph.offset[1]
|
|
17443
|
-
});
|
|
17444
|
-
}
|
|
17445
|
-
textArray[offset] = word.startX + glyph.offset[0] + lineOffset;
|
|
17446
|
-
textArray[offset + 1] = word.startY + glyph.offset[1];
|
|
17447
|
-
textArray[offset + 2] = glyph.char.charIndex;
|
|
17448
|
-
offset += 4;
|
|
17449
|
-
}
|
|
17236
|
+
set zoom(value) {
|
|
17237
|
+
this.#zoom = value;
|
|
17238
|
+
this.setDirty();
|
|
17450
17239
|
}
|
|
17451
|
-
|
|
17452
|
-
|
|
17240
|
+
get rotation() {
|
|
17241
|
+
return rad2deg(this.#rotation);
|
|
17453
17242
|
}
|
|
17454
|
-
|
|
17455
|
-
|
|
17456
|
-
|
|
17457
|
-
const lineWidths = [];
|
|
17458
|
-
let textOffsetX = 0;
|
|
17459
|
-
let textOffsetY = 0;
|
|
17460
|
-
let line = 0;
|
|
17461
|
-
let printedCharCount = 0;
|
|
17462
|
-
let nextCharCode = text.charCodeAt(0);
|
|
17463
|
-
let word = { glyphs: [], width: 0, startX: 0, startY: 0 };
|
|
17464
|
-
const words = [];
|
|
17465
|
-
for (let i3 = 0;i3 < text.length; i3++) {
|
|
17466
|
-
const isLastLetter = i3 === text.length - 1;
|
|
17467
|
-
const charCode = nextCharCode;
|
|
17468
|
-
nextCharCode = i3 < text.length - 1 ? text.charCodeAt(i3 + 1) : -1;
|
|
17469
|
-
switch (charCode) {
|
|
17470
|
-
case 9 /* HorizontalTab */:
|
|
17471
|
-
insertSpaces(TAB_SPACES);
|
|
17472
|
-
break;
|
|
17473
|
-
case 10 /* Newline */:
|
|
17474
|
-
flushLine();
|
|
17475
|
-
flushWord();
|
|
17476
|
-
break;
|
|
17477
|
-
case 13 /* CarriageReturn */:
|
|
17478
|
-
break;
|
|
17479
|
-
case 32 /* Space */:
|
|
17480
|
-
insertSpaces(1);
|
|
17481
|
-
break;
|
|
17482
|
-
default: {
|
|
17483
|
-
const advance = font.getXAdvance(charCode, nextCharCode);
|
|
17484
|
-
if (wordWrap && wordWrap.breakOn === "character" && textOffsetX + advance > wordWrap.emWidth) {
|
|
17485
|
-
if (word.startX === 0) {
|
|
17486
|
-
flushWord();
|
|
17487
|
-
} else {
|
|
17488
|
-
lineWidths.push(textOffsetX - word.width);
|
|
17489
|
-
line++;
|
|
17490
|
-
maxWidth = Math.max(maxWidth, textOffsetX);
|
|
17491
|
-
textOffsetX = word.width;
|
|
17492
|
-
textOffsetY -= font.lineHeight;
|
|
17493
|
-
word.startX = 0;
|
|
17494
|
-
word.startY = textOffsetY;
|
|
17495
|
-
word.glyphs.forEach((g3) => {
|
|
17496
|
-
g3.line = line;
|
|
17497
|
-
});
|
|
17498
|
-
}
|
|
17499
|
-
}
|
|
17500
|
-
word.glyphs.push({
|
|
17501
|
-
char: font.getChar(charCode),
|
|
17502
|
-
offset: [word.width, 0],
|
|
17503
|
-
line
|
|
17504
|
-
});
|
|
17505
|
-
if (isLastLetter) {
|
|
17506
|
-
flushWord();
|
|
17507
|
-
}
|
|
17508
|
-
word.width += advance;
|
|
17509
|
-
textOffsetX += advance;
|
|
17510
|
-
}
|
|
17511
|
-
}
|
|
17243
|
+
set rotation(value) {
|
|
17244
|
+
this.#rotation = deg2rad(value);
|
|
17245
|
+
this.setDirty();
|
|
17512
17246
|
}
|
|
17513
|
-
|
|
17514
|
-
|
|
17515
|
-
const lineCount = lineWidths.length;
|
|
17516
|
-
return {
|
|
17517
|
-
width: maxWidth,
|
|
17518
|
-
height: lineCount * font.lineHeight,
|
|
17519
|
-
lineWidths,
|
|
17520
|
-
lineCount,
|
|
17521
|
-
printedCharCount,
|
|
17522
|
-
words
|
|
17523
|
-
};
|
|
17524
|
-
function flushWord() {
|
|
17525
|
-
printedCharCount += word.glyphs.length;
|
|
17526
|
-
words.push(word);
|
|
17527
|
-
word = {
|
|
17528
|
-
glyphs: [],
|
|
17529
|
-
width: 0,
|
|
17530
|
-
startX: textOffsetX,
|
|
17531
|
-
startY: textOffsetY
|
|
17532
|
-
};
|
|
17247
|
+
get rotationRadians() {
|
|
17248
|
+
return this.#rotation;
|
|
17533
17249
|
}
|
|
17534
|
-
|
|
17535
|
-
|
|
17536
|
-
|
|
17537
|
-
maxWidth = Math.max(maxWidth, textOffsetX);
|
|
17538
|
-
textOffsetX = 0;
|
|
17539
|
-
textOffsetY -= font.lineHeight;
|
|
17250
|
+
set rotationRadians(value) {
|
|
17251
|
+
this.#rotation = value;
|
|
17252
|
+
this.setDirty();
|
|
17540
17253
|
}
|
|
17541
|
-
|
|
17542
|
-
|
|
17543
|
-
spaces = 1;
|
|
17544
|
-
textOffsetX += font.getXAdvance(32 /* Space */) * spaces;
|
|
17545
|
-
if (wordWrap?.breakOn === "word" && textOffsetX >= wordWrap.emWidth) {
|
|
17546
|
-
flushLine();
|
|
17547
|
-
}
|
|
17548
|
-
flushWord();
|
|
17254
|
+
get x() {
|
|
17255
|
+
return this.#position.x;
|
|
17549
17256
|
}
|
|
17550
|
-
|
|
17551
|
-
|
|
17552
|
-
if (!formatting.fontSize) {
|
|
17553
|
-
throw new Error("fontSize is required for shrinkToFit");
|
|
17257
|
+
get y() {
|
|
17258
|
+
return this.#position.y;
|
|
17554
17259
|
}
|
|
17555
|
-
|
|
17556
|
-
|
|
17260
|
+
set x(value) {
|
|
17261
|
+
this.#position.x = value;
|
|
17262
|
+
this.setDirty();
|
|
17557
17263
|
}
|
|
17558
|
-
|
|
17559
|
-
|
|
17560
|
-
|
|
17561
|
-
|
|
17562
|
-
|
|
17563
|
-
|
|
17564
|
-
|
|
17565
|
-
|
|
17566
|
-
const testMeasure = measureText(font, text, formatting.wordWrap);
|
|
17567
|
-
const padding = formatting.shrinkToFit.padding ?? 0;
|
|
17568
|
-
const scaledWidth = testMeasure.width * (testSize / font.lineHeight);
|
|
17569
|
-
const scaledHeight = testMeasure.height * (testSize / font.lineHeight);
|
|
17570
|
-
const fitsWidth = scaledWidth <= size.width - size.width * padding;
|
|
17571
|
-
const fitsHeight = scaledHeight <= size.height - size.height * padding;
|
|
17572
|
-
const fitsLines = testMeasure.lineCount <= maxLines;
|
|
17573
|
-
if (fitsWidth && fitsHeight && fitsLines) {
|
|
17574
|
-
low = testSize;
|
|
17575
|
-
} else {
|
|
17576
|
-
high = testSize;
|
|
17264
|
+
set y(value) {
|
|
17265
|
+
this.#position.y = value;
|
|
17266
|
+
this.setDirty();
|
|
17267
|
+
}
|
|
17268
|
+
get matrix() {
|
|
17269
|
+
if (this.#isDirty) {
|
|
17270
|
+
this.#isDirty = false;
|
|
17271
|
+
this.#matrix = createViewMatrix(this, this.#matrix);
|
|
17577
17272
|
}
|
|
17273
|
+
return this.#matrix;
|
|
17274
|
+
}
|
|
17275
|
+
setDirty() {
|
|
17276
|
+
this.#isDirty = true;
|
|
17578
17277
|
}
|
|
17579
|
-
return low;
|
|
17580
|
-
}
|
|
17581
|
-
|
|
17582
|
-
// src/math/angle.ts
|
|
17583
|
-
function deg2rad(degrees) {
|
|
17584
|
-
return degrees * (Math.PI / 180);
|
|
17585
|
-
}
|
|
17586
|
-
function rad2deg(radians) {
|
|
17587
|
-
return radians * (180 / Math.PI);
|
|
17588
|
-
}
|
|
17589
|
-
|
|
17590
|
-
// src/math/matrix.ts
|
|
17591
|
-
function createProjectionMatrix(resolution, dst) {
|
|
17592
|
-
const { width, height } = resolution;
|
|
17593
|
-
return mat3.scaling([2 / width, 2 / height], dst);
|
|
17594
|
-
}
|
|
17595
|
-
function createViewMatrix(camera, target) {
|
|
17596
|
-
const matrix = mat3.identity(target);
|
|
17597
|
-
mat3.scale(matrix, [camera.zoom, camera.zoom], matrix);
|
|
17598
|
-
mat3.rotate(matrix, camera.rotationRadians, matrix);
|
|
17599
|
-
mat3.translate(matrix, [-camera.x, -camera.y], matrix);
|
|
17600
|
-
return matrix;
|
|
17601
|
-
}
|
|
17602
|
-
function createModelMatrix(transform, base) {
|
|
17603
|
-
mat3.translate(base, [transform.position.x, transform.position.y], base);
|
|
17604
|
-
mat3.rotate(base, transform.rotation, base);
|
|
17605
|
-
mat3.scale(base, [transform.scale.x, transform.scale.y], base);
|
|
17606
|
-
return base;
|
|
17607
|
-
}
|
|
17608
|
-
function convertScreenToWorld(screenCoordinates, camera, projectionMatrix, resolution) {
|
|
17609
|
-
const inverseViewProjectionMatrix = mat3.mul(mat3.inverse(camera.matrix), mat3.inverse(projectionMatrix));
|
|
17610
|
-
const normalizedDeviceCoordinates = {
|
|
17611
|
-
x: 2 * screenCoordinates.x / resolution.width - 1,
|
|
17612
|
-
y: 1 - 2 * screenCoordinates.y / resolution.height
|
|
17613
|
-
};
|
|
17614
|
-
return transformPoint(normalizedDeviceCoordinates, inverseViewProjectionMatrix);
|
|
17615
|
-
}
|
|
17616
|
-
function convertWorldToScreen(worldCoordinates, camera, projectionMatrix, resolution) {
|
|
17617
|
-
const viewProjectionMatrix = mat3.mul(projectionMatrix, camera.matrix);
|
|
17618
|
-
const ndcPoint = transformPoint(worldCoordinates, viewProjectionMatrix);
|
|
17619
|
-
return {
|
|
17620
|
-
x: (ndcPoint.x + 1) * resolution.width / 2,
|
|
17621
|
-
y: (1 - ndcPoint.y) * resolution.height / 2
|
|
17622
|
-
};
|
|
17623
|
-
}
|
|
17624
|
-
function transformPoint(point, matrix) {
|
|
17625
|
-
const result = vec2.transformMat3([point.x, point.y], matrix);
|
|
17626
|
-
return {
|
|
17627
|
-
x: result[0],
|
|
17628
|
-
y: result[1]
|
|
17629
|
-
};
|
|
17630
17278
|
}
|
|
17631
17279
|
|
|
17632
17280
|
// src/scene/SceneNode.ts
|
|
@@ -17970,1161 +17618,1304 @@ function reviver(key, value) {
|
|
|
17970
17618
|
return value;
|
|
17971
17619
|
}
|
|
17972
17620
|
|
|
17973
|
-
// src/scene/
|
|
17974
|
-
var
|
|
17621
|
+
// src/scene/QuadNode.ts
|
|
17622
|
+
var PRIMITIVE_TEXTURE = "__primitive__";
|
|
17623
|
+
var RESERVED_PRIMITIVE_INDEX_START = 1000;
|
|
17624
|
+
var CIRCLE_INDEX = 1001;
|
|
17625
|
+
var DEFAULT_REGION = {
|
|
17626
|
+
x: 0,
|
|
17627
|
+
y: 0,
|
|
17628
|
+
width: 0,
|
|
17629
|
+
height: 0
|
|
17630
|
+
};
|
|
17975
17631
|
|
|
17976
|
-
class
|
|
17977
|
-
|
|
17978
|
-
#
|
|
17979
|
-
#
|
|
17980
|
-
|
|
17981
|
-
|
|
17982
|
-
|
|
17983
|
-
|
|
17632
|
+
class QuadNode extends SceneNode {
|
|
17633
|
+
assetManager;
|
|
17634
|
+
#color;
|
|
17635
|
+
#atlasCoords;
|
|
17636
|
+
#region;
|
|
17637
|
+
#matrixPool;
|
|
17638
|
+
#flip;
|
|
17639
|
+
#cropOffset;
|
|
17640
|
+
#cropRatio;
|
|
17641
|
+
#atlasSize;
|
|
17642
|
+
#textureId;
|
|
17643
|
+
#writeInstance;
|
|
17644
|
+
constructor(options, matrixPool) {
|
|
17645
|
+
assert(options.shader, "QuadNode requires a shader to be explicitly provided");
|
|
17646
|
+
assert(options.size, "QuadNode requires a size to be explicitly provided");
|
|
17647
|
+
assert(options.atlasCoords, "QuadNode requires atlas coords to be explicitly provided");
|
|
17648
|
+
options.render ??= {
|
|
17649
|
+
shader: options.shader,
|
|
17650
|
+
writeInstance: writeQuadInstance
|
|
17651
|
+
};
|
|
17652
|
+
super(options);
|
|
17653
|
+
assert(options.assetManager, "QuadNode requires an asset manager");
|
|
17654
|
+
this.assetManager = options.assetManager;
|
|
17655
|
+
if (options.atlasCoords && options.atlasCoords.atlasIndex >= RESERVED_PRIMITIVE_INDEX_START) {
|
|
17656
|
+
this.#textureId = PRIMITIVE_TEXTURE;
|
|
17657
|
+
this.#region = DEFAULT_REGION;
|
|
17658
|
+
this.#atlasSize = DEFAULT_REGION;
|
|
17659
|
+
} else {
|
|
17660
|
+
assert(options.textureId, "QuadNode requires texture id to be explicitly provided");
|
|
17661
|
+
this.#textureId = options.textureId;
|
|
17662
|
+
assert(options.region, "QuadNode requires a region to be explicitly provided");
|
|
17663
|
+
this.#region = options.region;
|
|
17664
|
+
assert(options.atlasSize, "QuadNode requires atlas size to be explicitly provided");
|
|
17665
|
+
this.#atlasSize = options.atlasSize;
|
|
17984
17666
|
}
|
|
17985
|
-
|
|
17986
|
-
|
|
17987
|
-
|
|
17667
|
+
this.#atlasCoords = options.atlasCoords;
|
|
17668
|
+
this.#color = options.color ?? { r: 1, g: 1, b: 1, a: 1 };
|
|
17669
|
+
this.#matrixPool = matrixPool;
|
|
17670
|
+
this.#flip = { x: options.flipX ? -1 : 1, y: options.flipY ? -1 : 1 };
|
|
17671
|
+
this.#cropOffset = options.cropOffset ?? { x: 0, y: 0 };
|
|
17672
|
+
this.#cropRatio = !this.#atlasCoords.uvScaleCropped ? { width: 1, height: 1 } : {
|
|
17673
|
+
width: this.#atlasCoords.uvScaleCropped.width / this.#atlasCoords.uvScale.width,
|
|
17674
|
+
height: this.#atlasCoords.uvScaleCropped.height / this.#atlasCoords.uvScale.height
|
|
17675
|
+
};
|
|
17676
|
+
this.#writeInstance = options.writeInstance;
|
|
17677
|
+
}
|
|
17678
|
+
get color() {
|
|
17679
|
+
return this.#color;
|
|
17680
|
+
}
|
|
17681
|
+
set color(value) {
|
|
17682
|
+
this.#color = value;
|
|
17683
|
+
}
|
|
17684
|
+
get size() {
|
|
17685
|
+
const size = super.size;
|
|
17686
|
+
if (!size) {
|
|
17687
|
+
throw new Error("QuadNode requires a size");
|
|
17988
17688
|
}
|
|
17989
|
-
|
|
17990
|
-
...opts,
|
|
17991
|
-
render: {
|
|
17992
|
-
shader,
|
|
17993
|
-
writeInstance: (_node, _array, _offset) => {
|
|
17994
|
-
throw new Error("not implemented - needs access to text uniform buffer, dimensions and a model matrix");
|
|
17995
|
-
}
|
|
17996
|
-
}
|
|
17997
|
-
});
|
|
17998
|
-
this.#font = shader.font;
|
|
17999
|
-
this.#text = text;
|
|
18000
|
-
this.#formatting = opts;
|
|
17689
|
+
return size;
|
|
18001
17690
|
}
|
|
18002
|
-
|
|
18003
|
-
|
|
17691
|
+
set size(val) {
|
|
17692
|
+
super.size = val;
|
|
18004
17693
|
}
|
|
18005
|
-
get
|
|
18006
|
-
|
|
17694
|
+
get matrixWithSize() {
|
|
17695
|
+
const matrix = mat3.clone(this.matrix, this.#matrixPool.get());
|
|
17696
|
+
mat3.scale(matrix, [this.size.width * this.#flip.x, this.size.height * this.#flip.y], matrix);
|
|
17697
|
+
return matrix;
|
|
18007
17698
|
}
|
|
18008
|
-
get
|
|
18009
|
-
return this.#
|
|
17699
|
+
get atlasCoords() {
|
|
17700
|
+
return this.#atlasCoords;
|
|
18010
17701
|
}
|
|
18011
|
-
|
|
18012
|
-
|
|
18013
|
-
throw new Error("text cannot be empty");
|
|
18014
|
-
}
|
|
18015
|
-
this.#text = text;
|
|
18016
|
-
this.setDirty();
|
|
17702
|
+
get region() {
|
|
17703
|
+
return this.#region;
|
|
18017
17704
|
}
|
|
18018
|
-
get
|
|
18019
|
-
return this.#
|
|
17705
|
+
get writeInstance() {
|
|
17706
|
+
return this.#writeInstance;
|
|
18020
17707
|
}
|
|
18021
|
-
|
|
18022
|
-
this.#
|
|
17708
|
+
get flipX() {
|
|
17709
|
+
return this.#flip.x === -1;
|
|
17710
|
+
}
|
|
17711
|
+
set flipX(value) {
|
|
17712
|
+
this.#flip.x = value ? -1 : 1;
|
|
18023
17713
|
this.setDirty();
|
|
18024
17714
|
}
|
|
18025
|
-
|
|
18026
|
-
this.#
|
|
17715
|
+
get flipY() {
|
|
17716
|
+
return this.#flip.y === -1;
|
|
17717
|
+
}
|
|
17718
|
+
set flipY(value) {
|
|
17719
|
+
this.#flip.y = value ? -1 : 1;
|
|
18027
17720
|
this.setDirty();
|
|
18028
17721
|
}
|
|
17722
|
+
get cropOffset() {
|
|
17723
|
+
return this.#cropOffset;
|
|
17724
|
+
}
|
|
17725
|
+
set cropOffset(value) {
|
|
17726
|
+
this.#cropOffset = value;
|
|
17727
|
+
}
|
|
17728
|
+
get textureId() {
|
|
17729
|
+
return this.#textureId;
|
|
17730
|
+
}
|
|
17731
|
+
get isPrimitive() {
|
|
17732
|
+
return this.#textureId === PRIMITIVE_TEXTURE;
|
|
17733
|
+
}
|
|
17734
|
+
get isCircle() {
|
|
17735
|
+
return this.#atlasCoords.atlasIndex === CIRCLE_INDEX;
|
|
17736
|
+
}
|
|
17737
|
+
extra = {
|
|
17738
|
+
setAtlasCoords: (value) => {
|
|
17739
|
+
this.#atlasCoords = value;
|
|
17740
|
+
},
|
|
17741
|
+
cropRatio: () => {
|
|
17742
|
+
return this.#cropRatio;
|
|
17743
|
+
},
|
|
17744
|
+
atlasSize: () => {
|
|
17745
|
+
return this.#atlasSize;
|
|
17746
|
+
}
|
|
17747
|
+
};
|
|
17748
|
+
}
|
|
17749
|
+
function writeQuadInstance(node, array, offset) {
|
|
17750
|
+
if (!(node instanceof QuadNode)) {
|
|
17751
|
+
throw new Error("QuadNode.writeInstance can only be called on QuadNodes");
|
|
17752
|
+
}
|
|
17753
|
+
array.set(node.matrixWithSize, offset);
|
|
17754
|
+
array.set([node.color.r, node.color.g, node.color.b, node.color.a], offset + 12);
|
|
17755
|
+
const region = node.region;
|
|
17756
|
+
if (node.textureId === PRIMITIVE_TEXTURE) {
|
|
17757
|
+
array.set([
|
|
17758
|
+
node.atlasCoords.uvOffset.x,
|
|
17759
|
+
node.atlasCoords.uvOffset.y,
|
|
17760
|
+
node.atlasCoords.uvScale.width,
|
|
17761
|
+
node.atlasCoords.uvScale.height
|
|
17762
|
+
], offset + 16);
|
|
17763
|
+
} else {
|
|
17764
|
+
const atlasSize = node.extra.atlasSize();
|
|
17765
|
+
array.set([
|
|
17766
|
+
node.atlasCoords.uvOffset.x + region.x / atlasSize.width,
|
|
17767
|
+
node.atlasCoords.uvOffset.y + region.y / atlasSize.height,
|
|
17768
|
+
region.width / atlasSize.width,
|
|
17769
|
+
region.height / atlasSize.height
|
|
17770
|
+
], offset + 16);
|
|
17771
|
+
}
|
|
17772
|
+
array.set([
|
|
17773
|
+
node.cropOffset.x / 2 / (node.atlasCoords.originalSize.width || 1),
|
|
17774
|
+
node.cropOffset.y / 2 / (node.atlasCoords.originalSize.height || 1),
|
|
17775
|
+
node.extra.cropRatio().width,
|
|
17776
|
+
node.extra.cropRatio().height
|
|
17777
|
+
], offset + 20);
|
|
17778
|
+
new DataView(array.buffer).setUint32(array.byteOffset + (offset + 24) * Float32Array.BYTES_PER_ELEMENT, node.atlasCoords.atlasIndex, true);
|
|
17779
|
+
node.writeInstance?.(array, offset + 28);
|
|
17780
|
+
return 1;
|
|
18029
17781
|
}
|
|
18030
17782
|
|
|
18031
|
-
// src/
|
|
18032
|
-
var
|
|
18033
|
-
|
|
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
|
-
);
|
|
17783
|
+
// src/scene/JumboQuadNode.ts
|
|
17784
|
+
var MAT3_SIZE = 12;
|
|
17785
|
+
var VEC4F_SIZE = 4;
|
|
18065
17786
|
|
|
18066
|
-
|
|
18067
|
-
|
|
18068
|
-
|
|
18069
|
-
|
|
18070
|
-
|
|
18071
|
-
|
|
18072
|
-
|
|
18073
|
-
|
|
18074
|
-
|
|
18075
|
-
|
|
18076
|
-
|
|
18077
|
-
|
|
18078
|
-
|
|
18079
|
-
|
|
18080
|
-
|
|
18081
|
-
|
|
18082
|
-
|
|
18083
|
-
|
|
18084
|
-
|
|
18085
|
-
|
|
18086
|
-
|
|
18087
|
-
|
|
18088
|
-
|
|
18089
|
-
|
|
18090
|
-
|
|
18091
|
-
|
|
18092
|
-
|
|
18093
|
-
|
|
18094
|
-
|
|
18095
|
-
|
|
18096
|
-
|
|
18097
|
-
|
|
18098
|
-
|
|
18099
|
-
|
|
18100
|
-
|
|
18101
|
-
|
|
18102
|
-
|
|
18103
|
-
|
|
18104
|
-
|
|
17787
|
+
class JumboQuadNode extends QuadNode {
|
|
17788
|
+
#tiles;
|
|
17789
|
+
#matrixPool;
|
|
17790
|
+
constructor(options, matrixPool) {
|
|
17791
|
+
assert(options.shader, "JumboQuadNode requires a shader to be explicitly provided");
|
|
17792
|
+
assert(options.tiles && options.tiles.length > 0, "JumboQuadNode requires at least one tile to be provided");
|
|
17793
|
+
options.render ??= {
|
|
17794
|
+
shader: options.shader,
|
|
17795
|
+
writeInstance: writeJumboQuadInstance
|
|
17796
|
+
};
|
|
17797
|
+
super({
|
|
17798
|
+
...options,
|
|
17799
|
+
atlasCoords: options.tiles[0].atlasCoords
|
|
17800
|
+
}, matrixPool);
|
|
17801
|
+
this.#matrixPool = matrixPool;
|
|
17802
|
+
this.#tiles = [];
|
|
17803
|
+
for (const tile of options.tiles) {
|
|
17804
|
+
assert(tile.atlasCoords, "JumboQuadNode requires atlas coords to be provided");
|
|
17805
|
+
assert(tile.size, "JumboQuadNode requires a size to be provided");
|
|
17806
|
+
this.#tiles.push({
|
|
17807
|
+
textureId: tile.textureId,
|
|
17808
|
+
offset: tile.offset,
|
|
17809
|
+
size: tile.size,
|
|
17810
|
+
atlasCoords: tile.atlasCoords
|
|
17811
|
+
});
|
|
17812
|
+
}
|
|
17813
|
+
}
|
|
17814
|
+
get atlasCoords() {
|
|
17815
|
+
throw new Error("JumboQuadNode does not have a single atlas coords");
|
|
17816
|
+
}
|
|
17817
|
+
get tiles() {
|
|
17818
|
+
return this.#tiles;
|
|
17819
|
+
}
|
|
17820
|
+
getTileMatrix(tile) {
|
|
17821
|
+
const matrix = mat3.clone(this.matrix, this.#matrixPool.get());
|
|
17822
|
+
const originalSize = {
|
|
17823
|
+
width: Math.max(...this.#tiles.map((t3) => t3.offset.x + t3.size.width)),
|
|
17824
|
+
height: Math.max(...this.#tiles.map((t3) => t3.offset.y + t3.size.height))
|
|
17825
|
+
};
|
|
17826
|
+
const proportionalSize = {
|
|
17827
|
+
width: this.size.width / originalSize.width,
|
|
17828
|
+
height: this.size.height / originalSize.height
|
|
17829
|
+
};
|
|
17830
|
+
const centerOffset = {
|
|
17831
|
+
x: tile.offset.x + tile.size.width / 2 - originalSize.width / 2,
|
|
17832
|
+
y: -(tile.offset.y + tile.size.height / 2 - originalSize.height / 2)
|
|
17833
|
+
};
|
|
17834
|
+
mat3.translate(matrix, [
|
|
17835
|
+
centerOffset.x * proportionalSize.width,
|
|
17836
|
+
centerOffset.y * proportionalSize.height
|
|
17837
|
+
], matrix);
|
|
17838
|
+
mat3.scale(matrix, [
|
|
17839
|
+
tile.size.width * proportionalSize.width * (this.flipX ? -1 : 1),
|
|
17840
|
+
tile.size.height * proportionalSize.height * (this.flipY ? -1 : 1)
|
|
17841
|
+
], matrix);
|
|
17842
|
+
return matrix;
|
|
17843
|
+
}
|
|
18105
17844
|
}
|
|
18106
|
-
|
|
18107
|
-
|
|
18108
|
-
|
|
18109
|
-
|
|
18110
|
-
|
|
18111
|
-
|
|
18112
|
-
|
|
18113
|
-
|
|
18114
|
-
|
|
18115
|
-
|
|
18116
|
-
|
|
18117
|
-
|
|
18118
|
-
|
|
18119
|
-
|
|
18120
|
-
|
|
18121
|
-
|
|
18122
|
-
|
|
18123
|
-
|
|
18124
|
-
|
|
18125
|
-
|
|
18126
|
-
|
|
17845
|
+
function writeJumboQuadInstance(node, array, offset) {
|
|
17846
|
+
if (!(node instanceof JumboQuadNode)) {
|
|
17847
|
+
throw new Error("JumboQuadNode.writeJumboQuadInstance can only be called on JumboQuadNodes");
|
|
17848
|
+
}
|
|
17849
|
+
let tileOffset = 0;
|
|
17850
|
+
for (const tile of node.tiles) {
|
|
17851
|
+
const coord = tile.atlasCoords;
|
|
17852
|
+
const matrix = node.getTileMatrix(tile);
|
|
17853
|
+
array.set(matrix, offset + tileOffset);
|
|
17854
|
+
tileOffset += MAT3_SIZE;
|
|
17855
|
+
array.set([node.color.r, node.color.g, node.color.b, node.color.a], offset + tileOffset);
|
|
17856
|
+
tileOffset += VEC4F_SIZE;
|
|
17857
|
+
array.set([
|
|
17858
|
+
coord.uvOffset.x,
|
|
17859
|
+
coord.uvOffset.y,
|
|
17860
|
+
coord.uvScale.width,
|
|
17861
|
+
coord.uvScale.height
|
|
17862
|
+
], offset + tileOffset);
|
|
17863
|
+
tileOffset += VEC4F_SIZE;
|
|
17864
|
+
const cropRatio = !coord.uvScaleCropped ? { width: 1, height: 1 } : {
|
|
17865
|
+
width: coord.uvScaleCropped.width / coord.uvScale.width,
|
|
17866
|
+
height: coord.uvScaleCropped.height / coord.uvScale.height
|
|
17867
|
+
};
|
|
17868
|
+
array.set([
|
|
17869
|
+
tile.atlasCoords.cropOffset.x / 2 / (tile.atlasCoords.originalSize.width || 1),
|
|
17870
|
+
tile.atlasCoords.cropOffset.y / 2 / (tile.atlasCoords.originalSize.height || 1),
|
|
17871
|
+
cropRatio.width,
|
|
17872
|
+
cropRatio.height
|
|
17873
|
+
], offset + tileOffset);
|
|
17874
|
+
tileOffset += VEC4F_SIZE;
|
|
17875
|
+
new DataView(array.buffer).setUint32(array.byteOffset + (offset + tileOffset) * Float32Array.BYTES_PER_ELEMENT, coord.atlasIndex, true);
|
|
17876
|
+
tileOffset += VEC4F_SIZE;
|
|
17877
|
+
}
|
|
17878
|
+
node.writeInstance?.(array, offset + tileOffset);
|
|
17879
|
+
return node.tiles.length;
|
|
18127
17880
|
}
|
|
18128
17881
|
|
|
18129
|
-
|
|
18130
|
-
|
|
18131
|
-
|
|
18132
|
-
|
|
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;
|
|
17882
|
+
// src/utils/error.ts
|
|
17883
|
+
var warnings = new Map;
|
|
17884
|
+
function warnOnce(key, msg) {
|
|
17885
|
+
if (warnings.has(key)) {
|
|
17886
|
+
return;
|
|
18148
17887
|
}
|
|
17888
|
+
warnings.set(key, true);
|
|
17889
|
+
console.warn(msg ?? key);
|
|
17890
|
+
}
|
|
18149
17891
|
|
|
18150
|
-
|
|
17892
|
+
// src/text/MsdfFont.ts
|
|
17893
|
+
class MsdfFont {
|
|
17894
|
+
id;
|
|
17895
|
+
json;
|
|
17896
|
+
imageBitmap;
|
|
17897
|
+
name;
|
|
17898
|
+
charset;
|
|
17899
|
+
charCount;
|
|
17900
|
+
lineHeight;
|
|
17901
|
+
charBuffer;
|
|
17902
|
+
#kernings;
|
|
17903
|
+
#chars;
|
|
17904
|
+
#fallbackCharCode;
|
|
17905
|
+
constructor(id, json, imageBitmap) {
|
|
17906
|
+
this.id = id;
|
|
17907
|
+
this.json = json;
|
|
17908
|
+
this.imageBitmap = imageBitmap;
|
|
17909
|
+
const charArray = Object.values(json.chars);
|
|
17910
|
+
this.charCount = charArray.length;
|
|
17911
|
+
this.lineHeight = json.common.lineHeight;
|
|
17912
|
+
this.charset = json.info.charset;
|
|
17913
|
+
this.name = json.info.face;
|
|
17914
|
+
this.#kernings = new Map;
|
|
17915
|
+
if (json.kernings) {
|
|
17916
|
+
for (const kearning of json.kernings) {
|
|
17917
|
+
let charKerning = this.#kernings.get(kearning.first);
|
|
17918
|
+
if (!charKerning) {
|
|
17919
|
+
charKerning = new Map;
|
|
17920
|
+
this.#kernings.set(kearning.first, charKerning);
|
|
17921
|
+
}
|
|
17922
|
+
charKerning.set(kearning.second, kearning.amount);
|
|
17923
|
+
}
|
|
17924
|
+
}
|
|
17925
|
+
this.#chars = new Map;
|
|
17926
|
+
const charCount = Object.values(json.chars).length;
|
|
17927
|
+
this.charBuffer = new Float32Array(charCount * 8);
|
|
17928
|
+
let offset = 0;
|
|
17929
|
+
const u3 = 1 / json.common.scaleW;
|
|
17930
|
+
const v3 = 1 / json.common.scaleH;
|
|
17931
|
+
for (const [i3, char] of json.chars.entries()) {
|
|
17932
|
+
this.#chars.set(char.id, char);
|
|
17933
|
+
this.#chars.get(char.id).charIndex = i3;
|
|
17934
|
+
this.charBuffer[offset] = char.x * u3;
|
|
17935
|
+
this.charBuffer[offset + 1] = char.y * v3;
|
|
17936
|
+
this.charBuffer[offset + 2] = char.width * u3;
|
|
17937
|
+
this.charBuffer[offset + 3] = char.height * v3;
|
|
17938
|
+
this.charBuffer[offset + 4] = char.width;
|
|
17939
|
+
this.charBuffer[offset + 5] = char.height;
|
|
17940
|
+
this.charBuffer[offset + 6] = char.xoffset;
|
|
17941
|
+
this.charBuffer[offset + 7] = -char.yoffset;
|
|
17942
|
+
offset += 8;
|
|
17943
|
+
}
|
|
17944
|
+
}
|
|
17945
|
+
getChar(charCode) {
|
|
17946
|
+
const char = this.#chars.get(charCode);
|
|
17947
|
+
if (!char) {
|
|
17948
|
+
const fallbackCharacter = this.#chars.get(this.#fallbackCharCode ?? this.#chars.keys().toArray()[0]);
|
|
17949
|
+
warnOnce(`unknown_char_${this.name}`, `Couldn't find character ${charCode} in characters for font ${this.name} -- defaulting to first available character "${fallbackCharacter.char}"`);
|
|
17950
|
+
return fallbackCharacter;
|
|
17951
|
+
}
|
|
17952
|
+
return char;
|
|
17953
|
+
}
|
|
17954
|
+
getXAdvance(charCode, nextCharCode = -1) {
|
|
17955
|
+
const char = this.getChar(charCode);
|
|
17956
|
+
if (nextCharCode >= 0) {
|
|
17957
|
+
const kerning = this.#kernings.get(charCode);
|
|
17958
|
+
if (kerning) {
|
|
17959
|
+
return char.xadvance + (kerning.get(nextCharCode) ?? 0);
|
|
17960
|
+
}
|
|
17961
|
+
}
|
|
17962
|
+
return char.xadvance;
|
|
17963
|
+
}
|
|
17964
|
+
static async create(id, fontJsonUrl) {
|
|
17965
|
+
const response = await fetch(fontJsonUrl);
|
|
17966
|
+
const json = await response.json();
|
|
17967
|
+
const i3 = fontJsonUrl.href.lastIndexOf("/");
|
|
17968
|
+
const baseUrl = i3 !== -1 ? fontJsonUrl.href.substring(0, i3 + 1) : undefined;
|
|
17969
|
+
if (json.pages.length < 1) {
|
|
17970
|
+
throw new Error(`Can't create an msdf font without a reference to the page url in the json`);
|
|
17971
|
+
}
|
|
17972
|
+
if (json.pages.length > 1) {
|
|
17973
|
+
throw new Error(`Can't create an msdf font with more than one page`);
|
|
17974
|
+
}
|
|
17975
|
+
const textureUrl = baseUrl + json.pages[0];
|
|
17976
|
+
const textureResponse = await fetch(textureUrl);
|
|
17977
|
+
const bitmap = await createImageBitmap(await textureResponse.blob());
|
|
17978
|
+
return new MsdfFont(id, json, bitmap);
|
|
17979
|
+
}
|
|
17980
|
+
set fallbackCharacter(character) {
|
|
17981
|
+
const charCode = character.charCodeAt(0);
|
|
17982
|
+
if (this.#chars.has(charCode)) {
|
|
17983
|
+
this.#fallbackCharCode = charCode;
|
|
17984
|
+
} else {
|
|
17985
|
+
const fallbackCode = this.#chars.keys().toArray()[0];
|
|
17986
|
+
console.warn(`${character} character does not exist in font ${this.name} defaulting to "${this.#chars.get(fallbackCode)?.char}".`);
|
|
17987
|
+
this.#fallbackCharCode = fallbackCode;
|
|
17988
|
+
}
|
|
17989
|
+
}
|
|
18151
17990
|
}
|
|
18152
|
-
`;
|
|
18153
17991
|
|
|
18154
|
-
// src/
|
|
18155
|
-
|
|
18156
|
-
|
|
18157
|
-
|
|
18158
|
-
|
|
18159
|
-
|
|
18160
|
-
|
|
18161
|
-
|
|
18162
|
-
|
|
18163
|
-
|
|
18164
|
-
|
|
18165
|
-
|
|
18166
|
-
|
|
18167
|
-
|
|
18168
|
-
|
|
18169
|
-
|
|
18170
|
-
|
|
18171
|
-
|
|
18172
|
-
|
|
18173
|
-
|
|
18174
|
-
|
|
18175
|
-
|
|
18176
|
-
|
|
18177
|
-
|
|
18178
|
-
|
|
18179
|
-
|
|
18180
|
-
|
|
18181
|
-
|
|
18182
|
-
|
|
18183
|
-
|
|
18184
|
-
|
|
18185
|
-
|
|
18186
|
-
|
|
18187
|
-
|
|
18188
|
-
|
|
18189
|
-
|
|
18190
|
-
|
|
17992
|
+
// src/text/shaping.ts
|
|
17993
|
+
var TAB_SPACES = 4;
|
|
17994
|
+
function shapeText(font, text, blockSize, fontSize, formatting, textArray, initialFloatOffset = 0, debug = false) {
|
|
17995
|
+
let offset = initialFloatOffset;
|
|
17996
|
+
const measurements = measureText(font, text, formatting.wordWrap);
|
|
17997
|
+
const alignment = formatting.align || "left";
|
|
17998
|
+
const em2px = fontSize / font.lineHeight;
|
|
17999
|
+
const hackHasExplicitBlock = blockSize.width !== measurements.width;
|
|
18000
|
+
let debugData = null;
|
|
18001
|
+
if (debug) {
|
|
18002
|
+
debugData = [];
|
|
18003
|
+
}
|
|
18004
|
+
for (const word of measurements.words) {
|
|
18005
|
+
for (const glyph of word.glyphs) {
|
|
18006
|
+
let lineOffset = 0;
|
|
18007
|
+
if (alignment === "center") {
|
|
18008
|
+
lineOffset = measurements.width * -0.5 - (measurements.width - measurements.lineWidths[glyph.line]) * -0.5;
|
|
18009
|
+
} else if (alignment === "right") {
|
|
18010
|
+
const blockSizeEm = blockSize.width / em2px;
|
|
18011
|
+
const delta = measurements.width - measurements.lineWidths[glyph.line];
|
|
18012
|
+
lineOffset = (hackHasExplicitBlock ? blockSizeEm / 2 : measurements.width / 2) - measurements.width + delta;
|
|
18013
|
+
} else if (alignment === "left") {
|
|
18014
|
+
const blockSizeEm = blockSize.width / em2px;
|
|
18015
|
+
lineOffset = hackHasExplicitBlock ? -blockSizeEm / 2 : -measurements.width / 2;
|
|
18016
|
+
}
|
|
18017
|
+
if (debug && debugData) {
|
|
18018
|
+
debugData.push({
|
|
18019
|
+
line: glyph.line,
|
|
18020
|
+
word: word.glyphs.map((g3) => g3.char.char).join(""),
|
|
18021
|
+
glyph: glyph.char.char,
|
|
18022
|
+
startX: word.startX,
|
|
18023
|
+
glyphX: glyph.offset[0],
|
|
18024
|
+
advance: glyph.char.xadvance,
|
|
18025
|
+
lineOffset,
|
|
18026
|
+
startY: word.startY,
|
|
18027
|
+
glyphY: glyph.offset[1]
|
|
18028
|
+
});
|
|
18029
|
+
}
|
|
18030
|
+
textArray[offset] = word.startX + glyph.offset[0] + lineOffset;
|
|
18031
|
+
textArray[offset + 1] = word.startY + glyph.offset[1];
|
|
18032
|
+
textArray[offset + 2] = glyph.char.charIndex;
|
|
18033
|
+
offset += 4;
|
|
18191
18034
|
}
|
|
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
18035
|
}
|
|
18210
|
-
|
|
18211
|
-
|
|
18036
|
+
if (debug && debugData) {
|
|
18037
|
+
console.table(debugData);
|
|
18212
18038
|
}
|
|
18213
|
-
|
|
18214
|
-
|
|
18215
|
-
|
|
18216
|
-
|
|
18217
|
-
|
|
18218
|
-
|
|
18219
|
-
|
|
18220
|
-
|
|
18221
|
-
|
|
18222
|
-
|
|
18223
|
-
|
|
18224
|
-
|
|
18225
|
-
|
|
18226
|
-
|
|
18227
|
-
|
|
18228
|
-
|
|
18229
|
-
|
|
18230
|
-
|
|
18231
|
-
|
|
18232
|
-
|
|
18233
|
-
|
|
18234
|
-
|
|
18235
|
-
|
|
18236
|
-
|
|
18039
|
+
}
|
|
18040
|
+
function measureText(font, text, wordWrap) {
|
|
18041
|
+
let maxWidth = 0;
|
|
18042
|
+
const lineWidths = [];
|
|
18043
|
+
let textOffsetX = 0;
|
|
18044
|
+
let textOffsetY = 0;
|
|
18045
|
+
let line = 0;
|
|
18046
|
+
let printedCharCount = 0;
|
|
18047
|
+
let nextCharCode = text.charCodeAt(0);
|
|
18048
|
+
let word = { glyphs: [], width: 0, startX: 0, startY: 0 };
|
|
18049
|
+
const words = [];
|
|
18050
|
+
for (let i3 = 0;i3 < text.length; i3++) {
|
|
18051
|
+
const isLastLetter = i3 === text.length - 1;
|
|
18052
|
+
const charCode = nextCharCode;
|
|
18053
|
+
nextCharCode = i3 < text.length - 1 ? text.charCodeAt(i3 + 1) : -1;
|
|
18054
|
+
switch (charCode) {
|
|
18055
|
+
case 9 /* HorizontalTab */:
|
|
18056
|
+
insertSpaces(TAB_SPACES);
|
|
18057
|
+
break;
|
|
18058
|
+
case 10 /* Newline */:
|
|
18059
|
+
flushLine();
|
|
18060
|
+
flushWord();
|
|
18061
|
+
break;
|
|
18062
|
+
case 13 /* CarriageReturn */:
|
|
18063
|
+
break;
|
|
18064
|
+
case 32 /* Space */:
|
|
18065
|
+
insertSpaces(1);
|
|
18066
|
+
break;
|
|
18067
|
+
default: {
|
|
18068
|
+
const advance = font.getXAdvance(charCode, nextCharCode);
|
|
18069
|
+
if (wordWrap && wordWrap.breakOn === "character" && textOffsetX + advance > wordWrap.emWidth) {
|
|
18070
|
+
if (word.startX === 0) {
|
|
18071
|
+
flushWord();
|
|
18072
|
+
} else {
|
|
18073
|
+
lineWidths.push(textOffsetX - word.width);
|
|
18074
|
+
line++;
|
|
18075
|
+
maxWidth = Math.max(maxWidth, textOffsetX);
|
|
18076
|
+
textOffsetX = word.width;
|
|
18077
|
+
textOffsetY -= font.lineHeight;
|
|
18078
|
+
word.startX = 0;
|
|
18079
|
+
word.startY = textOffsetY;
|
|
18080
|
+
word.glyphs.forEach((g3) => {
|
|
18081
|
+
g3.line = line;
|
|
18082
|
+
});
|
|
18083
|
+
}
|
|
18084
|
+
}
|
|
18085
|
+
word.glyphs.push({
|
|
18086
|
+
char: font.getChar(charCode),
|
|
18087
|
+
offset: [word.width, 0],
|
|
18088
|
+
line
|
|
18089
|
+
});
|
|
18090
|
+
if (isLastLetter) {
|
|
18091
|
+
flushWord();
|
|
18092
|
+
}
|
|
18093
|
+
word.width += advance;
|
|
18094
|
+
textOffsetX += advance;
|
|
18095
|
+
}
|
|
18237
18096
|
}
|
|
18238
|
-
|
|
18239
|
-
|
|
18097
|
+
}
|
|
18098
|
+
lineWidths.push(textOffsetX);
|
|
18099
|
+
maxWidth = Math.max(maxWidth, textOffsetX);
|
|
18100
|
+
const lineCount = lineWidths.length;
|
|
18101
|
+
return {
|
|
18102
|
+
width: maxWidth,
|
|
18103
|
+
height: lineCount * font.lineHeight,
|
|
18104
|
+
lineWidths,
|
|
18105
|
+
lineCount,
|
|
18106
|
+
printedCharCount,
|
|
18107
|
+
words
|
|
18108
|
+
};
|
|
18109
|
+
function flushWord() {
|
|
18110
|
+
printedCharCount += word.glyphs.length;
|
|
18111
|
+
words.push(word);
|
|
18112
|
+
word = {
|
|
18113
|
+
glyphs: [],
|
|
18114
|
+
width: 0,
|
|
18115
|
+
startX: textOffsetX,
|
|
18116
|
+
startY: textOffsetY
|
|
18117
|
+
};
|
|
18118
|
+
}
|
|
18119
|
+
function flushLine() {
|
|
18120
|
+
lineWidths.push(textOffsetX);
|
|
18121
|
+
line++;
|
|
18122
|
+
maxWidth = Math.max(maxWidth, textOffsetX);
|
|
18123
|
+
textOffsetX = 0;
|
|
18124
|
+
textOffsetY -= font.lineHeight;
|
|
18125
|
+
}
|
|
18126
|
+
function insertSpaces(spaces) {
|
|
18127
|
+
if (spaces < 1)
|
|
18128
|
+
spaces = 1;
|
|
18129
|
+
textOffsetX += font.getXAdvance(32 /* Space */) * spaces;
|
|
18130
|
+
if (wordWrap?.breakOn === "word" && textOffsetX >= wordWrap.emWidth) {
|
|
18131
|
+
flushLine();
|
|
18240
18132
|
}
|
|
18241
|
-
|
|
18242
|
-
|
|
18243
|
-
|
|
18244
|
-
|
|
18133
|
+
flushWord();
|
|
18134
|
+
}
|
|
18135
|
+
}
|
|
18136
|
+
function findLargestFontSize(font, text, size, formatting) {
|
|
18137
|
+
if (!formatting.fontSize) {
|
|
18138
|
+
throw new Error("fontSize is required for shrinkToFit");
|
|
18139
|
+
}
|
|
18140
|
+
if (!formatting.shrinkToFit) {
|
|
18141
|
+
throw new Error("shrinkToFit is required for findLargestFontSize");
|
|
18142
|
+
}
|
|
18143
|
+
const minSize = formatting.shrinkToFit.minFontSize;
|
|
18144
|
+
const maxSize = formatting.shrinkToFit.maxFontSize ?? formatting.fontSize;
|
|
18145
|
+
const maxLines = formatting.shrinkToFit.maxLines ?? Number.POSITIVE_INFINITY;
|
|
18146
|
+
const threshold = 0.5;
|
|
18147
|
+
let low = minSize;
|
|
18148
|
+
let high = maxSize;
|
|
18149
|
+
while (high - low > threshold) {
|
|
18150
|
+
const testSize = (low + high) / 2;
|
|
18151
|
+
const testMeasure = measureText(font, text, formatting.wordWrap);
|
|
18152
|
+
const padding = formatting.shrinkToFit.padding ?? 0;
|
|
18153
|
+
const scaledWidth = testMeasure.width * (testSize / font.lineHeight);
|
|
18154
|
+
const scaledHeight = testMeasure.height * (testSize / font.lineHeight);
|
|
18155
|
+
const fitsWidth = scaledWidth <= size.width - size.width * padding;
|
|
18156
|
+
const fitsHeight = scaledHeight <= size.height - size.height * padding;
|
|
18157
|
+
const fitsLines = testMeasure.lineCount <= maxLines;
|
|
18158
|
+
if (fitsWidth && fitsHeight && fitsLines) {
|
|
18159
|
+
low = testSize;
|
|
18160
|
+
} else {
|
|
18161
|
+
high = testSize;
|
|
18245
18162
|
}
|
|
18246
|
-
|
|
18247
|
-
|
|
18248
|
-
|
|
18249
|
-
|
|
18163
|
+
}
|
|
18164
|
+
return low;
|
|
18165
|
+
}
|
|
18166
|
+
|
|
18167
|
+
// src/scene/TextNode.ts
|
|
18168
|
+
var DEFAULT_FONT_SIZE = 14;
|
|
18169
|
+
|
|
18170
|
+
class TextNode extends SceneNode {
|
|
18171
|
+
#text;
|
|
18172
|
+
#formatting;
|
|
18173
|
+
#font;
|
|
18174
|
+
constructor(shader, text, opts = {}) {
|
|
18175
|
+
const { width, height } = measureText(shader.font, text, opts.wordWrap);
|
|
18176
|
+
if (text.length > shader.maxCharCount) {
|
|
18177
|
+
throw new Error(`Text: ${text} exceeds ${shader.maxCharCount} characters. Try using fewer characters or increase the limit in Toodle.attach.`);
|
|
18250
18178
|
}
|
|
18251
|
-
|
|
18252
|
-
|
|
18253
|
-
|
|
18254
|
-
gl.uniform1i(this.#uTextBuffer, 2);
|
|
18179
|
+
const em2px = shader.font.lineHeight / (opts.fontSize ?? DEFAULT_FONT_SIZE);
|
|
18180
|
+
if (!opts.shrinkToFit && !opts.size) {
|
|
18181
|
+
opts.size = { width: width / em2px, height: height / em2px };
|
|
18255
18182
|
}
|
|
18256
|
-
|
|
18257
|
-
|
|
18258
|
-
|
|
18259
|
-
|
|
18260
|
-
|
|
18261
|
-
|
|
18262
|
-
|
|
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);
|
|
18183
|
+
super({
|
|
18184
|
+
...opts,
|
|
18185
|
+
render: {
|
|
18186
|
+
shader,
|
|
18187
|
+
writeInstance: (_node, _array, _offset) => {
|
|
18188
|
+
throw new Error("not implemented - needs access to text uniform buffer, dimensions and a model matrix");
|
|
18189
|
+
}
|
|
18296
18190
|
}
|
|
18297
|
-
|
|
18191
|
+
});
|
|
18192
|
+
this.#font = shader.font;
|
|
18193
|
+
this.#text = text;
|
|
18194
|
+
this.#formatting = opts;
|
|
18195
|
+
}
|
|
18196
|
+
get text() {
|
|
18197
|
+
return this.#text;
|
|
18198
|
+
}
|
|
18199
|
+
get formatting() {
|
|
18200
|
+
return this.#formatting;
|
|
18201
|
+
}
|
|
18202
|
+
get font() {
|
|
18203
|
+
return this.#font;
|
|
18204
|
+
}
|
|
18205
|
+
set text(text) {
|
|
18206
|
+
if (!text) {
|
|
18207
|
+
throw new Error("text cannot be empty");
|
|
18298
18208
|
}
|
|
18299
|
-
|
|
18300
|
-
|
|
18209
|
+
this.#text = text;
|
|
18210
|
+
this.setDirty();
|
|
18301
18211
|
}
|
|
18302
|
-
|
|
18303
|
-
|
|
18304
|
-
|
|
18305
|
-
|
|
18306
|
-
|
|
18307
|
-
|
|
18308
|
-
|
|
18309
|
-
|
|
18310
|
-
|
|
18311
|
-
|
|
18312
|
-
|
|
18212
|
+
get tint() {
|
|
18213
|
+
return this.#formatting.color || { r: 1, g: 1, b: 1, a: 1 };
|
|
18214
|
+
}
|
|
18215
|
+
set tint(tint) {
|
|
18216
|
+
this.#formatting.color = tint;
|
|
18217
|
+
this.setDirty();
|
|
18218
|
+
}
|
|
18219
|
+
set formatting(formatting) {
|
|
18220
|
+
this.#formatting = formatting;
|
|
18221
|
+
this.setDirty();
|
|
18222
|
+
}
|
|
18223
|
+
}
|
|
18224
|
+
|
|
18225
|
+
// src/backends/webgl2/WebGLFontPipeline.ts
|
|
18226
|
+
class WebGLFontPipeline2 {
|
|
18227
|
+
font;
|
|
18228
|
+
fontTexture;
|
|
18229
|
+
charDataTexture;
|
|
18230
|
+
textBufferTexture;
|
|
18231
|
+
maxCharCount;
|
|
18232
|
+
lineHeight;
|
|
18233
|
+
#gl;
|
|
18234
|
+
constructor(gl, font, fontTexture, charDataTexture, textBufferTexture, maxCharCount) {
|
|
18235
|
+
this.#gl = gl;
|
|
18236
|
+
this.font = font;
|
|
18237
|
+
this.fontTexture = fontTexture;
|
|
18238
|
+
this.charDataTexture = charDataTexture;
|
|
18239
|
+
this.textBufferTexture = textBufferTexture;
|
|
18240
|
+
this.maxCharCount = maxCharCount;
|
|
18241
|
+
this.lineHeight = font.lineHeight;
|
|
18242
|
+
}
|
|
18243
|
+
static create(gl, font, maxCharCount) {
|
|
18244
|
+
const fontTexture = gl.createTexture();
|
|
18245
|
+
assert(fontTexture, "Failed to create font texture");
|
|
18246
|
+
gl.bindTexture(gl.TEXTURE_2D, fontTexture);
|
|
18247
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
18248
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
18249
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
18250
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
18251
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, font.imageBitmap);
|
|
18252
|
+
const charDataTexture = gl.createTexture();
|
|
18253
|
+
assert(charDataTexture, "Failed to create char data texture");
|
|
18254
|
+
gl.bindTexture(gl.TEXTURE_2D, charDataTexture);
|
|
18255
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
18256
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
18257
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
18258
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
18259
|
+
const charCount = font.charCount;
|
|
18260
|
+
const charTextureWidth = charCount * 2;
|
|
18261
|
+
const charTextureData = new Float32Array(charTextureWidth * 4);
|
|
18262
|
+
for (let i3 = 0;i3 < charCount; i3++) {
|
|
18263
|
+
const srcOffset = i3 * 8;
|
|
18264
|
+
const dstOffset0 = i3 * 2 * 4;
|
|
18265
|
+
const dstOffset1 = (i3 * 2 + 1) * 4;
|
|
18266
|
+
charTextureData[dstOffset0] = font.charBuffer[srcOffset];
|
|
18267
|
+
charTextureData[dstOffset0 + 1] = font.charBuffer[srcOffset + 1];
|
|
18268
|
+
charTextureData[dstOffset0 + 2] = font.charBuffer[srcOffset + 2];
|
|
18269
|
+
charTextureData[dstOffset0 + 3] = font.charBuffer[srcOffset + 3];
|
|
18270
|
+
charTextureData[dstOffset1] = font.charBuffer[srcOffset + 4];
|
|
18271
|
+
charTextureData[dstOffset1 + 1] = font.charBuffer[srcOffset + 5];
|
|
18272
|
+
charTextureData[dstOffset1 + 2] = font.charBuffer[srcOffset + 6];
|
|
18273
|
+
charTextureData[dstOffset1 + 3] = font.charBuffer[srcOffset + 7];
|
|
18313
18274
|
}
|
|
18314
|
-
|
|
18275
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, charTextureWidth, 1, 0, gl.RGBA, gl.FLOAT, charTextureData);
|
|
18276
|
+
const textBufferTexture = gl.createTexture();
|
|
18277
|
+
assert(textBufferTexture, "Failed to create text buffer texture");
|
|
18278
|
+
gl.bindTexture(gl.TEXTURE_2D, textBufferTexture);
|
|
18279
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
18280
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
18281
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
18282
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
18283
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, maxCharCount, 1, 0, gl.RGBA, gl.FLOAT, null);
|
|
18284
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
18285
|
+
return new WebGLFontPipeline2(gl, font, fontTexture, charDataTexture, textBufferTexture, maxCharCount);
|
|
18286
|
+
}
|
|
18287
|
+
updateTextBuffer(data, glyphCount) {
|
|
18288
|
+
const gl = this.#gl;
|
|
18289
|
+
gl.bindTexture(gl.TEXTURE_2D, this.textBufferTexture);
|
|
18290
|
+
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, glyphCount, 1, gl.RGBA, gl.FLOAT, data);
|
|
18315
18291
|
}
|
|
18316
18292
|
destroy() {
|
|
18317
|
-
const gl = this.#
|
|
18318
|
-
gl.
|
|
18319
|
-
gl.
|
|
18320
|
-
this
|
|
18293
|
+
const gl = this.#gl;
|
|
18294
|
+
gl.deleteTexture(this.fontTexture);
|
|
18295
|
+
gl.deleteTexture(this.charDataTexture);
|
|
18296
|
+
gl.deleteTexture(this.textBufferTexture);
|
|
18321
18297
|
}
|
|
18322
18298
|
}
|
|
18323
18299
|
|
|
18324
|
-
// src/backends/
|
|
18325
|
-
var
|
|
18326
|
-
|
|
18300
|
+
// src/backends/webgl2/glsl/text.glsl.ts
|
|
18301
|
+
var vertexShader2 = `#version 300 es
|
|
18302
|
+
precision highp float;
|
|
18327
18303
|
|
|
18328
|
-
//
|
|
18329
|
-
|
|
18330
|
-
vec2f(0, -1),
|
|
18331
|
-
vec2f(1, -1),
|
|
18332
|
-
vec2f(0, 0),
|
|
18333
|
-
vec2f(1, 0),
|
|
18334
|
-
);
|
|
18304
|
+
// Engine uniforms
|
|
18305
|
+
uniform mat3 u_viewProjection;
|
|
18335
18306
|
|
|
18336
|
-
//
|
|
18337
|
-
|
|
18338
|
-
|
|
18339
|
-
|
|
18340
|
-
|
|
18341
|
-
|
|
18342
|
-
|
|
18307
|
+
// Per-text-block uniforms
|
|
18308
|
+
uniform mat3 u_textTransform;
|
|
18309
|
+
uniform vec4 u_textColor;
|
|
18310
|
+
uniform float u_fontSize;
|
|
18311
|
+
uniform float u_blockWidth;
|
|
18312
|
+
uniform float u_blockHeight;
|
|
18313
|
+
uniform float u_lineHeight;
|
|
18343
18314
|
|
|
18344
|
-
//
|
|
18345
|
-
|
|
18346
|
-
|
|
18347
|
-
|
|
18348
|
-
};
|
|
18315
|
+
// Character data texture (RGBA32F, 2 texels per character)
|
|
18316
|
+
// Texel 0: texOffset.xy, texExtent.xy
|
|
18317
|
+
// Texel 1: size.xy, offset.xy
|
|
18318
|
+
uniform sampler2D u_charData;
|
|
18349
18319
|
|
|
18350
|
-
//
|
|
18351
|
-
|
|
18352
|
-
|
|
18353
|
-
@location(0) texcoord: vec2f,
|
|
18354
|
-
@location(1) debugColor: vec4f,
|
|
18355
|
-
@location(2) @interpolate(flat) instanceIndex: u32,
|
|
18356
|
-
};
|
|
18320
|
+
// Text buffer texture (RGBA32F, 1 texel per glyph)
|
|
18321
|
+
// Each texel: xy = glyph position, z = char index
|
|
18322
|
+
uniform sampler2D u_textBuffer;
|
|
18357
18323
|
|
|
18358
|
-
//
|
|
18359
|
-
|
|
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
|
-
};
|
|
18324
|
+
// Outputs to fragment shader
|
|
18325
|
+
out vec2 v_texcoord;
|
|
18365
18326
|
|
|
18366
|
-
//
|
|
18367
|
-
|
|
18368
|
-
|
|
18369
|
-
|
|
18370
|
-
|
|
18371
|
-
|
|
18372
|
-
|
|
18373
|
-
bufferPosition: f32 // Index and length in textBuffer
|
|
18374
|
-
};
|
|
18327
|
+
// Quad vertex positions for a character (matches WGSL)
|
|
18328
|
+
const vec2 pos[4] = vec2[4](
|
|
18329
|
+
vec2(0.0, -1.0),
|
|
18330
|
+
vec2(1.0, -1.0),
|
|
18331
|
+
vec2(0.0, 0.0),
|
|
18332
|
+
vec2(1.0, 0.0)
|
|
18333
|
+
);
|
|
18375
18334
|
|
|
18376
|
-
|
|
18377
|
-
|
|
18378
|
-
|
|
18379
|
-
|
|
18380
|
-
|
|
18335
|
+
void main() {
|
|
18336
|
+
// gl_VertexID gives us 0-3 for the quad vertices
|
|
18337
|
+
// gl_InstanceID gives us which glyph we're rendering
|
|
18338
|
+
int vertexIndex = gl_VertexID;
|
|
18339
|
+
int glyphIndex = gl_InstanceID;
|
|
18381
18340
|
|
|
18382
|
-
//
|
|
18383
|
-
|
|
18384
|
-
|
|
18341
|
+
// Fetch glyph data from text buffer texture
|
|
18342
|
+
vec4 glyphData = texelFetch(u_textBuffer, ivec2(glyphIndex, 0), 0);
|
|
18343
|
+
vec2 glyphPos = glyphData.xy;
|
|
18344
|
+
int charIndex = int(glyphData.z);
|
|
18385
18345
|
|
|
18386
|
-
//
|
|
18387
|
-
|
|
18346
|
+
// Fetch character metrics (2 texels per char)
|
|
18347
|
+
// Texel 0: texOffset.x, texOffset.y, texExtent.x, texExtent.y
|
|
18348
|
+
// Texel 1: size.x, size.y, offset.x, offset.y
|
|
18349
|
+
vec4 charData0 = texelFetch(u_charData, ivec2(charIndex * 2, 0), 0);
|
|
18350
|
+
vec4 charData1 = texelFetch(u_charData, ivec2(charIndex * 2 + 1, 0), 0);
|
|
18388
18351
|
|
|
18389
|
-
|
|
18390
|
-
|
|
18391
|
-
|
|
18392
|
-
|
|
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;
|
|
18352
|
+
vec2 texOffset = charData0.xy;
|
|
18353
|
+
vec2 texExtent = charData0.zw;
|
|
18354
|
+
vec2 charSize = charData1.xy;
|
|
18355
|
+
vec2 charOffset = charData1.zw;
|
|
18401
18356
|
|
|
18402
|
-
|
|
18403
|
-
|
|
18404
|
-
let char = chars[u32(textElement.z)];
|
|
18357
|
+
// Center text vertically; origin is mid-height
|
|
18358
|
+
vec2 offset = vec2(0.0, -u_blockHeight / 2.0);
|
|
18405
18359
|
|
|
18406
|
-
|
|
18407
|
-
|
|
18408
|
-
|
|
18360
|
+
// Glyph position in ems (quad pos * size + per-char offset)
|
|
18361
|
+
vec2 emPos = pos[vertexIndex] * charSize + charOffset + glyphPos - offset;
|
|
18362
|
+
vec2 charPos = emPos * (u_fontSize / u_lineHeight);
|
|
18409
18363
|
|
|
18410
|
-
//
|
|
18411
|
-
|
|
18364
|
+
// Transform position through model and view-projection matrices
|
|
18365
|
+
vec3 worldPos = u_textTransform * vec3(charPos, 1.0);
|
|
18366
|
+
vec3 clipPos = u_viewProjection * worldPos;
|
|
18367
|
+
|
|
18368
|
+
gl_Position = vec4(clipPos.xy, 0.0, 1.0);
|
|
18412
18369
|
|
|
18413
|
-
//
|
|
18414
|
-
|
|
18415
|
-
|
|
18370
|
+
// Calculate texture coordinates
|
|
18371
|
+
v_texcoord = pos[vertexIndex] * vec2(1.0, -1.0);
|
|
18372
|
+
v_texcoord *= texExtent;
|
|
18373
|
+
v_texcoord += texOffset;
|
|
18374
|
+
}
|
|
18375
|
+
`;
|
|
18376
|
+
var fragmentShader2 = `#version 300 es
|
|
18377
|
+
precision highp float;
|
|
18416
18378
|
|
|
18417
|
-
|
|
18418
|
-
|
|
18379
|
+
// Font texture (MSDF atlas)
|
|
18380
|
+
uniform sampler2D u_fontTexture;
|
|
18419
18381
|
|
|
18420
|
-
|
|
18421
|
-
|
|
18422
|
-
output.texcoord *= char.texExtent;
|
|
18423
|
-
output.texcoord += char.texOffset;
|
|
18424
|
-
output.debugColor = debugColors[vertexIndex];
|
|
18425
|
-
output.instanceIndex = textIndex;
|
|
18426
|
-
return output;
|
|
18382
|
+
// Text color
|
|
18383
|
+
uniform vec4 u_textColor;
|
|
18427
18384
|
|
|
18428
|
-
|
|
18429
|
-
|
|
18430
|
-
|
|
18385
|
+
// Input from vertex shader
|
|
18386
|
+
in vec2 v_texcoord;
|
|
18387
|
+
|
|
18388
|
+
// Output color
|
|
18389
|
+
out vec4 fragColor;
|
|
18431
18390
|
|
|
18432
18391
|
// Signed distance function sampling for MSDF font rendering
|
|
18433
|
-
|
|
18434
|
-
|
|
18392
|
+
// Median of three: max(min(r,g), min(max(r,g), b))
|
|
18393
|
+
float sampleMsdf(vec2 texcoord) {
|
|
18394
|
+
vec4 c = texture(u_fontTexture, texcoord);
|
|
18435
18395
|
return max(min(c.r, c.g), min(max(c.r, c.g), c.b));
|
|
18436
18396
|
}
|
|
18437
18397
|
|
|
18438
|
-
|
|
18439
|
-
//
|
|
18440
|
-
|
|
18441
|
-
|
|
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));
|
|
18398
|
+
void main() {
|
|
18399
|
+
// pxRange (AKA distanceRange) comes from the msdfgen tool
|
|
18400
|
+
float pxRange = 4.0;
|
|
18401
|
+
vec2 texSize = vec2(textureSize(u_fontTexture, 0));
|
|
18449
18402
|
|
|
18450
|
-
|
|
18451
|
-
|
|
18403
|
+
// Anti-aliasing technique by Paul Houx
|
|
18404
|
+
// https://github.com/Chlumsky/msdfgen/issues/22#issuecomment-234958005
|
|
18405
|
+
float dx = texSize.x * length(vec2(dFdx(v_texcoord.x), dFdy(v_texcoord.x)));
|
|
18406
|
+
float dy = texSize.y * length(vec2(dFdx(v_texcoord.y), dFdy(v_texcoord.y)));
|
|
18452
18407
|
|
|
18453
|
-
|
|
18454
|
-
|
|
18455
|
-
|
|
18408
|
+
float toPixels = pxRange * inversesqrt(dx * dx + dy * dy);
|
|
18409
|
+
float sigDist = sampleMsdf(v_texcoord) - 0.5;
|
|
18410
|
+
float pxDist = sigDist * toPixels;
|
|
18456
18411
|
|
|
18457
|
-
|
|
18458
|
-
|
|
18412
|
+
float edgeWidth = 0.5;
|
|
18413
|
+
float alpha = smoothstep(-edgeWidth, edgeWidth, pxDist);
|
|
18459
18414
|
|
|
18460
18415
|
if (alpha < 0.001) {
|
|
18461
18416
|
discard;
|
|
18462
18417
|
}
|
|
18463
18418
|
|
|
18464
|
-
|
|
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
|
-
};
|
|
18649
|
-
// src/backends/webgpu/WebGPUTextShader.ts
|
|
18650
|
-
var deets = new _t2(text_wgsl_default);
|
|
18651
|
-
var struct = deets.structs.find((s3) => s3.name === "TextBlockDescriptor");
|
|
18652
|
-
if (!struct) {
|
|
18653
|
-
throw new Error("FormattedText struct not found");
|
|
18419
|
+
fragColor = vec4(u_textColor.rgb, u_textColor.a * alpha);
|
|
18654
18420
|
}
|
|
18655
|
-
|
|
18421
|
+
`;
|
|
18656
18422
|
|
|
18657
|
-
|
|
18423
|
+
// src/backends/webgl2/WebGLTextShader.ts
|
|
18424
|
+
class WebGLTextShader2 {
|
|
18658
18425
|
label = "text";
|
|
18426
|
+
code = fragmentShader2;
|
|
18427
|
+
font;
|
|
18428
|
+
maxCharCount;
|
|
18659
18429
|
#backend;
|
|
18660
18430
|
#pipeline;
|
|
18661
|
-
#
|
|
18662
|
-
#
|
|
18663
|
-
#
|
|
18664
|
-
#
|
|
18665
|
-
#
|
|
18666
|
-
#
|
|
18667
|
-
#
|
|
18668
|
-
#
|
|
18669
|
-
#
|
|
18670
|
-
#
|
|
18671
|
-
|
|
18431
|
+
#program;
|
|
18432
|
+
#vao;
|
|
18433
|
+
#cpuTextBuffer;
|
|
18434
|
+
#cachedUniform = null;
|
|
18435
|
+
#uViewProjection = null;
|
|
18436
|
+
#uTextTransform = null;
|
|
18437
|
+
#uTextColor = null;
|
|
18438
|
+
#uFontSize = null;
|
|
18439
|
+
#uBlockWidth = null;
|
|
18440
|
+
#uBlockHeight = null;
|
|
18441
|
+
#uLineHeight = null;
|
|
18442
|
+
#uCharData = null;
|
|
18443
|
+
#uTextBuffer = null;
|
|
18444
|
+
#uFontTexture = null;
|
|
18445
|
+
constructor(backend, pipeline) {
|
|
18672
18446
|
this.#backend = backend;
|
|
18673
|
-
|
|
18674
|
-
this
|
|
18675
|
-
this
|
|
18676
|
-
|
|
18677
|
-
this.#
|
|
18678
|
-
|
|
18679
|
-
|
|
18680
|
-
|
|
18681
|
-
|
|
18682
|
-
|
|
18683
|
-
|
|
18684
|
-
|
|
18685
|
-
|
|
18686
|
-
|
|
18687
|
-
|
|
18688
|
-
|
|
18689
|
-
this.#
|
|
18690
|
-
|
|
18691
|
-
|
|
18692
|
-
|
|
18693
|
-
|
|
18694
|
-
this.#
|
|
18695
|
-
this.#
|
|
18696
|
-
|
|
18697
|
-
|
|
18698
|
-
|
|
18699
|
-
|
|
18700
|
-
|
|
18701
|
-
|
|
18702
|
-
|
|
18703
|
-
|
|
18704
|
-
|
|
18705
|
-
resource: { buffer: this.#textBlockBuffer }
|
|
18706
|
-
}
|
|
18707
|
-
]
|
|
18708
|
-
}));
|
|
18709
|
-
const engineUniformsBindGroup = device.createBindGroup({
|
|
18710
|
-
label: "msdf text uniforms bind group",
|
|
18711
|
-
layout: pipeline.pipeline.getBindGroupLayout(2),
|
|
18712
|
-
entries: [
|
|
18713
|
-
{
|
|
18714
|
-
binding: 0,
|
|
18715
|
-
resource: { buffer: this.#engineUniformsBuffer }
|
|
18716
|
-
}
|
|
18717
|
-
]
|
|
18718
|
-
});
|
|
18719
|
-
this.#bindGroups.push(engineUniformsBindGroup);
|
|
18447
|
+
this.#pipeline = pipeline;
|
|
18448
|
+
this.font = pipeline.font;
|
|
18449
|
+
this.maxCharCount = pipeline.maxCharCount;
|
|
18450
|
+
const gl = backend.gl;
|
|
18451
|
+
const vs = this.#compileShader(gl, gl.VERTEX_SHADER, vertexShader2);
|
|
18452
|
+
const fs = this.#compileShader(gl, gl.FRAGMENT_SHADER, fragmentShader2);
|
|
18453
|
+
const program = gl.createProgram();
|
|
18454
|
+
assert(program, "Failed to create WebGL program");
|
|
18455
|
+
gl.attachShader(program, vs);
|
|
18456
|
+
gl.attachShader(program, fs);
|
|
18457
|
+
gl.linkProgram(program);
|
|
18458
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
18459
|
+
const info = gl.getProgramInfoLog(program);
|
|
18460
|
+
throw new Error(`Failed to link text shader program: ${info}`);
|
|
18461
|
+
}
|
|
18462
|
+
this.#program = program;
|
|
18463
|
+
this.#uViewProjection = gl.getUniformLocation(program, "u_viewProjection");
|
|
18464
|
+
this.#uTextTransform = gl.getUniformLocation(program, "u_textTransform");
|
|
18465
|
+
this.#uTextColor = gl.getUniformLocation(program, "u_textColor");
|
|
18466
|
+
this.#uFontSize = gl.getUniformLocation(program, "u_fontSize");
|
|
18467
|
+
this.#uBlockWidth = gl.getUniformLocation(program, "u_blockWidth");
|
|
18468
|
+
this.#uBlockHeight = gl.getUniformLocation(program, "u_blockHeight");
|
|
18469
|
+
this.#uLineHeight = gl.getUniformLocation(program, "u_lineHeight");
|
|
18470
|
+
this.#uCharData = gl.getUniformLocation(program, "u_charData");
|
|
18471
|
+
this.#uTextBuffer = gl.getUniformLocation(program, "u_textBuffer");
|
|
18472
|
+
this.#uFontTexture = gl.getUniformLocation(program, "u_fontTexture");
|
|
18473
|
+
const vao = gl.createVertexArray();
|
|
18474
|
+
assert(vao, "Failed to create WebGL VAO");
|
|
18475
|
+
this.#vao = vao;
|
|
18476
|
+
this.#cpuTextBuffer = new Float32Array(this.maxCharCount * 4);
|
|
18477
|
+
gl.deleteShader(vs);
|
|
18478
|
+
gl.deleteShader(fs);
|
|
18720
18479
|
}
|
|
18721
18480
|
startFrame(uniform) {
|
|
18722
|
-
|
|
18723
|
-
device.queue.writeBuffer(this.#engineUniformsBuffer, 0, uniform.viewProjectionMatrix);
|
|
18724
|
-
this.#instanceIndex = 0;
|
|
18725
|
-
this.#textBlockOffset = 0;
|
|
18481
|
+
this.#cachedUniform = uniform;
|
|
18726
18482
|
}
|
|
18727
18483
|
processBatch(nodes) {
|
|
18728
18484
|
if (nodes.length === 0)
|
|
18729
18485
|
return 0;
|
|
18730
|
-
const
|
|
18731
|
-
|
|
18732
|
-
|
|
18733
|
-
|
|
18486
|
+
const gl = this.#backend.gl;
|
|
18487
|
+
const uniform = this.#cachedUniform;
|
|
18488
|
+
if (!uniform) {
|
|
18489
|
+
throw new Error("Tried to process batch but engine uniform is not set");
|
|
18490
|
+
}
|
|
18491
|
+
gl.useProgram(this.#program);
|
|
18492
|
+
gl.bindVertexArray(this.#vao);
|
|
18493
|
+
if (this.#uViewProjection) {
|
|
18494
|
+
const m3 = uniform.viewProjectionMatrix;
|
|
18495
|
+
const mat3x3 = new Float32Array([
|
|
18496
|
+
m3[0],
|
|
18497
|
+
m3[1],
|
|
18498
|
+
m3[2],
|
|
18499
|
+
m3[4],
|
|
18500
|
+
m3[5],
|
|
18501
|
+
m3[6],
|
|
18502
|
+
m3[8],
|
|
18503
|
+
m3[9],
|
|
18504
|
+
m3[10]
|
|
18505
|
+
]);
|
|
18506
|
+
gl.uniformMatrix3fv(this.#uViewProjection, false, mat3x3);
|
|
18507
|
+
}
|
|
18508
|
+
if (this.#uLineHeight) {
|
|
18509
|
+
gl.uniform1f(this.#uLineHeight, this.#pipeline.lineHeight);
|
|
18510
|
+
}
|
|
18511
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
18512
|
+
gl.bindTexture(gl.TEXTURE_2D, this.#pipeline.fontTexture);
|
|
18513
|
+
if (this.#uFontTexture) {
|
|
18514
|
+
gl.uniform1i(this.#uFontTexture, 0);
|
|
18515
|
+
}
|
|
18516
|
+
gl.activeTexture(gl.TEXTURE1);
|
|
18517
|
+
gl.bindTexture(gl.TEXTURE_2D, this.#pipeline.charDataTexture);
|
|
18518
|
+
if (this.#uCharData) {
|
|
18519
|
+
gl.uniform1i(this.#uCharData, 1);
|
|
18520
|
+
}
|
|
18521
|
+
gl.activeTexture(gl.TEXTURE2);
|
|
18522
|
+
gl.bindTexture(gl.TEXTURE_2D, this.#pipeline.textBufferTexture);
|
|
18523
|
+
if (this.#uTextBuffer) {
|
|
18524
|
+
gl.uniform1i(this.#uTextBuffer, 2);
|
|
18734
18525
|
}
|
|
18735
18526
|
for (const node of nodes) {
|
|
18736
18527
|
if (!(node instanceof TextNode)) {
|
|
18737
18528
|
console.error(node);
|
|
18738
|
-
throw new Error(`Tried to use
|
|
18529
|
+
throw new Error(`Tried to use WebGLTextShader on something that isn't a TextNode: ${node}`);
|
|
18739
18530
|
}
|
|
18740
18531
|
const text = node.text;
|
|
18741
18532
|
const formatting = node.formatting;
|
|
18742
|
-
const measurements = measureText(this
|
|
18743
|
-
const textBlockSize = 4 * text.length;
|
|
18744
|
-
const textDescriptorOffset = this.#instanceIndex * textDescriptorInstanceSize / Float32Array.BYTES_PER_ELEMENT;
|
|
18745
|
-
this.#cpuDescriptorBuffer.set(node.matrix, textDescriptorOffset);
|
|
18746
|
-
this.#cpuDescriptorBuffer.set([node.tint.r, node.tint.g, node.tint.b, node.tint.a], textDescriptorOffset + 12);
|
|
18533
|
+
const measurements = measureText(this.font, text, formatting.wordWrap);
|
|
18747
18534
|
const size = node.size ?? measurements;
|
|
18748
|
-
const fontSize = formatting.shrinkToFit ? findLargestFontSize(this
|
|
18535
|
+
const fontSize = formatting.shrinkToFit ? findLargestFontSize(this.font, text, size, formatting) : formatting.fontSize;
|
|
18749
18536
|
const actualFontSize = fontSize || DEFAULT_FONT_SIZE;
|
|
18750
|
-
this
|
|
18751
|
-
this.#
|
|
18752
|
-
this.#
|
|
18753
|
-
|
|
18754
|
-
|
|
18755
|
-
|
|
18756
|
-
|
|
18757
|
-
|
|
18758
|
-
|
|
18759
|
-
|
|
18760
|
-
|
|
18761
|
-
|
|
18762
|
-
|
|
18763
|
-
|
|
18764
|
-
|
|
18765
|
-
|
|
18766
|
-
|
|
18767
|
-
|
|
18768
|
-
|
|
18769
|
-
|
|
18770
|
-
}
|
|
18771
|
-
|
|
18772
|
-
|
|
18773
|
-
|
|
18774
|
-
|
|
18775
|
-
|
|
18776
|
-
|
|
18777
|
-
|
|
18778
|
-
|
|
18779
|
-
|
|
18780
|
-
|
|
18781
|
-
const layer = this.#findOrCreateLayer(z3);
|
|
18782
|
-
const pipeline = this.#findOrCreatePipeline(layer, node.renderComponent.shader);
|
|
18783
|
-
pipeline.nodes.push(node);
|
|
18784
|
-
}
|
|
18785
|
-
for (const kid of node.kids) {
|
|
18786
|
-
this.enqueue(kid);
|
|
18787
|
-
}
|
|
18788
|
-
}
|
|
18789
|
-
flush() {
|
|
18790
|
-
this.nodes = [];
|
|
18791
|
-
this.layers = [];
|
|
18792
|
-
this.pipelines = [];
|
|
18793
|
-
}
|
|
18794
|
-
#findOrCreateLayer(z3) {
|
|
18795
|
-
let layer = this.layers.find((l3) => l3.z === z3);
|
|
18796
|
-
if (!layer) {
|
|
18797
|
-
layer = { z: z3, pipelines: [] };
|
|
18798
|
-
this.layers.push(layer);
|
|
18799
|
-
this.layers.sort((a3, b3) => a3.z - b3.z);
|
|
18800
|
-
}
|
|
18801
|
-
return layer;
|
|
18802
|
-
}
|
|
18803
|
-
#findOrCreatePipeline(layer, shader) {
|
|
18804
|
-
let pipeline = layer.pipelines.find((p3) => p3.shader === shader);
|
|
18805
|
-
if (!pipeline) {
|
|
18806
|
-
pipeline = { shader, nodes: [] };
|
|
18807
|
-
layer.pipelines.push(pipeline);
|
|
18808
|
-
this.pipelines.push(pipeline);
|
|
18537
|
+
shapeText(this.font, text, size, actualFontSize, formatting, this.#cpuTextBuffer, 0);
|
|
18538
|
+
this.#pipeline.updateTextBuffer(this.#cpuTextBuffer, measurements.printedCharCount);
|
|
18539
|
+
if (this.#uTextTransform) {
|
|
18540
|
+
const m3 = node.matrix;
|
|
18541
|
+
const mat3x3 = new Float32Array([
|
|
18542
|
+
m3[0],
|
|
18543
|
+
m3[1],
|
|
18544
|
+
m3[2],
|
|
18545
|
+
m3[4],
|
|
18546
|
+
m3[5],
|
|
18547
|
+
m3[6],
|
|
18548
|
+
m3[8],
|
|
18549
|
+
m3[9],
|
|
18550
|
+
m3[10]
|
|
18551
|
+
]);
|
|
18552
|
+
gl.uniformMatrix3fv(this.#uTextTransform, false, mat3x3);
|
|
18553
|
+
}
|
|
18554
|
+
if (this.#uTextColor) {
|
|
18555
|
+
const tint = node.tint;
|
|
18556
|
+
gl.uniform4f(this.#uTextColor, tint.r, tint.g, tint.b, tint.a);
|
|
18557
|
+
}
|
|
18558
|
+
if (this.#uFontSize) {
|
|
18559
|
+
gl.uniform1f(this.#uFontSize, actualFontSize);
|
|
18560
|
+
}
|
|
18561
|
+
if (this.#uBlockWidth) {
|
|
18562
|
+
gl.uniform1f(this.#uBlockWidth, formatting.align === "center" ? 0 : measurements.width);
|
|
18563
|
+
}
|
|
18564
|
+
if (this.#uBlockHeight) {
|
|
18565
|
+
gl.uniform1f(this.#uBlockHeight, measurements.height);
|
|
18566
|
+
}
|
|
18567
|
+
gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, measurements.printedCharCount);
|
|
18809
18568
|
}
|
|
18810
|
-
|
|
18811
|
-
|
|
18812
|
-
}
|
|
18813
|
-
|
|
18814
|
-
// src/scene/Camera.ts
|
|
18815
|
-
class Camera {
|
|
18816
|
-
#position = { x: 0, y: 0 };
|
|
18817
|
-
#zoom = 1;
|
|
18818
|
-
#rotation = 0;
|
|
18819
|
-
#isDirty = true;
|
|
18820
|
-
#matrix = mat3.create();
|
|
18821
|
-
get zoom() {
|
|
18822
|
-
return this.#zoom;
|
|
18823
|
-
}
|
|
18824
|
-
set zoom(value) {
|
|
18825
|
-
this.#zoom = value;
|
|
18826
|
-
this.setDirty();
|
|
18827
|
-
}
|
|
18828
|
-
get rotation() {
|
|
18829
|
-
return rad2deg(this.#rotation);
|
|
18830
|
-
}
|
|
18831
|
-
set rotation(value) {
|
|
18832
|
-
this.#rotation = deg2rad(value);
|
|
18833
|
-
this.setDirty();
|
|
18834
|
-
}
|
|
18835
|
-
get rotationRadians() {
|
|
18836
|
-
return this.#rotation;
|
|
18837
|
-
}
|
|
18838
|
-
set rotationRadians(value) {
|
|
18839
|
-
this.#rotation = value;
|
|
18840
|
-
this.setDirty();
|
|
18841
|
-
}
|
|
18842
|
-
get x() {
|
|
18843
|
-
return this.#position.x;
|
|
18844
|
-
}
|
|
18845
|
-
get y() {
|
|
18846
|
-
return this.#position.y;
|
|
18847
|
-
}
|
|
18848
|
-
set x(value) {
|
|
18849
|
-
this.#position.x = value;
|
|
18850
|
-
this.setDirty();
|
|
18851
|
-
}
|
|
18852
|
-
set y(value) {
|
|
18853
|
-
this.#position.y = value;
|
|
18854
|
-
this.setDirty();
|
|
18569
|
+
gl.bindVertexArray(null);
|
|
18570
|
+
return nodes.length;
|
|
18855
18571
|
}
|
|
18856
|
-
|
|
18857
|
-
|
|
18858
|
-
|
|
18859
|
-
|
|
18572
|
+
endFrame() {}
|
|
18573
|
+
#compileShader(gl, type, source) {
|
|
18574
|
+
const shader = gl.createShader(type);
|
|
18575
|
+
assert(shader, "Failed to create WebGL shader");
|
|
18576
|
+
gl.shaderSource(shader, source);
|
|
18577
|
+
gl.compileShader(shader);
|
|
18578
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
18579
|
+
const info = gl.getShaderInfoLog(shader);
|
|
18580
|
+
const typeStr = type === gl.VERTEX_SHADER ? "vertex" : "fragment";
|
|
18581
|
+
gl.deleteShader(shader);
|
|
18582
|
+
throw new Error(`Failed to compile ${typeStr} shader: ${info}`);
|
|
18860
18583
|
}
|
|
18861
|
-
return
|
|
18584
|
+
return shader;
|
|
18862
18585
|
}
|
|
18863
|
-
|
|
18864
|
-
|
|
18586
|
+
destroy() {
|
|
18587
|
+
const gl = this.#backend.gl;
|
|
18588
|
+
gl.deleteProgram(this.#program);
|
|
18589
|
+
gl.deleteVertexArray(this.#vao);
|
|
18590
|
+
this.#pipeline.destroy();
|
|
18865
18591
|
}
|
|
18866
18592
|
}
|
|
18867
18593
|
|
|
18868
|
-
// src/
|
|
18869
|
-
var
|
|
18870
|
-
|
|
18871
|
-
|
|
18872
|
-
|
|
18873
|
-
|
|
18874
|
-
|
|
18875
|
-
|
|
18876
|
-
|
|
18594
|
+
// src/backends/webgpu/wgsl/text.wgsl.ts
|
|
18595
|
+
var text_wgsl_default = `
|
|
18596
|
+
// Adapted from: https://webgpu.github.io/webgpu-samples/?sample=textRenderingMsdf
|
|
18597
|
+
|
|
18598
|
+
// Quad vertex positions for a character
|
|
18599
|
+
const pos = array(
|
|
18600
|
+
vec2f(0, -1),
|
|
18601
|
+
vec2f(1, -1),
|
|
18602
|
+
vec2f(0, 0),
|
|
18603
|
+
vec2f(1, 0),
|
|
18604
|
+
);
|
|
18605
|
+
|
|
18606
|
+
// Debug colors for visualization
|
|
18607
|
+
const debugColors = array(
|
|
18608
|
+
vec4f(1, 0, 0, 1),
|
|
18609
|
+
vec4f(0, 1, 0, 1),
|
|
18610
|
+
vec4f(0, 0, 1, 1),
|
|
18611
|
+
vec4f(1, 1, 1, 1),
|
|
18612
|
+
);
|
|
18613
|
+
|
|
18614
|
+
// Vertex input from GPU
|
|
18615
|
+
struct VertexInput {
|
|
18616
|
+
@builtin(vertex_index) vertex: u32,
|
|
18617
|
+
@builtin(instance_index) instance: u32,
|
|
18877
18618
|
};
|
|
18878
18619
|
|
|
18879
|
-
|
|
18880
|
-
|
|
18881
|
-
|
|
18882
|
-
|
|
18883
|
-
|
|
18884
|
-
|
|
18885
|
-
|
|
18886
|
-
|
|
18887
|
-
|
|
18888
|
-
|
|
18889
|
-
|
|
18890
|
-
|
|
18891
|
-
|
|
18892
|
-
|
|
18893
|
-
|
|
18894
|
-
|
|
18895
|
-
|
|
18896
|
-
|
|
18897
|
-
|
|
18898
|
-
|
|
18899
|
-
|
|
18900
|
-
|
|
18901
|
-
|
|
18902
|
-
|
|
18903
|
-
|
|
18904
|
-
|
|
18905
|
-
|
|
18906
|
-
|
|
18907
|
-
|
|
18908
|
-
|
|
18909
|
-
|
|
18910
|
-
|
|
18911
|
-
|
|
18912
|
-
|
|
18913
|
-
|
|
18914
|
-
|
|
18915
|
-
|
|
18916
|
-
|
|
18917
|
-
|
|
18918
|
-
|
|
18919
|
-
|
|
18920
|
-
|
|
18921
|
-
|
|
18922
|
-
|
|
18923
|
-
|
|
18924
|
-
|
|
18925
|
-
|
|
18926
|
-
|
|
18927
|
-
|
|
18928
|
-
|
|
18929
|
-
|
|
18930
|
-
|
|
18931
|
-
|
|
18932
|
-
|
|
18933
|
-
|
|
18934
|
-
|
|
18935
|
-
|
|
18936
|
-
|
|
18937
|
-
|
|
18938
|
-
|
|
18939
|
-
|
|
18940
|
-
|
|
18941
|
-
|
|
18942
|
-
|
|
18943
|
-
|
|
18944
|
-
|
|
18945
|
-
|
|
18946
|
-
|
|
18947
|
-
|
|
18948
|
-
|
|
18949
|
-
|
|
18950
|
-
|
|
18951
|
-
|
|
18952
|
-
|
|
18953
|
-
|
|
18954
|
-
|
|
18955
|
-
|
|
18956
|
-
|
|
18957
|
-
|
|
18958
|
-
|
|
18959
|
-
this.#flip.x = value ? -1 : 1;
|
|
18960
|
-
this.setDirty();
|
|
18961
|
-
}
|
|
18962
|
-
get flipY() {
|
|
18963
|
-
return this.#flip.y === -1;
|
|
18964
|
-
}
|
|
18965
|
-
set flipY(value) {
|
|
18966
|
-
this.#flip.y = value ? -1 : 1;
|
|
18967
|
-
this.setDirty();
|
|
18968
|
-
}
|
|
18969
|
-
get cropOffset() {
|
|
18970
|
-
return this.#cropOffset;
|
|
18971
|
-
}
|
|
18972
|
-
set cropOffset(value) {
|
|
18973
|
-
this.#cropOffset = value;
|
|
18974
|
-
}
|
|
18975
|
-
get textureId() {
|
|
18976
|
-
return this.#textureId;
|
|
18977
|
-
}
|
|
18978
|
-
get isPrimitive() {
|
|
18979
|
-
return this.#textureId === PRIMITIVE_TEXTURE;
|
|
18980
|
-
}
|
|
18981
|
-
get isCircle() {
|
|
18982
|
-
return this.#atlasCoords.atlasIndex === CIRCLE_INDEX;
|
|
18983
|
-
}
|
|
18984
|
-
extra = {
|
|
18985
|
-
setAtlasCoords: (value) => {
|
|
18986
|
-
this.#atlasCoords = value;
|
|
18987
|
-
},
|
|
18988
|
-
cropRatio: () => {
|
|
18989
|
-
return this.#cropRatio;
|
|
18990
|
-
},
|
|
18991
|
-
atlasSize: () => {
|
|
18992
|
-
return this.#atlasSize;
|
|
18993
|
-
}
|
|
18994
|
-
};
|
|
18620
|
+
// Output from vertex shader to fragment shader
|
|
18621
|
+
struct VertexOutput {
|
|
18622
|
+
@builtin(position) position: vec4f,
|
|
18623
|
+
@location(0) texcoord: vec2f,
|
|
18624
|
+
@location(1) debugColor: vec4f,
|
|
18625
|
+
@location(2) @interpolate(flat) instanceIndex: u32,
|
|
18626
|
+
};
|
|
18627
|
+
|
|
18628
|
+
// Metadata for a single character glyph
|
|
18629
|
+
struct Char {
|
|
18630
|
+
texOffset: vec2f, // Offset to top-left in MSDF texture (pixels)
|
|
18631
|
+
texExtent: vec2f, // Size in texture (pixels)
|
|
18632
|
+
size: vec2f, // Glyph size in ems
|
|
18633
|
+
offset: vec2f, // Position offset in ems
|
|
18634
|
+
};
|
|
18635
|
+
|
|
18636
|
+
// Metadata for a text block
|
|
18637
|
+
struct TextBlockDescriptor {
|
|
18638
|
+
transform: mat3x3f, // Text transform matrix (model matrix)
|
|
18639
|
+
color: vec4f, // Text color
|
|
18640
|
+
fontSize: f32, // Font size
|
|
18641
|
+
blockWidth: f32, // Total width of text block
|
|
18642
|
+
blockHeight: f32, // Total height of text block
|
|
18643
|
+
bufferPosition: f32 // Index and length in textBuffer
|
|
18644
|
+
};
|
|
18645
|
+
|
|
18646
|
+
// Font bindings
|
|
18647
|
+
@group(0) @binding(0) var fontTexture: texture_2d<f32>;
|
|
18648
|
+
@group(0) @binding(1) var fontSampler: sampler;
|
|
18649
|
+
@group(0) @binding(2) var<storage> chars: array<Char>;
|
|
18650
|
+
@group(0) @binding(3) var<uniform> fontData: vec4f; // Contains line height (x)
|
|
18651
|
+
|
|
18652
|
+
// Text bindings
|
|
18653
|
+
@group(1) @binding(0) var<storage> texts: array<TextBlockDescriptor>;
|
|
18654
|
+
@group(1) @binding(1) var<storage> textBuffer: array<vec4f>; // Each vec4: xy = glyph pos, z = char index
|
|
18655
|
+
|
|
18656
|
+
// Global uniforms
|
|
18657
|
+
@group(2) @binding(0) var<uniform> viewProjectionMatrix: mat3x3f;
|
|
18658
|
+
|
|
18659
|
+
// Vertex shader
|
|
18660
|
+
@vertex
|
|
18661
|
+
fn vertexMain(input: VertexInput) -> VertexOutput {
|
|
18662
|
+
// Because the instance index is used for character indexing, we are
|
|
18663
|
+
// overloading the vertex index to store the instance of the text metadata.
|
|
18664
|
+
//
|
|
18665
|
+
// I.e...
|
|
18666
|
+
// Vertex 0-4 = Instance 0, Vertex 0-4
|
|
18667
|
+
// Vertex 4-8 = Instance 1, Vertex 0-4
|
|
18668
|
+
// Vertex 8-12 = Instance 2, Vertex 0-4
|
|
18669
|
+
let vertexIndex = input.vertex % 4;
|
|
18670
|
+
let textIndex = input.vertex / 4;
|
|
18671
|
+
|
|
18672
|
+
let text = texts[textIndex];
|
|
18673
|
+
let textElement = textBuffer[u32(text.bufferPosition) + input.instance];
|
|
18674
|
+
let char = chars[u32(textElement.z)];
|
|
18675
|
+
|
|
18676
|
+
let lineHeight = fontData.x;
|
|
18677
|
+
let textWidth = text.blockWidth;
|
|
18678
|
+
let textHeight = text.blockHeight;
|
|
18679
|
+
|
|
18680
|
+
// Center text vertically; origin is mid-height
|
|
18681
|
+
let offset = vec2f(0, -textHeight / 2);
|
|
18682
|
+
|
|
18683
|
+
// Glyph position in ems (quad pos * size + per-char offset)
|
|
18684
|
+
let emPos = pos[vertexIndex] * char.size + char.offset + textElement.xy - offset;
|
|
18685
|
+
let charPos = emPos * (text.fontSize / lineHeight);
|
|
18686
|
+
|
|
18687
|
+
var output: VertexOutput;
|
|
18688
|
+
let transformedPosition = viewProjectionMatrix * text.transform * vec3f(charPos, 1);
|
|
18689
|
+
|
|
18690
|
+
output.position = vec4f(transformedPosition, 1);
|
|
18691
|
+
output.texcoord = pos[vertexIndex] * vec2f(1, -1);
|
|
18692
|
+
output.texcoord *= char.texExtent;
|
|
18693
|
+
output.texcoord += char.texOffset;
|
|
18694
|
+
output.debugColor = debugColors[vertexIndex];
|
|
18695
|
+
output.instanceIndex = textIndex;
|
|
18696
|
+
return output;
|
|
18697
|
+
|
|
18698
|
+
// To debug - hardcode quad in bottom right quarter of the screen:
|
|
18699
|
+
// output.position = vec4f(pos[input.vertex], 0, 1);
|
|
18995
18700
|
}
|
|
18996
|
-
|
|
18997
|
-
|
|
18998
|
-
|
|
18999
|
-
|
|
19000
|
-
|
|
19001
|
-
array.set([node.color.r, node.color.g, node.color.b, node.color.a], offset + 12);
|
|
19002
|
-
const region = node.region;
|
|
19003
|
-
if (node.textureId === PRIMITIVE_TEXTURE) {
|
|
19004
|
-
array.set([
|
|
19005
|
-
node.atlasCoords.uvOffset.x,
|
|
19006
|
-
node.atlasCoords.uvOffset.y,
|
|
19007
|
-
node.atlasCoords.uvScale.width,
|
|
19008
|
-
node.atlasCoords.uvScale.height
|
|
19009
|
-
], offset + 16);
|
|
19010
|
-
} else {
|
|
19011
|
-
const atlasSize = node.extra.atlasSize();
|
|
19012
|
-
array.set([
|
|
19013
|
-
node.atlasCoords.uvOffset.x + region.x / atlasSize.width,
|
|
19014
|
-
node.atlasCoords.uvOffset.y + region.y / atlasSize.height,
|
|
19015
|
-
region.width / atlasSize.width,
|
|
19016
|
-
region.height / atlasSize.height
|
|
19017
|
-
], offset + 16);
|
|
19018
|
-
}
|
|
19019
|
-
array.set([
|
|
19020
|
-
node.cropOffset.x / 2 / (node.atlasCoords.originalSize.width || 1),
|
|
19021
|
-
node.cropOffset.y / 2 / (node.atlasCoords.originalSize.height || 1),
|
|
19022
|
-
node.extra.cropRatio().width,
|
|
19023
|
-
node.extra.cropRatio().height
|
|
19024
|
-
], offset + 20);
|
|
19025
|
-
new DataView(array.buffer).setUint32(array.byteOffset + (offset + 24) * Float32Array.BYTES_PER_ELEMENT, node.atlasCoords.atlasIndex, true);
|
|
19026
|
-
node.writeInstance?.(array, offset + 28);
|
|
19027
|
-
return 1;
|
|
18701
|
+
|
|
18702
|
+
// Signed distance function sampling for MSDF font rendering
|
|
18703
|
+
fn sampleMsdf(texcoord: vec2f) -> f32 {
|
|
18704
|
+
let c = textureSample(fontTexture, fontSampler, texcoord);
|
|
18705
|
+
return max(min(c.r, c.g), min(max(c.r, c.g), c.b));
|
|
19028
18706
|
}
|
|
19029
18707
|
|
|
19030
|
-
//
|
|
19031
|
-
|
|
19032
|
-
|
|
18708
|
+
// Fragment shader
|
|
18709
|
+
// Anti-aliasing technique by Paul Houx
|
|
18710
|
+
// more details here:
|
|
18711
|
+
// https://github.com/Chlumsky/msdfgen/issues/22#issuecomment-234958005
|
|
18712
|
+
@fragment
|
|
18713
|
+
fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {
|
|
18714
|
+
let text = texts[input.instanceIndex];
|
|
19033
18715
|
|
|
19034
|
-
|
|
19035
|
-
|
|
19036
|
-
|
|
19037
|
-
|
|
19038
|
-
|
|
19039
|
-
|
|
19040
|
-
|
|
19041
|
-
|
|
19042
|
-
|
|
19043
|
-
|
|
19044
|
-
|
|
19045
|
-
|
|
19046
|
-
|
|
19047
|
-
|
|
19048
|
-
|
|
19049
|
-
|
|
19050
|
-
for (const tile of options.tiles) {
|
|
19051
|
-
assert(tile.atlasCoords, "JumboQuadNode requires atlas coords to be provided");
|
|
19052
|
-
assert(tile.size, "JumboQuadNode requires a size to be provided");
|
|
19053
|
-
this.#tiles.push({
|
|
19054
|
-
textureId: tile.textureId,
|
|
19055
|
-
offset: tile.offset,
|
|
19056
|
-
size: tile.size,
|
|
19057
|
-
atlasCoords: tile.atlasCoords
|
|
19058
|
-
});
|
|
19059
|
-
}
|
|
19060
|
-
}
|
|
19061
|
-
get atlasCoords() {
|
|
19062
|
-
throw new Error("JumboQuadNode does not have a single atlas coords");
|
|
19063
|
-
}
|
|
19064
|
-
get tiles() {
|
|
19065
|
-
return this.#tiles;
|
|
19066
|
-
}
|
|
19067
|
-
getTileMatrix(tile) {
|
|
19068
|
-
const matrix = mat3.clone(this.matrix, this.#matrixPool.get());
|
|
19069
|
-
const originalSize = {
|
|
19070
|
-
width: Math.max(...this.#tiles.map((t3) => t3.offset.x + t3.size.width)),
|
|
19071
|
-
height: Math.max(...this.#tiles.map((t3) => t3.offset.y + t3.size.height))
|
|
19072
|
-
};
|
|
19073
|
-
const proportionalSize = {
|
|
19074
|
-
width: this.size.width / originalSize.width,
|
|
19075
|
-
height: this.size.height / originalSize.height
|
|
19076
|
-
};
|
|
19077
|
-
const centerOffset = {
|
|
19078
|
-
x: tile.offset.x + tile.size.width / 2 - originalSize.width / 2,
|
|
19079
|
-
y: -(tile.offset.y + tile.size.height / 2 - originalSize.height / 2)
|
|
19080
|
-
};
|
|
19081
|
-
mat3.translate(matrix, [
|
|
19082
|
-
centerOffset.x * proportionalSize.width,
|
|
19083
|
-
centerOffset.y * proportionalSize.height
|
|
19084
|
-
], matrix);
|
|
19085
|
-
mat3.scale(matrix, [
|
|
19086
|
-
tile.size.width * proportionalSize.width * (this.flipX ? -1 : 1),
|
|
19087
|
-
tile.size.height * proportionalSize.height * (this.flipY ? -1 : 1)
|
|
19088
|
-
], matrix);
|
|
19089
|
-
return matrix;
|
|
18716
|
+
// pxRange (AKA distanceRange) comes from the msdfgen tool.
|
|
18717
|
+
let pxRange = 4.0;
|
|
18718
|
+
let texSize = vec2f(textureDimensions(fontTexture, 0));
|
|
18719
|
+
|
|
18720
|
+
let dx = texSize.x * length(vec2f(dpdxFine(input.texcoord.x), dpdyFine(input.texcoord.x)));
|
|
18721
|
+
let dy = texSize.y * length(vec2f(dpdxFine(input.texcoord.y), dpdyFine(input.texcoord.y)));
|
|
18722
|
+
|
|
18723
|
+
let toPixels = pxRange * inverseSqrt(dx * dx + dy * dy);
|
|
18724
|
+
let sigDist = sampleMsdf(input.texcoord) - 0.5;
|
|
18725
|
+
let pxDist = sigDist * toPixels;
|
|
18726
|
+
|
|
18727
|
+
let edgeWidth = 0.5;
|
|
18728
|
+
let alpha = smoothstep(-edgeWidth, edgeWidth, pxDist);
|
|
18729
|
+
|
|
18730
|
+
if (alpha < 0.001) {
|
|
18731
|
+
discard;
|
|
19090
18732
|
}
|
|
18733
|
+
|
|
18734
|
+
let msdfColor = vec4f(text.color.rgb, text.color.a * alpha);
|
|
18735
|
+
return msdfColor;
|
|
18736
|
+
|
|
18737
|
+
// Debug options:
|
|
18738
|
+
// return text.color;
|
|
18739
|
+
// return input.debugColor;
|
|
18740
|
+
// return vec4f(1, 0, 1, 1); // hardcoded magenta
|
|
18741
|
+
// return textureSample(fontTexture, fontSampler, input.texcoord);
|
|
19091
18742
|
}
|
|
19092
|
-
|
|
19093
|
-
|
|
19094
|
-
|
|
18743
|
+
`;
|
|
18744
|
+
|
|
18745
|
+
// src/backends/webgpu/FontPipeline.ts
|
|
18746
|
+
class FontPipeline2 {
|
|
18747
|
+
pipeline;
|
|
18748
|
+
font;
|
|
18749
|
+
fontBindGroup;
|
|
18750
|
+
maxCharCount;
|
|
18751
|
+
constructor(pipeline, font, fontBindGroup, maxCharCount) {
|
|
18752
|
+
this.pipeline = pipeline;
|
|
18753
|
+
this.font = font;
|
|
18754
|
+
this.fontBindGroup = fontBindGroup;
|
|
18755
|
+
this.maxCharCount = maxCharCount;
|
|
19095
18756
|
}
|
|
19096
|
-
|
|
19097
|
-
|
|
19098
|
-
const
|
|
19099
|
-
|
|
19100
|
-
|
|
19101
|
-
|
|
19102
|
-
|
|
19103
|
-
|
|
19104
|
-
|
|
19105
|
-
|
|
19106
|
-
|
|
19107
|
-
|
|
19108
|
-
|
|
19109
|
-
|
|
19110
|
-
|
|
19111
|
-
const
|
|
19112
|
-
|
|
19113
|
-
|
|
19114
|
-
|
|
19115
|
-
|
|
19116
|
-
|
|
19117
|
-
|
|
19118
|
-
|
|
19119
|
-
|
|
19120
|
-
|
|
19121
|
-
|
|
19122
|
-
|
|
19123
|
-
|
|
18757
|
+
static async create(device, font, colorFormat, maxCharCount) {
|
|
18758
|
+
const pipeline = await pipelinePromise(device, colorFormat, font.name);
|
|
18759
|
+
const texture = device.createTexture({
|
|
18760
|
+
label: `MSDF font ${font.name}`,
|
|
18761
|
+
size: [font.imageBitmap.width, font.imageBitmap.height, 1],
|
|
18762
|
+
format: "rgba8unorm",
|
|
18763
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
|
|
18764
|
+
});
|
|
18765
|
+
device.queue.copyExternalImageToTexture({ source: font.imageBitmap }, { texture }, [font.imageBitmap.width, font.imageBitmap.height]);
|
|
18766
|
+
const charsGpuBuffer = device.createBuffer({
|
|
18767
|
+
label: `MSDF font ${font.name} character layout buffer`,
|
|
18768
|
+
size: font.charCount * Float32Array.BYTES_PER_ELEMENT * 8,
|
|
18769
|
+
usage: GPUBufferUsage.STORAGE,
|
|
18770
|
+
mappedAtCreation: true
|
|
18771
|
+
});
|
|
18772
|
+
const charsArray = new Float32Array(charsGpuBuffer.getMappedRange());
|
|
18773
|
+
charsArray.set(font.charBuffer, 0);
|
|
18774
|
+
charsGpuBuffer.unmap();
|
|
18775
|
+
const fontDataBuffer = device.createBuffer({
|
|
18776
|
+
label: `MSDF font ${font.name} metadata buffer`,
|
|
18777
|
+
size: Float32Array.BYTES_PER_ELEMENT * 4,
|
|
18778
|
+
usage: GPUBufferUsage.UNIFORM,
|
|
18779
|
+
mappedAtCreation: true
|
|
18780
|
+
});
|
|
18781
|
+
const fontDataArray = new Float32Array(fontDataBuffer.getMappedRange());
|
|
18782
|
+
fontDataArray[0] = font.lineHeight;
|
|
18783
|
+
fontDataBuffer.unmap();
|
|
18784
|
+
const fontBindGroup = device.createBindGroup({
|
|
18785
|
+
layout: pipeline.getBindGroupLayout(0),
|
|
18786
|
+
entries: [
|
|
18787
|
+
{
|
|
18788
|
+
binding: 0,
|
|
18789
|
+
resource: texture.createView()
|
|
18790
|
+
},
|
|
18791
|
+
{
|
|
18792
|
+
binding: 1,
|
|
18793
|
+
resource: device.createSampler(sampler)
|
|
18794
|
+
},
|
|
18795
|
+
{
|
|
18796
|
+
binding: 2,
|
|
18797
|
+
resource: {
|
|
18798
|
+
buffer: charsGpuBuffer
|
|
18799
|
+
}
|
|
18800
|
+
},
|
|
18801
|
+
{
|
|
18802
|
+
binding: 3,
|
|
18803
|
+
resource: {
|
|
18804
|
+
buffer: fontDataBuffer
|
|
18805
|
+
}
|
|
18806
|
+
}
|
|
18807
|
+
]
|
|
18808
|
+
});
|
|
18809
|
+
return new FontPipeline2(pipeline, font, fontBindGroup, maxCharCount);
|
|
19124
18810
|
}
|
|
19125
|
-
node.writeInstance?.(array, offset + tileOffset);
|
|
19126
|
-
return node.tiles.length;
|
|
19127
18811
|
}
|
|
18812
|
+
function pipelinePromise(device, colorFormat, label) {
|
|
18813
|
+
const shader = device.createShaderModule({
|
|
18814
|
+
label: `${label} shader`,
|
|
18815
|
+
code: text_wgsl_default
|
|
18816
|
+
});
|
|
18817
|
+
return device.createRenderPipelineAsync({
|
|
18818
|
+
label: `${label} pipeline`,
|
|
18819
|
+
layout: device.createPipelineLayout({
|
|
18820
|
+
bindGroupLayouts: [
|
|
18821
|
+
device.createBindGroupLayout(fontBindGroupLayout),
|
|
18822
|
+
device.createBindGroupLayout(textUniformBindGroupLayout),
|
|
18823
|
+
device.createBindGroupLayout(engineUniformBindGroupLayout)
|
|
18824
|
+
]
|
|
18825
|
+
}),
|
|
18826
|
+
vertex: {
|
|
18827
|
+
module: shader,
|
|
18828
|
+
entryPoint: "vertexMain"
|
|
18829
|
+
},
|
|
18830
|
+
fragment: {
|
|
18831
|
+
module: shader,
|
|
18832
|
+
entryPoint: "fragmentMain",
|
|
18833
|
+
targets: [
|
|
18834
|
+
{
|
|
18835
|
+
format: colorFormat,
|
|
18836
|
+
blend: {
|
|
18837
|
+
color: {
|
|
18838
|
+
srcFactor: "src-alpha",
|
|
18839
|
+
dstFactor: "one-minus-src-alpha"
|
|
18840
|
+
},
|
|
18841
|
+
alpha: {
|
|
18842
|
+
srcFactor: "one",
|
|
18843
|
+
dstFactor: "one"
|
|
18844
|
+
}
|
|
18845
|
+
}
|
|
18846
|
+
}
|
|
18847
|
+
]
|
|
18848
|
+
},
|
|
18849
|
+
primitive: {
|
|
18850
|
+
topology: "triangle-strip",
|
|
18851
|
+
stripIndexFormat: "uint32"
|
|
18852
|
+
}
|
|
18853
|
+
});
|
|
18854
|
+
}
|
|
18855
|
+
if (typeof GPUShaderStage === "undefined") {
|
|
18856
|
+
globalThis.GPUShaderStage = {
|
|
18857
|
+
VERTEX: 1,
|
|
18858
|
+
FRAGMENT: 2,
|
|
18859
|
+
COMPUTE: 4
|
|
18860
|
+
};
|
|
18861
|
+
}
|
|
18862
|
+
var fontBindGroupLayout = {
|
|
18863
|
+
label: "MSDF font group layout",
|
|
18864
|
+
entries: [
|
|
18865
|
+
{
|
|
18866
|
+
binding: 0,
|
|
18867
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
18868
|
+
texture: {}
|
|
18869
|
+
},
|
|
18870
|
+
{
|
|
18871
|
+
binding: 1,
|
|
18872
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
18873
|
+
sampler: {}
|
|
18874
|
+
},
|
|
18875
|
+
{
|
|
18876
|
+
binding: 2,
|
|
18877
|
+
visibility: GPUShaderStage.VERTEX,
|
|
18878
|
+
buffer: { type: "read-only-storage" }
|
|
18879
|
+
},
|
|
18880
|
+
{
|
|
18881
|
+
binding: 3,
|
|
18882
|
+
visibility: GPUShaderStage.VERTEX,
|
|
18883
|
+
buffer: {}
|
|
18884
|
+
}
|
|
18885
|
+
]
|
|
18886
|
+
};
|
|
18887
|
+
var engineUniformBindGroupLayout = {
|
|
18888
|
+
label: "Uniform bind group",
|
|
18889
|
+
entries: [
|
|
18890
|
+
{
|
|
18891
|
+
binding: 0,
|
|
18892
|
+
visibility: GPUShaderStage.VERTEX,
|
|
18893
|
+
buffer: {}
|
|
18894
|
+
}
|
|
18895
|
+
]
|
|
18896
|
+
};
|
|
18897
|
+
var sampler = {
|
|
18898
|
+
label: "MSDF text sampler",
|
|
18899
|
+
minFilter: "linear",
|
|
18900
|
+
magFilter: "linear",
|
|
18901
|
+
mipmapFilter: "linear",
|
|
18902
|
+
maxAnisotropy: 16
|
|
18903
|
+
};
|
|
18904
|
+
var textUniformBindGroupLayout = {
|
|
18905
|
+
label: "MSDF text block uniform",
|
|
18906
|
+
entries: [
|
|
18907
|
+
{
|
|
18908
|
+
binding: 0,
|
|
18909
|
+
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
|
|
18910
|
+
buffer: { type: "read-only-storage" }
|
|
18911
|
+
},
|
|
18912
|
+
{
|
|
18913
|
+
binding: 1,
|
|
18914
|
+
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
|
|
18915
|
+
buffer: { type: "read-only-storage" }
|
|
18916
|
+
}
|
|
18917
|
+
]
|
|
18918
|
+
};
|
|
19128
18919
|
|
|
19129
18920
|
// src/backends/webgpu/wgsl/pixel-scraping.wgsl.ts
|
|
19130
18921
|
var pixel_scraping_wgsl_default = `
|
|
@@ -19499,6 +19290,130 @@ function createPipelines(device, label) {
|
|
|
19499
19290
|
};
|
|
19500
19291
|
}
|
|
19501
19292
|
|
|
19293
|
+
// src/backends/webgpu/WebGPUTextShader.ts
|
|
19294
|
+
var deets = new _t2(text_wgsl_default);
|
|
19295
|
+
var struct = deets.structs.find((s3) => s3.name === "TextBlockDescriptor");
|
|
19296
|
+
if (!struct) {
|
|
19297
|
+
throw new Error("FormattedText struct not found");
|
|
19298
|
+
}
|
|
19299
|
+
var textDescriptorInstanceSize = struct.size;
|
|
19300
|
+
|
|
19301
|
+
class WebGPUTextShader2 {
|
|
19302
|
+
label = "text";
|
|
19303
|
+
code = text_wgsl_default;
|
|
19304
|
+
#backend;
|
|
19305
|
+
#pipeline;
|
|
19306
|
+
#bindGroups = [];
|
|
19307
|
+
#font;
|
|
19308
|
+
#maxCharCount;
|
|
19309
|
+
#engineUniformsBuffer;
|
|
19310
|
+
#descriptorBuffer;
|
|
19311
|
+
#textBlockBuffer;
|
|
19312
|
+
#cpuDescriptorBuffer;
|
|
19313
|
+
#cpuTextBlockBuffer;
|
|
19314
|
+
#instanceIndex = 0;
|
|
19315
|
+
#textBlockOffset = 0;
|
|
19316
|
+
constructor(backend, pipeline, font, _colorFormat, instanceCount) {
|
|
19317
|
+
this.#backend = backend;
|
|
19318
|
+
const device = backend.device;
|
|
19319
|
+
this.#font = font;
|
|
19320
|
+
this.#pipeline = pipeline.pipeline;
|
|
19321
|
+
this.#maxCharCount = pipeline.maxCharCount;
|
|
19322
|
+
this.#descriptorBuffer = device.createBuffer({
|
|
19323
|
+
label: "msdf text descriptor buffer",
|
|
19324
|
+
size: textDescriptorInstanceSize * instanceCount,
|
|
19325
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
19326
|
+
});
|
|
19327
|
+
this.#cpuDescriptorBuffer = new Float32Array(instanceCount * textDescriptorInstanceSize / Float32Array.BYTES_PER_ELEMENT);
|
|
19328
|
+
this.#cpuTextBlockBuffer = new Float32Array(instanceCount * this.maxCharCount * 4);
|
|
19329
|
+
this.#engineUniformsBuffer = device.createBuffer({
|
|
19330
|
+
label: "msdf view projection matrix",
|
|
19331
|
+
size: Float32Array.BYTES_PER_ELEMENT * 12,
|
|
19332
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
19333
|
+
});
|
|
19334
|
+
this.#textBlockBuffer = device.createBuffer({
|
|
19335
|
+
label: "msdf text buffer",
|
|
19336
|
+
size: instanceCount * this.maxCharCount * 4 * Float32Array.BYTES_PER_ELEMENT,
|
|
19337
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
19338
|
+
});
|
|
19339
|
+
this.#bindGroups.push(pipeline.fontBindGroup);
|
|
19340
|
+
this.#bindGroups.push(device.createBindGroup({
|
|
19341
|
+
label: "msdf text bind group",
|
|
19342
|
+
layout: pipeline.pipeline.getBindGroupLayout(1),
|
|
19343
|
+
entries: [
|
|
19344
|
+
{
|
|
19345
|
+
binding: 0,
|
|
19346
|
+
resource: { buffer: this.#descriptorBuffer }
|
|
19347
|
+
},
|
|
19348
|
+
{
|
|
19349
|
+
binding: 1,
|
|
19350
|
+
resource: { buffer: this.#textBlockBuffer }
|
|
19351
|
+
}
|
|
19352
|
+
]
|
|
19353
|
+
}));
|
|
19354
|
+
const engineUniformsBindGroup = device.createBindGroup({
|
|
19355
|
+
label: "msdf text uniforms bind group",
|
|
19356
|
+
layout: pipeline.pipeline.getBindGroupLayout(2),
|
|
19357
|
+
entries: [
|
|
19358
|
+
{
|
|
19359
|
+
binding: 0,
|
|
19360
|
+
resource: { buffer: this.#engineUniformsBuffer }
|
|
19361
|
+
}
|
|
19362
|
+
]
|
|
19363
|
+
});
|
|
19364
|
+
this.#bindGroups.push(engineUniformsBindGroup);
|
|
19365
|
+
}
|
|
19366
|
+
startFrame(uniform) {
|
|
19367
|
+
const device = this.#backend.device;
|
|
19368
|
+
device.queue.writeBuffer(this.#engineUniformsBuffer, 0, uniform.viewProjectionMatrix);
|
|
19369
|
+
this.#instanceIndex = 0;
|
|
19370
|
+
this.#textBlockOffset = 0;
|
|
19371
|
+
}
|
|
19372
|
+
processBatch(nodes) {
|
|
19373
|
+
if (nodes.length === 0)
|
|
19374
|
+
return 0;
|
|
19375
|
+
const renderPass = this.#backend.renderPass;
|
|
19376
|
+
renderPass.setPipeline(this.#pipeline);
|
|
19377
|
+
for (let i3 = 0;i3 < this.#bindGroups.length; i3++) {
|
|
19378
|
+
renderPass.setBindGroup(i3, this.#bindGroups[i3]);
|
|
19379
|
+
}
|
|
19380
|
+
for (const node of nodes) {
|
|
19381
|
+
if (!(node instanceof TextNode)) {
|
|
19382
|
+
console.error(node);
|
|
19383
|
+
throw new Error(`Tried to use WebGPUTextShader on something that isn't a TextNode: ${node}`);
|
|
19384
|
+
}
|
|
19385
|
+
const text = node.text;
|
|
19386
|
+
const formatting = node.formatting;
|
|
19387
|
+
const measurements = measureText(this.#font, text, formatting.wordWrap);
|
|
19388
|
+
const textBlockSize = 4 * text.length;
|
|
19389
|
+
const textDescriptorOffset = this.#instanceIndex * textDescriptorInstanceSize / Float32Array.BYTES_PER_ELEMENT;
|
|
19390
|
+
this.#cpuDescriptorBuffer.set(node.matrix, textDescriptorOffset);
|
|
19391
|
+
this.#cpuDescriptorBuffer.set([node.tint.r, node.tint.g, node.tint.b, node.tint.a], textDescriptorOffset + 12);
|
|
19392
|
+
const size = node.size ?? measurements;
|
|
19393
|
+
const fontSize = formatting.shrinkToFit ? findLargestFontSize(this.#font, text, size, formatting) : formatting.fontSize;
|
|
19394
|
+
const actualFontSize = fontSize || DEFAULT_FONT_SIZE;
|
|
19395
|
+
this.#cpuDescriptorBuffer[textDescriptorOffset + 16] = actualFontSize;
|
|
19396
|
+
this.#cpuDescriptorBuffer[textDescriptorOffset + 17] = formatting.align === "center" ? 0 : measurements.width;
|
|
19397
|
+
this.#cpuDescriptorBuffer[textDescriptorOffset + 18] = measurements.height;
|
|
19398
|
+
this.#cpuDescriptorBuffer[textDescriptorOffset + 19] = this.#textBlockOffset / 4;
|
|
19399
|
+
shapeText(this.#font, text, size, actualFontSize, formatting, this.#cpuTextBlockBuffer, this.#textBlockOffset);
|
|
19400
|
+
this.#backend.device.queue.writeBuffer(this.#descriptorBuffer, textDescriptorOffset * Float32Array.BYTES_PER_ELEMENT, this.#cpuDescriptorBuffer, textDescriptorOffset, textDescriptorInstanceSize / Float32Array.BYTES_PER_ELEMENT);
|
|
19401
|
+
this.#backend.device.queue.writeBuffer(this.#textBlockBuffer, this.#textBlockOffset * Float32Array.BYTES_PER_ELEMENT, this.#cpuTextBlockBuffer, this.#textBlockOffset, textBlockSize);
|
|
19402
|
+
this.#textBlockOffset += textBlockSize;
|
|
19403
|
+
renderPass.draw(4, measurements.printedCharCount, 4 * this.#instanceIndex, 0);
|
|
19404
|
+
this.#instanceIndex++;
|
|
19405
|
+
}
|
|
19406
|
+
return nodes.length;
|
|
19407
|
+
}
|
|
19408
|
+
endFrame() {}
|
|
19409
|
+
get font() {
|
|
19410
|
+
return this.#font;
|
|
19411
|
+
}
|
|
19412
|
+
get maxCharCount() {
|
|
19413
|
+
return this.#maxCharCount;
|
|
19414
|
+
}
|
|
19415
|
+
}
|
|
19416
|
+
|
|
19502
19417
|
// src/textures/Bundles.ts
|
|
19503
19418
|
class Bundles {
|
|
19504
19419
|
#bundles = new Map;
|
|
@@ -19716,6 +19631,8 @@ class Bundles {
|
|
|
19716
19631
|
const rightCrop = frame.sourceSize.w - frame.spriteSourceSize.x - frame.spriteSourceSize.w;
|
|
19717
19632
|
const topCrop = frame.spriteSourceSize.y;
|
|
19718
19633
|
const bottomCrop = frame.sourceSize.h - frame.spriteSourceSize.y - frame.spriteSourceSize.h;
|
|
19634
|
+
const halfTexelX = 0.5 / atlasWidth;
|
|
19635
|
+
const halfTexelY = 0.5 / atlasHeight;
|
|
19719
19636
|
return {
|
|
19720
19637
|
cropOffset: {
|
|
19721
19638
|
x: leftCrop - rightCrop,
|
|
@@ -19726,16 +19643,16 @@ class Bundles {
|
|
|
19726
19643
|
height: frame.sourceSize.h
|
|
19727
19644
|
},
|
|
19728
19645
|
uvOffset: {
|
|
19729
|
-
x: frame.frame.x / atlasWidth,
|
|
19730
|
-
y: frame.frame.y / atlasHeight
|
|
19646
|
+
x: frame.frame.x / atlasWidth + halfTexelX,
|
|
19647
|
+
y: frame.frame.y / atlasHeight + halfTexelY
|
|
19731
19648
|
},
|
|
19732
19649
|
uvScale: {
|
|
19733
19650
|
width: frame.sourceSize.w / atlasWidth,
|
|
19734
19651
|
height: frame.sourceSize.h / atlasHeight
|
|
19735
19652
|
},
|
|
19736
19653
|
uvScaleCropped: {
|
|
19737
|
-
width: frame.frame.w / atlasWidth,
|
|
19738
|
-
height: frame.frame.h / atlasHeight
|
|
19654
|
+
width: (frame.frame.w - 1) / atlasWidth,
|
|
19655
|
+
height: (frame.frame.h - 1) / atlasHeight
|
|
19739
19656
|
}
|
|
19740
19657
|
};
|
|
19741
19658
|
}
|
|
@@ -19845,18 +19762,19 @@ async function packBitmapsToAtlas(images, textureSize, device) {
|
|
|
19845
19762
|
height: space.height - texture.height
|
|
19846
19763
|
});
|
|
19847
19764
|
}
|
|
19765
|
+
const halfTexel = 0.5 / textureSize;
|
|
19848
19766
|
atlasRegionMap.set(id, {
|
|
19849
19767
|
uvOffset: {
|
|
19850
|
-
x: space.x / textureSize,
|
|
19851
|
-
y: space.y / textureSize
|
|
19768
|
+
x: space.x / textureSize + halfTexel,
|
|
19769
|
+
y: space.y / textureSize + halfTexel
|
|
19852
19770
|
},
|
|
19853
19771
|
uvScale: {
|
|
19854
19772
|
width: originalSize.width / textureSize,
|
|
19855
19773
|
height: originalSize.height / textureSize
|
|
19856
19774
|
},
|
|
19857
19775
|
uvScaleCropped: {
|
|
19858
|
-
width: texture.width / textureSize,
|
|
19859
|
-
height: texture.height / textureSize
|
|
19776
|
+
width: (texture.width - 1) / textureSize,
|
|
19777
|
+
height: (texture.height - 1) / textureSize
|
|
19860
19778
|
},
|
|
19861
19779
|
cropOffset: offset,
|
|
19862
19780
|
originalSize
|
|
@@ -20170,13 +20088,13 @@ class AssetManager {
|
|
|
20170
20088
|
const webgpuBackend = this.#backend;
|
|
20171
20089
|
const device = webgpuBackend.device;
|
|
20172
20090
|
const presentationFormat = webgpuBackend.presentationFormat;
|
|
20173
|
-
const fontPipeline = await
|
|
20174
|
-
const textShader = new
|
|
20091
|
+
const fontPipeline = await FontPipeline2.create(device, font, presentationFormat, limits.maxTextLength);
|
|
20092
|
+
const textShader = new WebGPUTextShader2(webgpuBackend, fontPipeline, font, presentationFormat, limits.instanceCount);
|
|
20175
20093
|
this.#fonts.set(id, textShader);
|
|
20176
20094
|
} else {
|
|
20177
20095
|
const webglBackend = this.#backend;
|
|
20178
|
-
const fontPipeline =
|
|
20179
|
-
const textShader = new
|
|
20096
|
+
const fontPipeline = WebGLFontPipeline2.create(webglBackend.gl, font, limits.maxTextLength);
|
|
20097
|
+
const textShader = new WebGLTextShader2(webglBackend, fontPipeline);
|
|
20180
20098
|
this.#fonts.set(id, textShader);
|
|
20181
20099
|
}
|
|
20182
20100
|
return id;
|
|
@@ -20319,13 +20237,6 @@ class AssetManager {
|
|
|
20319
20237
|
}
|
|
20320
20238
|
}
|
|
20321
20239
|
|
|
20322
|
-
// src/utils/mod.ts
|
|
20323
|
-
var exports_mod2 = {};
|
|
20324
|
-
__export(exports_mod2, {
|
|
20325
|
-
assert: () => assert,
|
|
20326
|
-
Pool: () => Pool
|
|
20327
|
-
});
|
|
20328
|
-
|
|
20329
20240
|
// src/utils/pool.ts
|
|
20330
20241
|
class Pool {
|
|
20331
20242
|
#items = [];
|
|
@@ -20347,6 +20258,7 @@ class Pool {
|
|
|
20347
20258
|
this.#index = 0;
|
|
20348
20259
|
}
|
|
20349
20260
|
}
|
|
20261
|
+
|
|
20350
20262
|
// src/Toodle.ts
|
|
20351
20263
|
class Toodle {
|
|
20352
20264
|
assets;
|
|
@@ -20683,11 +20595,11 @@ class Toodle {
|
|
|
20683
20595
|
}
|
|
20684
20596
|
let backend;
|
|
20685
20597
|
if (backendType === "webgpu") {
|
|
20686
|
-
backend = await
|
|
20598
|
+
backend = await WebGPUBackend2.create(canvas, {
|
|
20687
20599
|
limits: options?.limits
|
|
20688
20600
|
});
|
|
20689
20601
|
} else {
|
|
20690
|
-
backend = await
|
|
20602
|
+
backend = await WebGLBackend2.create(canvas, {
|
|
20691
20603
|
limits: options?.limits
|
|
20692
20604
|
});
|
|
20693
20605
|
}
|
|
@@ -20705,204 +20617,20 @@ class Toodle {
|
|
|
20705
20617
|
return this.#backend;
|
|
20706
20618
|
}
|
|
20707
20619
|
}
|
|
20708
|
-
// src/colors/mod.ts
|
|
20709
|
-
var exports_mod3 = {};
|
|
20710
|
-
__export(exports_mod3, {
|
|
20711
|
-
web: () => web
|
|
20712
|
-
});
|
|
20713
|
-
var web = Object.freeze({
|
|
20714
|
-
aliceBlue: { r: 0.941176, g: 0.972549, b: 1, a: 1 },
|
|
20715
|
-
antiqueWhite: { r: 0.980392, g: 0.921569, b: 0.843137, a: 1 },
|
|
20716
|
-
aqua: { r: 0, g: 1, b: 1, a: 1 },
|
|
20717
|
-
aquamarine: { r: 0.498039, g: 1, b: 0.831373, a: 1 },
|
|
20718
|
-
azure: { r: 0.941176, g: 1, b: 1, a: 1 },
|
|
20719
|
-
beige: { r: 0.960784, g: 0.960784, b: 0.862745, a: 1 },
|
|
20720
|
-
bisque: { r: 1, g: 0.894118, b: 0.768627, a: 1 },
|
|
20721
|
-
black: { r: 0, g: 0, b: 0, a: 1 },
|
|
20722
|
-
blanchedAlmond: { r: 1, g: 0.921569, b: 0.803922, a: 1 },
|
|
20723
|
-
blue: { r: 0, g: 0, b: 1, a: 1 },
|
|
20724
|
-
blueViolet: { r: 0.541176, g: 0.168627, b: 0.886275, a: 1 },
|
|
20725
|
-
brown: { r: 0.647059, g: 0.164706, b: 0.164706, a: 1 },
|
|
20726
|
-
burlywood: { r: 0.870588, g: 0.721569, b: 0.529412, a: 1 },
|
|
20727
|
-
cadetBlue: { r: 0.372549, g: 0.619608, b: 0.627451, a: 1 },
|
|
20728
|
-
chartreuse: { r: 0.498039, g: 1, b: 0, a: 1 },
|
|
20729
|
-
chocolate: { r: 0.823529, g: 0.411765, b: 0.117647, a: 1 },
|
|
20730
|
-
coral: { r: 1, g: 0.498039, b: 0.313726, a: 1 },
|
|
20731
|
-
cornflowerBlue: { r: 0.392157, g: 0.584314, b: 0.929412, a: 1 },
|
|
20732
|
-
cornsilk: { r: 1, g: 0.972549, b: 0.862745, a: 1 },
|
|
20733
|
-
crimson: { r: 0.862745, g: 0.0784314, b: 0.235294, a: 1 },
|
|
20734
|
-
cyan: { r: 0, g: 1, b: 1, a: 1 },
|
|
20735
|
-
darkBlue: { r: 0, g: 0, b: 0.545098, a: 1 },
|
|
20736
|
-
darkCyan: { r: 0, g: 0.545098, b: 0.545098, a: 1 },
|
|
20737
|
-
darkGoldenrod: { r: 0.721569, g: 0.52549, b: 0.0431373, a: 1 },
|
|
20738
|
-
darkGray: { r: 0.662745, g: 0.662745, b: 0.662745, a: 1 },
|
|
20739
|
-
darkGreen: { r: 0, g: 0.392157, b: 0, a: 1 },
|
|
20740
|
-
darkKhaki: { r: 0.741176, g: 0.717647, b: 0.419608, a: 1 },
|
|
20741
|
-
darkMagenta: { r: 0.545098, g: 0, b: 0.545098, a: 1 },
|
|
20742
|
-
darkOliveGreen: { r: 0.333333, g: 0.419608, b: 0.184314, a: 1 },
|
|
20743
|
-
darkOrange: { r: 1, g: 0.54902, b: 0, a: 1 },
|
|
20744
|
-
darkOrchid: { r: 0.6, g: 0.196078, b: 0.8, a: 1 },
|
|
20745
|
-
darkRed: { r: 0.545098, g: 0, b: 0, a: 1 },
|
|
20746
|
-
darkSalmon: { r: 0.913725, g: 0.588235, b: 0.478431, a: 1 },
|
|
20747
|
-
darkSeaGreen: { r: 0.560784, g: 0.737255, b: 0.560784, a: 1 },
|
|
20748
|
-
darkSlateBlue: { r: 0.282353, g: 0.239216, b: 0.545098, a: 1 },
|
|
20749
|
-
darkSlateGray: { r: 0.184314, g: 0.309804, b: 0.309804, a: 1 },
|
|
20750
|
-
darkTurquoise: { r: 0, g: 0.807843, b: 0.819608, a: 1 },
|
|
20751
|
-
darkViolet: { r: 0.580392, g: 0, b: 0.827451, a: 1 },
|
|
20752
|
-
deepPink: { r: 1, g: 0.0784314, b: 0.576471, a: 1 },
|
|
20753
|
-
deepSkyBlue: { r: 0, g: 0.74902, b: 1, a: 1 },
|
|
20754
|
-
dimGray: { r: 0.411765, g: 0.411765, b: 0.411765, a: 1 },
|
|
20755
|
-
dodgerBlue: { r: 0.117647, g: 0.564706, b: 1, a: 1 },
|
|
20756
|
-
firebrick: { r: 0.698039, g: 0.133333, b: 0.133333, a: 1 },
|
|
20757
|
-
floralWhite: { r: 1, g: 0.980392, b: 0.941176, a: 1 },
|
|
20758
|
-
forestGreen: { r: 0.133333, g: 0.545098, b: 0.133333, a: 1 },
|
|
20759
|
-
fuchsia: { r: 1, g: 0, b: 1, a: 1 },
|
|
20760
|
-
gainsboro: { r: 0.862745, g: 0.862745, b: 0.862745, a: 1 },
|
|
20761
|
-
ghostWhite: { r: 0.972549, g: 0.972549, b: 1, a: 1 },
|
|
20762
|
-
gold: { r: 1, g: 0.843137, b: 0, a: 1 },
|
|
20763
|
-
goldenrod: { r: 0.854902, g: 0.647059, b: 0.12549, a: 1 },
|
|
20764
|
-
gray: { r: 0.745098, g: 0.745098, b: 0.745098, a: 1 },
|
|
20765
|
-
green: { r: 0, g: 1, b: 0, a: 1 },
|
|
20766
|
-
greenYellow: { r: 0.678431, g: 1, b: 0.184314, a: 1 },
|
|
20767
|
-
honeydew: { r: 0.941176, g: 1, b: 0.941176, a: 1 },
|
|
20768
|
-
hotPink: { r: 1, g: 0.411765, b: 0.705882, a: 1 },
|
|
20769
|
-
indigo: { r: 0.294118, g: 0, b: 0.509804, a: 1 },
|
|
20770
|
-
ivory: { r: 1, g: 1, b: 0.941176, a: 1 },
|
|
20771
|
-
khaki: { r: 0.941176, g: 0.901961, b: 0.54902, a: 1 },
|
|
20772
|
-
lavender: { r: 0.901961, g: 0.901961, b: 0.980392, a: 1 },
|
|
20773
|
-
lavenderBlush: { r: 1, g: 0.941176, b: 0.960784, a: 1 },
|
|
20774
|
-
lawnGreen: { r: 0.486275, g: 0.988235, b: 0, a: 1 },
|
|
20775
|
-
lemonChiffon: { r: 1, g: 0.980392, b: 0.803922, a: 1 },
|
|
20776
|
-
lightBlue: { r: 0.678431, g: 0.847059, b: 0.901961, a: 1 },
|
|
20777
|
-
lightCoral: { r: 0.941176, g: 0.501961, b: 0.501961, a: 1 },
|
|
20778
|
-
lightCyan: { r: 0.878431, g: 1, b: 1, a: 1 },
|
|
20779
|
-
lightGoldenrod: { r: 0.980392, g: 0.980392, b: 0.823529, a: 1 },
|
|
20780
|
-
lightGray: { r: 0.827451, g: 0.827451, b: 0.827451, a: 1 },
|
|
20781
|
-
lightGreen: { r: 0.564706, g: 0.933333, b: 0.564706, a: 1 },
|
|
20782
|
-
lightPink: { r: 1, g: 0.713726, b: 0.756863, a: 1 },
|
|
20783
|
-
lightSalmon: { r: 1, g: 0.627451, b: 0.478431, a: 1 },
|
|
20784
|
-
lightSeaGreen: { r: 0.12549, g: 0.698039, b: 0.666667, a: 1 },
|
|
20785
|
-
lightSkyBlue: { r: 0.529412, g: 0.807843, b: 0.980392, a: 1 },
|
|
20786
|
-
lightSlateGray: { r: 0.466667, g: 0.533333, b: 0.6, a: 1 },
|
|
20787
|
-
lightSteelBlue: { r: 0.690196, g: 0.768627, b: 0.870588, a: 1 },
|
|
20788
|
-
lightYellow: { r: 1, g: 1, b: 0.878431, a: 1 },
|
|
20789
|
-
lime: { r: 0, g: 1, b: 0, a: 1 },
|
|
20790
|
-
limeGreen: { r: 0.196078, g: 0.803922, b: 0.196078, a: 1 },
|
|
20791
|
-
linen: { r: 0.980392, g: 0.941176, b: 0.901961, a: 1 },
|
|
20792
|
-
magenta: { r: 1, g: 0, b: 1, a: 1 },
|
|
20793
|
-
maroon: { r: 0.690196, g: 0.188235, b: 0.376471, a: 1 },
|
|
20794
|
-
mediumAquamarine: { r: 0.4, g: 0.803922, b: 0.666667, a: 1 },
|
|
20795
|
-
mediumBlue: { r: 0, g: 0, b: 0.803922, a: 1 },
|
|
20796
|
-
mediumOrchid: { r: 0.729412, g: 0.333333, b: 0.827451, a: 1 },
|
|
20797
|
-
mediumPurple: { r: 0.576471, g: 0.439216, b: 0.858824, a: 1 },
|
|
20798
|
-
mediumSeaGreen: { r: 0.235294, g: 0.701961, b: 0.443137, a: 1 },
|
|
20799
|
-
mediumSlateBlue: { r: 0.482353, g: 0.407843, b: 0.933333, a: 1 },
|
|
20800
|
-
mediumSpringGreen: { r: 0, g: 0.980392, b: 0.603922, a: 1 },
|
|
20801
|
-
mediumTurquoise: { r: 0.282353, g: 0.819608, b: 0.8, a: 1 },
|
|
20802
|
-
mediumVioletRed: { r: 0.780392, g: 0.0823529, b: 0.521569, a: 1 },
|
|
20803
|
-
midnightBlue: { r: 0.0980392, g: 0.0980392, b: 0.439216, a: 1 },
|
|
20804
|
-
mintCream: { r: 0.960784, g: 1, b: 0.980392, a: 1 },
|
|
20805
|
-
mistyRose: { r: 1, g: 0.894118, b: 0.882353, a: 1 },
|
|
20806
|
-
moccasin: { r: 1, g: 0.894118, b: 0.709804, a: 1 },
|
|
20807
|
-
navyBlue: { r: 0, g: 0, b: 0.501961, a: 1 },
|
|
20808
|
-
oldLace: { r: 0.992157, g: 0.960784, b: 0.901961, a: 1 },
|
|
20809
|
-
olive: { r: 0.501961, g: 0.501961, b: 0, a: 1 },
|
|
20810
|
-
oliveDrab: { r: 0.419608, g: 0.556863, b: 0.137255, a: 1 },
|
|
20811
|
-
orange: { r: 1, g: 0.647059, b: 0, a: 1 },
|
|
20812
|
-
orangeRed: { r: 1, g: 0.270588, b: 0, a: 1 },
|
|
20813
|
-
orchid: { r: 0.854902, g: 0.439216, b: 0.839216, a: 1 },
|
|
20814
|
-
paleGoldenrod: { r: 0.933333, g: 0.909804, b: 0.666667, a: 1 },
|
|
20815
|
-
paleGreen: { r: 0.596078, g: 0.984314, b: 0.596078, a: 1 },
|
|
20816
|
-
paleTurquoise: { r: 0.686275, g: 0.933333, b: 0.933333, a: 1 },
|
|
20817
|
-
paleVioletRed: { r: 0.858824, g: 0.439216, b: 0.576471, a: 1 },
|
|
20818
|
-
papayaWhip: { r: 1, g: 0.937255, b: 0.835294, a: 1 },
|
|
20819
|
-
peachPuff: { r: 1, g: 0.854902, b: 0.72549, a: 1 },
|
|
20820
|
-
peru: { r: 0.803922, g: 0.521569, b: 0.247059, a: 1 },
|
|
20821
|
-
pink: { r: 1, g: 0.752941, b: 0.796078, a: 1 },
|
|
20822
|
-
plum: { r: 0.866667, g: 0.627451, b: 0.866667, a: 1 },
|
|
20823
|
-
powderBlue: { r: 0.690196, g: 0.878431, b: 0.901961, a: 1 },
|
|
20824
|
-
purple: { r: 0.627451, g: 0.12549, b: 0.941176, a: 1 },
|
|
20825
|
-
rebeccaPurple: { r: 0.4, g: 0.2, b: 0.6, a: 1 },
|
|
20826
|
-
red: { r: 1, g: 0, b: 0, a: 1 },
|
|
20827
|
-
rosyBrown: { r: 0.737255, g: 0.560784, b: 0.560784, a: 1 },
|
|
20828
|
-
royalBlue: { r: 0.254902, g: 0.411765, b: 0.882353, a: 1 },
|
|
20829
|
-
saddleBrown: { r: 0.545098, g: 0.270588, b: 0.0745098, a: 1 },
|
|
20830
|
-
salmon: { r: 0.980392, g: 0.501961, b: 0.447059, a: 1 },
|
|
20831
|
-
sandyBrown: { r: 0.956863, g: 0.643137, b: 0.376471, a: 1 },
|
|
20832
|
-
seaGreen: { r: 0.180392, g: 0.545098, b: 0.341176, a: 1 },
|
|
20833
|
-
seashell: { r: 1, g: 0.960784, b: 0.933333, a: 1 },
|
|
20834
|
-
sienna: { r: 0.627451, g: 0.321569, b: 0.176471, a: 1 },
|
|
20835
|
-
silver: { r: 0.752941, g: 0.752941, b: 0.752941, a: 1 },
|
|
20836
|
-
skyBlue: { r: 0.529412, g: 0.807843, b: 0.921569, a: 1 },
|
|
20837
|
-
slateBlue: { r: 0.415686, g: 0.352941, b: 0.803922, a: 1 },
|
|
20838
|
-
slateGray: { r: 0.439216, g: 0.501961, b: 0.564706, a: 1 },
|
|
20839
|
-
snow: { r: 1, g: 0.980392, b: 0.980392, a: 1 },
|
|
20840
|
-
springGreen: { r: 0, g: 1, b: 0.498039, a: 1 },
|
|
20841
|
-
steelBlue: { r: 0.27451, g: 0.509804, b: 0.705882, a: 1 },
|
|
20842
|
-
tan: { r: 0.823529, g: 0.705882, b: 0.54902, a: 1 },
|
|
20843
|
-
teal: { r: 0, g: 0.501961, b: 0.501961, a: 1 },
|
|
20844
|
-
thistle: { r: 0.847059, g: 0.74902, b: 0.847059, a: 1 },
|
|
20845
|
-
tomato: { r: 1, g: 0.388235, b: 0.278431, a: 1 },
|
|
20846
|
-
transparent: { r: 1, g: 1, b: 1, a: 0 },
|
|
20847
|
-
turquoise: { r: 0.25098, g: 0.878431, b: 0.815686, a: 1 },
|
|
20848
|
-
violet: { r: 0.933333, g: 0.509804, b: 0.933333, a: 1 },
|
|
20849
|
-
webGray: { r: 0.501961, g: 0.501961, b: 0.501961, a: 1 },
|
|
20850
|
-
webGreen: { r: 0, g: 0.501961, b: 0, a: 1 },
|
|
20851
|
-
webMaroon: { r: 0.501961, g: 0, b: 0, a: 1 },
|
|
20852
|
-
webPurple: { r: 0.501961, g: 0, b: 0.501961, a: 1 },
|
|
20853
|
-
wheat: { r: 0.960784, g: 0.870588, b: 0.701961, a: 1 },
|
|
20854
|
-
white: { r: 1, g: 1, b: 1, a: 1 },
|
|
20855
|
-
whiteSmoke: { r: 0.960784, g: 0.960784, b: 0.960784, a: 1 },
|
|
20856
|
-
yellow: { r: 1, g: 1, b: 0, a: 1 },
|
|
20857
|
-
yellowGreen: { r: 0.603922, g: 0.803922, b: 0.196078, a: 1 }
|
|
20858
|
-
});
|
|
20859
|
-
// src/math/mod.ts
|
|
20860
|
-
var exports_mod4 = {};
|
|
20861
|
-
__export(exports_mod4, {
|
|
20862
|
-
transformPoint: () => transformPoint,
|
|
20863
|
-
rad2deg: () => rad2deg,
|
|
20864
|
-
deg2rad: () => deg2rad,
|
|
20865
|
-
createViewMatrix: () => createViewMatrix,
|
|
20866
|
-
createProjectionMatrix: () => createProjectionMatrix,
|
|
20867
|
-
createModelMatrix: () => createModelMatrix,
|
|
20868
|
-
convertWorldToScreen: () => convertWorldToScreen,
|
|
20869
|
-
convertScreenToWorld: () => convertScreenToWorld
|
|
20870
|
-
});
|
|
20871
|
-
// src/scene/mod.ts
|
|
20872
|
-
var exports_mod5 = {};
|
|
20873
|
-
__export(exports_mod5, {
|
|
20874
|
-
TextNode: () => TextNode,
|
|
20875
|
-
SceneNode: () => SceneNode,
|
|
20876
|
-
QuadNode: () => QuadNode,
|
|
20877
|
-
DEFAULT_FONT_SIZE: () => DEFAULT_FONT_SIZE,
|
|
20878
|
-
Camera: () => Camera
|
|
20879
|
-
});
|
|
20880
|
-
// src/screen/mod.ts
|
|
20881
|
-
var exports_mod6 = {};
|
|
20882
|
-
// src/text/mod.ts
|
|
20883
|
-
var exports_mod7 = {};
|
|
20884
|
-
__export(exports_mod7, {
|
|
20885
|
-
TextShader: () => WebGPUTextShader
|
|
20886
|
-
});
|
|
20887
|
-
// src/textures/mod.ts
|
|
20888
|
-
var exports_mod8 = {};
|
|
20889
|
-
__export(exports_mod8, {
|
|
20890
|
-
Bundles: () => Bundles
|
|
20891
|
-
});
|
|
20892
20620
|
export {
|
|
20893
|
-
|
|
20621
|
+
Utils,
|
|
20894
20622
|
Toodle,
|
|
20895
|
-
|
|
20896
|
-
|
|
20897
|
-
|
|
20898
|
-
|
|
20899
|
-
|
|
20623
|
+
Textures,
|
|
20624
|
+
Text,
|
|
20625
|
+
Screen,
|
|
20626
|
+
Scene,
|
|
20627
|
+
GfxMath,
|
|
20900
20628
|
DEFAULT_LIMITS,
|
|
20901
|
-
|
|
20902
|
-
Bundles,
|
|
20903
|
-
|
|
20904
|
-
AssetManager
|
|
20629
|
+
Colors,
|
|
20630
|
+
Bundles2 as Bundles,
|
|
20631
|
+
Backends,
|
|
20632
|
+
AssetManager2 as AssetManager
|
|
20905
20633
|
};
|
|
20906
20634
|
|
|
20907
|
-
//# debugId=
|
|
20635
|
+
//# debugId=F5D1111C12CFF04864756E2164756E21
|
|
20908
20636
|
//# sourceMappingURL=mod.js.map
|