three-text 0.6.1 → 0.6.2
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/README.md +22 -25
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/index.min.cjs +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.min.js +1 -1
- package/dist/vector/core/index.cjs +20 -56
- package/dist/vector/core/index.js +20 -56
- package/dist/vector/index2.cjs +5 -19
- package/dist/vector/index2.js +5 -19
- package/dist/vector/slugTSL.cjs +4 -27
- package/dist/vector/slugTSL.js +4 -27
- package/dist/vector/webgl/index.cjs +1 -4
- package/dist/vector/webgl/index.js +1 -4
- package/dist/vector/webgpu/index.cjs +0 -3
- package/dist/vector/webgpu/index.js +0 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,8 +19,6 @@ High fidelity 3D text rendering and layout for the web
|
|
|
19
19
|
|
|
20
20
|
The library has a framework-agnostic core with lightweight adapters for [Three.js](https://threejs.org), [React Three Fiber](https://docs.pmnd.rs/react-three-fiber), [p5.js](https://p5js.org), [WebGL](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API), and [WebGPU](https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API)
|
|
21
21
|
|
|
22
|
-
Under the hood, three-text relies on a core of [harfbuzzjs](https://github.com/harfbuzz/harfbuzzjs) (based on [HarfBuzz](https://github.com/harfbuzz/harfbuzz) by Behdad Esfahbod et al) for text shaping, [Knuth-Plass](http://www.eprg.org/G53DOC/pdfs/knuth-plass-breaking.pdf) line breaking (with [SILE](https://github.com/sile-typesetter/sile/blob/master/core/break.lua) and LuaTex being the closest modern references), [Liang](https://tug.org/docs/liang/liang-thesis.pdf) hyphenation and the [TeX hyphenation patterns](https://github.com/hyphenation/tex-hyphen), and [woff-lib](https://github.com/countertype/woff-lib) for optional WOFF2 support. The mesh text pipeline uses [libtess-ts](https://github.com/countertype/libtess-ts) (a port of the [GLU tessellator](https://www.songho.ca/opengl/gl_tessellation.html) by Eric Veach) for removing overlaps and triangulation, adaptive curve polygonization from Maxim Shemanarev's [Anti-Grain Geometry](https://web.archive.org/web/20060128212843/http://www.antigrain.com/research/adaptive_bezier/index.html), and [Visvalingam-Whyatt](https://hull-repository.worktribe.com/preview/376364/000870493786962263.pdf) [line simplification](https://bost.ocks.org/mike/simplify/). The vector pipeline uses [Slug](https://github.com/EricLengyel/Slug) by Eric Lengyel for resolution-independent curve rendering
|
|
23
|
-
|
|
24
22
|
## Table of contents
|
|
25
23
|
|
|
26
24
|
- [Overview](#overview)
|
|
@@ -41,6 +39,7 @@ Under the hood, three-text relies on a core of [harfbuzzjs](https://github.com/h
|
|
|
41
39
|
- [Browser compatibility](#browser-compatibility)
|
|
42
40
|
- [Testing](#testing)
|
|
43
41
|
- [Build system](#build-system)
|
|
42
|
+
- [Entry points](#entry-points)
|
|
44
43
|
- [Build outputs](#build-outputs)
|
|
45
44
|
- [Acknowledgements](#acknowledgements)
|
|
46
45
|
- [License](#license)
|
|
@@ -60,34 +59,14 @@ npm install three
|
|
|
60
59
|
|
|
61
60
|
## Architecture
|
|
62
61
|
|
|
63
|
-
three-text has a framework-agnostic core that processes fonts and generates geometry data. Lightweight adapters convert this data to framework-specific formats
|
|
64
|
-
|
|
65
|
-
- **`three-text`** - Three.js adapter (default export, returns `BufferGeometry`)
|
|
66
|
-
- **`three-text/mesh`** - Same as above (explicit alias)
|
|
67
|
-
- **`three-text/mesh/react`** - React Three Fiber component for extruded mesh text
|
|
68
|
-
- **`three-text/three`** - Deprecated, use `three-text/mesh`
|
|
69
|
-
- **`three-text/three/react`** - Deprecated, use `three-text/mesh/react`
|
|
70
|
-
- **`three-text/mesh/webgl`** - WebGL mesh buffer utility
|
|
71
|
-
- **`three-text/mesh/webgpu`** - WebGPU mesh buffer utility
|
|
72
|
-
- **`three-text/mesh/p5`** - p5.js adapter
|
|
73
|
-
- **`three-text/core`** - Framework-agnostic core (returns raw arrays)
|
|
74
|
-
- **`three-text/vector`** - Vector rendering (Slug per-fragment curve evaluation), `Text.create()` returns a `THREE.Group`
|
|
75
|
-
- **`three-text/vector/react`** - React Three Fiber component for vector text
|
|
76
|
-
- **`three-text/vector/core`** - Framework-agnostic vector core (returns raw `SlugGPUData`, no Three.js dependency)
|
|
77
|
-
- **`three-text/vector/webgl`** - Raw WebGL2 vector renderer (no Three.js dependency)
|
|
78
|
-
- **`three-text/vector/webgpu`** - Raw WebGPU vector renderer (no Three.js dependency)
|
|
79
|
-
- **`three-text/webgl`** - Deprecated, use `three-text/mesh/webgl`
|
|
80
|
-
- **`three-text/webgpu`** - Deprecated, use `three-text/mesh/webgpu`
|
|
81
|
-
- **`three-text/p5`** - Deprecated, use `three-text/mesh/p5`
|
|
82
|
-
|
|
83
|
-
Most users will just `import { Text } from 'three-text'` for Three.js projects with mesh, or `import { Text } from 'three-text/vector'` for vector text
|
|
62
|
+
three-text has a framework-agnostic core that processes fonts and generates geometry data. Lightweight adapters convert this data to framework-specific formats. Most users will just `import { Text } from 'three-text'` for Three.js projects with mesh, or `import { Text } from 'three-text/vector'` for vector text. See [Entry points](#entry-points) for the full list of adapters
|
|
84
63
|
|
|
85
64
|
### Mesh vs vector
|
|
86
65
|
|
|
87
66
|
The library offers two rendering modes that share the same core (HarfBuzz shaping, Knuth-Plass justification, glyph caching):
|
|
88
67
|
|
|
89
68
|
- **Mesh** (`three-text` (default) / `three-text/mesh`): triangulated geometry you can extrude, light, and shade. Use for 3D text, text in a scene graph, or anywhere you need depth
|
|
90
|
-
- **Vector** (`three-text/vector`): resolution-independent rendering on the GPU via per-fragment curve evaluation.
|
|
69
|
+
- **Vector** (`three-text/vector`): resolution-independent rendering on the GPU via per-fragment curve evaluation. Use for flat text that needs to stay sharp at arbitrary zoom
|
|
91
70
|
|
|
92
71
|
Both can be used in the same project from separate entry points
|
|
93
72
|
|
|
@@ -483,6 +462,8 @@ Existing solutions take different approaches:
|
|
|
483
462
|
|
|
484
463
|
three-text produces actual geometry from font files, sharper at close distances than bitmap approaches, with control over typesetting and paragraph justification via TeX-based parameters
|
|
485
464
|
|
|
465
|
+
Under the hood, three-text relies on a core of [harfbuzzjs](https://github.com/harfbuzz/harfbuzzjs) (based on [HarfBuzz](https://github.com/harfbuzz/harfbuzz) by Behdad Esfahbod et al) for text shaping, [Knuth-Plass](http://www.eprg.org/G53DOC/pdfs/knuth-plass-breaking.pdf) line breaking (with [SILE](https://github.com/sile-typesetter/sile/blob/master/core/break.lua) and LuaTex being the closest modern references), [Liang](https://tug.org/docs/liang/liang-thesis.pdf) hyphenation and the [TeX hyphenation patterns](https://github.com/hyphenation/tex-hyphen), and [woff-lib](https://github.com/countertype/woff-lib) for optional WOFF2 support. The mesh text pipeline uses [libtess-ts](https://github.com/countertype/libtess-ts) (a port of the [GLU tessellator](https://www.songho.ca/opengl/gl_tessellation.html) by Eric Veach) for removing overlaps and triangulation, adaptive curve polygonization from Maxim Shemanarev's [Anti-Grain Geometry](https://web.archive.org/web/20060128212843/http://www.antigrain.com/research/adaptive_bezier/index.html), and [Visvalingam-Whyatt](https://hull-repository.worktribe.com/preview/376364/000870493786962263.pdf) [line simplification](https://bost.ocks.org/mike/simplify/). The vector pipeline uses [Slug](https://github.com/EricLengyel/Slug) by Eric Lengyel for resolution-independent curve rendering
|
|
466
|
+
|
|
486
467
|
### Why Slug
|
|
487
468
|
|
|
488
469
|
The vector path uses the Slug algorithm by Eric Lengyel. Each glyph is a single quad; the fragment shader evaluates curve coverage analytically to compute a winding number. Because it operates on winding rather than geometry, it naturally handles the self-intersecting contours that variable fonts produce when axes like weight push outlines into each other
|
|
@@ -1256,6 +1237,22 @@ git submodule update --init --recursive
|
|
|
1256
1237
|
|
|
1257
1238
|
The script then processes the TeX hyphenation data into optimized trie structures. The process is slow for the complete set of languages (~1 minute on an M2 Max), so using `--languages` for development is recommended
|
|
1258
1239
|
|
|
1240
|
+
## Entry points
|
|
1241
|
+
|
|
1242
|
+
- **`three-text`** - Three.js adapter (default export, returns mesh `BufferGeometry`)
|
|
1243
|
+
- **`three-text/mesh`** - Same as above (explicit alias)
|
|
1244
|
+
- **`three-text/mesh/react`** - React Three Fiber component for extruded mesh text
|
|
1245
|
+
- **`three-text/three/react`** - Deprecated, use `three-text/mesh/react`
|
|
1246
|
+
- **`three-text/mesh/webgl`** - WebGL mesh buffer utility
|
|
1247
|
+
- **`three-text/mesh/webgpu`** - WebGPU mesh buffer utility
|
|
1248
|
+
- **`three-text/mesh/p5`** - p5.js adapter
|
|
1249
|
+
- **`three-text/core`** - Framework-agnostic core (returns raw arrays)
|
|
1250
|
+
- **`three-text/vector`** - Vector rendering (Slug per-fragment curve evaluation), `Text.create()` returns a `THREE.Group`
|
|
1251
|
+
- **`three-text/vector/react`** - React Three Fiber component for vector text
|
|
1252
|
+
- **`three-text/vector/core`** - Framework-agnostic vector core (returns raw `SlugGPUData`, no Three.js dependency)
|
|
1253
|
+
- **`three-text/vector/webgl`** - Raw WebGL2 vector renderer (no Three.js dependency)
|
|
1254
|
+
- **`three-text/vector/webgpu`** - Raw WebGPU vector renderer (no Three.js dependency)
|
|
1255
|
+
|
|
1259
1256
|
## Build outputs
|
|
1260
1257
|
|
|
1261
1258
|
The build generates multiple module formats for core and all adapters:
|
|
@@ -1289,4 +1286,4 @@ The build generates multiple module formats for core and all adapters:
|
|
|
1289
1286
|
|
|
1290
1287
|
`three-text` was written by Jeremy Tribby ([@jpt](https://github.com/jpt)) and is licensed under the MIT License. See the [LICENSE](LICENSE) file for details
|
|
1291
1288
|
|
|
1292
|
-
This software includes code from third-party libraries under compatible permissive licenses. For full license details, see the [LICENSE_THIRD_PARTY](LICENSE_THIRD_PARTY) file
|
|
1289
|
+
This software includes code from third-party libraries under compatible permissive licenses. For full license details, see the [LICENSE_THIRD_PARTY](LICENSE_THIRD_PARTY) file
|
package/dist/index.cjs
CHANGED
package/dist/index.js
CHANGED
package/dist/index.min.cjs
CHANGED
package/dist/index.min.js
CHANGED
package/dist/index.umd.js
CHANGED
package/dist/index.umd.min.js
CHANGED
|
@@ -258,7 +258,6 @@ class GlyphOutlineCollector {
|
|
|
258
258
|
// GPU-ready packed textures + vertex attribute buffers
|
|
259
259
|
const TEX_WIDTH = 4096;
|
|
260
260
|
const LOG_TEX_WIDTH = 12;
|
|
261
|
-
// Float/Uint32 reinterpretation helpers
|
|
262
261
|
const _f32 = new Float32Array(1);
|
|
263
262
|
const _u32 = new Uint32Array(_f32.buffer);
|
|
264
263
|
function uintAsFloat(u) {
|
|
@@ -270,9 +269,7 @@ function uintAsFloat(u) {
|
|
|
270
269
|
function packSlugData(shapes, options) {
|
|
271
270
|
const bandCount = options?.bandCount ?? 16;
|
|
272
271
|
const evenOdd = options?.evenOdd ?? false;
|
|
273
|
-
// Pack all curves into curveTexture
|
|
274
272
|
const allCurves = [];
|
|
275
|
-
// Estimate max texels needed
|
|
276
273
|
let totalCurves = 0;
|
|
277
274
|
for (const shape of shapes) {
|
|
278
275
|
totalCurves += shape.curves.length;
|
|
@@ -285,13 +282,12 @@ function packSlugData(shapes, options) {
|
|
|
285
282
|
const entries = [];
|
|
286
283
|
const [ox, oy] = shape.bounds;
|
|
287
284
|
for (const curve of shape.curves) {
|
|
288
|
-
// Don't let a curve span across row boundary (needs 2 consecutive texels)
|
|
289
285
|
if (curveX >= TEX_WIDTH - 1) {
|
|
290
286
|
curveX = 0;
|
|
291
287
|
curveY++;
|
|
292
288
|
}
|
|
293
|
-
//
|
|
294
|
-
//
|
|
289
|
+
// Glyph-local space (relative to bounds min) keeps renderCoord
|
|
290
|
+
// subtraction near zero, preserving float32 precision in the solver
|
|
295
291
|
const lp1x = curve.p1[0] - ox, lp1y = curve.p1[1] - oy;
|
|
296
292
|
const lp2x = curve.p2[0] - ox, lp2y = curve.p2[1] - oy;
|
|
297
293
|
const lp3x = curve.p3[0] - ox, lp3y = curve.p3[1] - oy;
|
|
@@ -320,12 +316,10 @@ function packSlugData(shapes, options) {
|
|
|
320
316
|
allCurves.push(entries);
|
|
321
317
|
}
|
|
322
318
|
const actualCurveTexHeight = curveY + 1;
|
|
323
|
-
//
|
|
324
|
-
//
|
|
325
|
-
// [
|
|
326
|
-
// [hBandMax+
|
|
327
|
-
// [hBandMax+vBandMax+2 .. ] : curve index lists
|
|
328
|
-
// First pass: compute total band texels needed
|
|
319
|
+
// Band texture layout per shape (relative to glyphLoc):
|
|
320
|
+
// [0 .. hBandMax] h-band headers
|
|
321
|
+
// [hBandMax+1 .. hBandMax+1+vBandMax] v-band headers
|
|
322
|
+
// [hBandMax+vBandMax+2 .. ] curve index lists
|
|
329
323
|
const shapeBandData = [];
|
|
330
324
|
let totalBandTexels = 0;
|
|
331
325
|
for (let si = 0; si < shapes.length; si++) {
|
|
@@ -345,21 +339,19 @@ function packSlugData(shapes, options) {
|
|
|
345
339
|
const vBandCount = Math.min(bandCount, 255);
|
|
346
340
|
const bandMaxY = hBandCount - 1;
|
|
347
341
|
const bandMaxX = vBandCount - 1;
|
|
348
|
-
//
|
|
342
|
+
// Horizontal bands (partition y-axis)
|
|
349
343
|
const hBands = [];
|
|
350
344
|
const hLists = [];
|
|
351
345
|
const bandH = h / hBandCount;
|
|
352
346
|
for (let bi = 0; bi < hBandCount; bi++) {
|
|
353
347
|
const bandMinY = bi * bandH;
|
|
354
348
|
const bandMaxYCoord = bandMinY + bandH;
|
|
355
|
-
// Collect curves whose y-range overlaps this band
|
|
356
349
|
const list = [];
|
|
357
350
|
for (const c of curves) {
|
|
358
351
|
if (c.maxY >= bandMinY && c.minY <= bandMaxYCoord) {
|
|
359
352
|
list.push({ curve: c, sortKey: c.maxX });
|
|
360
353
|
}
|
|
361
354
|
}
|
|
362
|
-
// Sort by descending max-x for early exit
|
|
363
355
|
list.sort((a, b) => b.sortKey - a.sortKey);
|
|
364
356
|
const flatList = [];
|
|
365
357
|
for (const item of list) {
|
|
@@ -368,7 +360,7 @@ function packSlugData(shapes, options) {
|
|
|
368
360
|
hBands.push({ curveCount: list.length, listOffset: 0 });
|
|
369
361
|
hLists.push(flatList);
|
|
370
362
|
}
|
|
371
|
-
//
|
|
363
|
+
// Vertical bands (partition x-axis)
|
|
372
364
|
const vBands = [];
|
|
373
365
|
const vLists = [];
|
|
374
366
|
const bandW = w / vBandCount;
|
|
@@ -381,7 +373,6 @@ function packSlugData(shapes, options) {
|
|
|
381
373
|
list.push({ curve: c, sortKey: c.maxY });
|
|
382
374
|
}
|
|
383
375
|
}
|
|
384
|
-
// Sort by descending max-y for early exit
|
|
385
376
|
list.sort((a, b) => b.sortKey - a.sortKey);
|
|
386
377
|
const flatList = [];
|
|
387
378
|
for (const item of list) {
|
|
@@ -390,7 +381,6 @@ function packSlugData(shapes, options) {
|
|
|
390
381
|
vBands.push({ curveCount: list.length, listOffset: 0 });
|
|
391
382
|
vLists.push(flatList);
|
|
392
383
|
}
|
|
393
|
-
// Total texels for this shape: band headers + curve lists
|
|
394
384
|
const headerTexels = hBandCount + vBandCount;
|
|
395
385
|
let listTexels = 0;
|
|
396
386
|
for (const l of hLists)
|
|
@@ -404,10 +394,8 @@ function packSlugData(shapes, options) {
|
|
|
404
394
|
});
|
|
405
395
|
totalBandTexels += total;
|
|
406
396
|
}
|
|
407
|
-
// Allocate bandTexture (extra rows for row-alignment padding of curve lists)
|
|
408
397
|
const bandTexHeight = Math.max(1, Math.ceil(totalBandTexels / TEX_WIDTH) + shapes.length * 2);
|
|
409
398
|
const bandData = new Uint32Array(TEX_WIDTH * bandTexHeight * 4);
|
|
410
|
-
// Pack band data per shape
|
|
411
399
|
let bandX = 0;
|
|
412
400
|
let bandY = 0;
|
|
413
401
|
const glyphLocs = [];
|
|
@@ -417,11 +405,8 @@ function packSlugData(shapes, options) {
|
|
|
417
405
|
glyphLocs.push({ x: 0, y: 0 });
|
|
418
406
|
continue;
|
|
419
407
|
}
|
|
420
|
-
//
|
|
421
|
-
//
|
|
422
|
-
// But the initial band header reads don't use CalcBandLoc, so glyphLoc.x + bandMax.y + 1 + bandMaxX
|
|
423
|
-
// must be reachable. CalcBandLoc handles wrapping for curve lists.
|
|
424
|
-
// To be safe, start each glyph at the beginning of a row if remaining space is tight.
|
|
408
|
+
// Band headers are read without CalcBandLoc wrapping, so all
|
|
409
|
+
// headers for a glyph must fit within a single texture row
|
|
425
410
|
const minContiguous = sd.hBands.length + sd.vBands.length;
|
|
426
411
|
if (bandX + minContiguous > TEX_WIDTH) {
|
|
427
412
|
bandX = 0;
|
|
@@ -430,11 +415,8 @@ function packSlugData(shapes, options) {
|
|
|
430
415
|
const glyphLocX = bandX;
|
|
431
416
|
const glyphLocY = bandY;
|
|
432
417
|
glyphLocs.push({ x: glyphLocX, y: glyphLocY });
|
|
433
|
-
// Curve lists start after all headers
|
|
434
418
|
let listStartOffset = sd.hBands.length + sd.vBands.length;
|
|
435
|
-
//
|
|
436
|
-
// with NO row wrapping. Each list must fit entirely within a single texture row.
|
|
437
|
-
// Pad the offset to the next row start when a list would cross a row boundary.
|
|
419
|
+
// Curve lists aren't row-wrapped by the shader, so pad to avoid crossing
|
|
438
420
|
const ensureListFits = (listLen) => {
|
|
439
421
|
if (listLen === 0)
|
|
440
422
|
return;
|
|
@@ -443,21 +425,18 @@ function packSlugData(shapes, options) {
|
|
|
443
425
|
listStartOffset += (TEX_WIDTH - startX);
|
|
444
426
|
}
|
|
445
427
|
};
|
|
446
|
-
// Assign list offsets for h-bands
|
|
447
428
|
for (let bi = 0; bi < sd.hBands.length; bi++) {
|
|
448
429
|
const listLen = sd.hLists[bi].length / 2;
|
|
449
430
|
ensureListFits(listLen);
|
|
450
431
|
sd.hBands[bi].listOffset = listStartOffset;
|
|
451
432
|
listStartOffset += listLen;
|
|
452
433
|
}
|
|
453
|
-
// Assign list offsets for v-bands
|
|
454
434
|
for (let bi = 0; bi < sd.vBands.length; bi++) {
|
|
455
435
|
const listLen = sd.vLists[bi].length / 2;
|
|
456
436
|
ensureListFits(listLen);
|
|
457
437
|
sd.vBands[bi].listOffset = listStartOffset;
|
|
458
438
|
listStartOffset += listLen;
|
|
459
439
|
}
|
|
460
|
-
// Write h-band headers
|
|
461
440
|
for (let bi = 0; bi < sd.hBands.length; bi++) {
|
|
462
441
|
const tx = glyphLocX + bi;
|
|
463
442
|
const ty = glyphLocY;
|
|
@@ -467,7 +446,6 @@ function packSlugData(shapes, options) {
|
|
|
467
446
|
bandData[idx + 2] = 0;
|
|
468
447
|
bandData[idx + 3] = 0;
|
|
469
448
|
}
|
|
470
|
-
// Write v-band headers (after h-bands)
|
|
471
449
|
const vBandStart = glyphLocX + sd.hBands.length;
|
|
472
450
|
for (let bi = 0; bi < sd.vBands.length; bi++) {
|
|
473
451
|
const tx = vBandStart + bi;
|
|
@@ -478,9 +456,7 @@ function packSlugData(shapes, options) {
|
|
|
478
456
|
bandData[idx + 2] = 0;
|
|
479
457
|
bandData[idx + 3] = 0;
|
|
480
458
|
}
|
|
481
|
-
// Write curve lists using CalcBandLoc-style wrapping
|
|
482
459
|
const texWidthMask = (1 << LOG_TEX_WIDTH) - 1;
|
|
483
|
-
// Write h-band curve lists
|
|
484
460
|
for (let bi = 0; bi < sd.hBands.length; bi++) {
|
|
485
461
|
const list = sd.hLists[bi];
|
|
486
462
|
const baseOffset = sd.hBands[bi].listOffset;
|
|
@@ -489,13 +465,12 @@ function packSlugData(shapes, options) {
|
|
|
489
465
|
const by = glyphLocY + (bx >> LOG_TEX_WIDTH);
|
|
490
466
|
bx &= texWidthMask;
|
|
491
467
|
const idx = (by * TEX_WIDTH + bx) * 4;
|
|
492
|
-
bandData[idx + 0] = list[ci];
|
|
493
|
-
bandData[idx + 1] = list[ci + 1];
|
|
468
|
+
bandData[idx + 0] = list[ci];
|
|
469
|
+
bandData[idx + 1] = list[ci + 1];
|
|
494
470
|
bandData[idx + 2] = 0;
|
|
495
471
|
bandData[idx + 3] = 0;
|
|
496
472
|
}
|
|
497
473
|
}
|
|
498
|
-
// Write v-band curve lists
|
|
499
474
|
for (let bi = 0; bi < sd.vBands.length; bi++) {
|
|
500
475
|
const list = sd.vLists[bi];
|
|
501
476
|
const baseOffset = sd.vBands[bi].listOffset;
|
|
@@ -510,24 +485,20 @@ function packSlugData(shapes, options) {
|
|
|
510
485
|
bandData[idx + 3] = 0;
|
|
511
486
|
}
|
|
512
487
|
}
|
|
513
|
-
// Advance band cursor past this shape's data
|
|
514
488
|
let endBx = glyphLocX + listStartOffset;
|
|
515
489
|
bandY = glyphLocY + (endBx >> LOG_TEX_WIDTH);
|
|
516
490
|
bandX = endBx & texWidthMask;
|
|
517
491
|
}
|
|
518
492
|
const actualBandTexHeight = bandY + 1;
|
|
519
|
-
// Build vertex attributes
|
|
520
|
-
// 5 attribs x 4 floats x 4 vertices per shape = 80 floats per shape
|
|
521
493
|
const FLOATS_PER_VERTEX = 20; // 5 attribs * 4 components
|
|
522
494
|
const VERTS_PER_SHAPE = 4;
|
|
523
495
|
const vertices = new Float32Array(shapes.length * VERTS_PER_SHAPE * FLOATS_PER_VERTEX);
|
|
524
496
|
const indices = new Uint16Array(shapes.length * 6);
|
|
525
|
-
// Corner normals (outward-pointing, un-normalized; SlugDilate normalizes)
|
|
526
497
|
const cornerNormals = [
|
|
527
|
-
[-1, -1],
|
|
528
|
-
[1, -1],
|
|
529
|
-
[1, 1],
|
|
530
|
-
[-1, 1],
|
|
498
|
+
[-1, -1],
|
|
499
|
+
[1, -1],
|
|
500
|
+
[1, 1],
|
|
501
|
+
[-1, 1],
|
|
531
502
|
];
|
|
532
503
|
for (let si = 0; si < shapes.length; si++) {
|
|
533
504
|
const shape = shapes[si];
|
|
@@ -536,28 +507,23 @@ function packSlugData(shapes, options) {
|
|
|
536
507
|
const [bMinX, bMinY, bMaxX, bMaxY] = shape.bounds;
|
|
537
508
|
const w = bMaxX - bMinX;
|
|
538
509
|
const h = bMaxY - bMinY;
|
|
539
|
-
// Corner positions in object-space
|
|
540
510
|
const corners = [
|
|
541
511
|
[bMinX, bMinY],
|
|
542
512
|
[bMaxX, bMinY],
|
|
543
513
|
[bMaxX, bMaxY],
|
|
544
514
|
[bMinX, bMaxY],
|
|
545
515
|
];
|
|
546
|
-
// Em-space sample coords in glyph-local space (origin at bounds min)
|
|
547
516
|
const emCorners = [
|
|
548
517
|
[0, 0],
|
|
549
518
|
[w, 0],
|
|
550
519
|
[w, h],
|
|
551
520
|
[0, h],
|
|
552
521
|
];
|
|
553
|
-
// Pack tex.z: glyph location in band texture
|
|
554
522
|
const texZ = uintAsFloat((glyph.x & 0xFFFF) | ((glyph.y & 0xFFFF) << 16));
|
|
555
|
-
// Pack tex.w: band max + flags
|
|
556
523
|
let texWBits = (sd.bandMaxX & 0xFF) | ((sd.bandMaxY & 0xFF) << 16);
|
|
557
524
|
if (evenOdd)
|
|
558
525
|
texWBits |= 0x10000000; // E flag at bit 28
|
|
559
526
|
const texW = uintAsFloat(texWBits);
|
|
560
|
-
// Band transform: scale to map glyph-local em-coords to band indices
|
|
561
527
|
const bandScaleX = w > 0 ? sd.vBands.length / w : 0;
|
|
562
528
|
const bandScaleY = h > 0 ? sd.hBands.length / h : 0;
|
|
563
529
|
for (let vi = 0; vi < 4; vi++) {
|
|
@@ -572,23 +538,22 @@ function packSlugData(shapes, options) {
|
|
|
572
538
|
vertices[base + 5] = emCorners[vi][1];
|
|
573
539
|
vertices[base + 6] = texZ;
|
|
574
540
|
vertices[base + 7] = texW;
|
|
575
|
-
// jac: identity
|
|
541
|
+
// jac: identity (em-space is a translation of object-space)
|
|
576
542
|
vertices[base + 8] = 1.0;
|
|
577
543
|
vertices[base + 9] = 0.0;
|
|
578
544
|
vertices[base + 10] = 0.0;
|
|
579
545
|
vertices[base + 11] = 1.0;
|
|
580
|
-
// bnd: band scale (offset
|
|
546
|
+
// bnd: band scale (offset zero in glyph-local space)
|
|
581
547
|
vertices[base + 12] = bandScaleX;
|
|
582
548
|
vertices[base + 13] = bandScaleY;
|
|
583
549
|
vertices[base + 14] = 0;
|
|
584
550
|
vertices[base + 15] = 0;
|
|
585
|
-
// col
|
|
551
|
+
// col
|
|
586
552
|
vertices[base + 16] = 1.0;
|
|
587
553
|
vertices[base + 17] = 1.0;
|
|
588
554
|
vertices[base + 18] = 1.0;
|
|
589
555
|
vertices[base + 19] = 1.0;
|
|
590
556
|
}
|
|
591
|
-
// Indices: two triangles per quad
|
|
592
557
|
const vBase = si * 4;
|
|
593
558
|
const iBase = si * 6;
|
|
594
559
|
indices[iBase + 0] = vBase + 0;
|
|
@@ -797,7 +762,6 @@ function computePlaneBounds(glyphInfos) {
|
|
|
797
762
|
}
|
|
798
763
|
return { min: { x: minX, y: minY, z: 0 }, max: { x: maxX, y: maxY, z: 0 } };
|
|
799
764
|
}
|
|
800
|
-
// Public API
|
|
801
765
|
function buildVectorResult(layoutHandle, ctx, options) {
|
|
802
766
|
const scale = layoutHandle.layoutData.pixelsPerFontUnit;
|
|
803
767
|
let cachedQuery = null;
|
|
@@ -256,7 +256,6 @@ class GlyphOutlineCollector {
|
|
|
256
256
|
// GPU-ready packed textures + vertex attribute buffers
|
|
257
257
|
const TEX_WIDTH = 4096;
|
|
258
258
|
const LOG_TEX_WIDTH = 12;
|
|
259
|
-
// Float/Uint32 reinterpretation helpers
|
|
260
259
|
const _f32 = new Float32Array(1);
|
|
261
260
|
const _u32 = new Uint32Array(_f32.buffer);
|
|
262
261
|
function uintAsFloat(u) {
|
|
@@ -268,9 +267,7 @@ function uintAsFloat(u) {
|
|
|
268
267
|
function packSlugData(shapes, options) {
|
|
269
268
|
const bandCount = options?.bandCount ?? 16;
|
|
270
269
|
const evenOdd = options?.evenOdd ?? false;
|
|
271
|
-
// Pack all curves into curveTexture
|
|
272
270
|
const allCurves = [];
|
|
273
|
-
// Estimate max texels needed
|
|
274
271
|
let totalCurves = 0;
|
|
275
272
|
for (const shape of shapes) {
|
|
276
273
|
totalCurves += shape.curves.length;
|
|
@@ -283,13 +280,12 @@ function packSlugData(shapes, options) {
|
|
|
283
280
|
const entries = [];
|
|
284
281
|
const [ox, oy] = shape.bounds;
|
|
285
282
|
for (const curve of shape.curves) {
|
|
286
|
-
// Don't let a curve span across row boundary (needs 2 consecutive texels)
|
|
287
283
|
if (curveX >= TEX_WIDTH - 1) {
|
|
288
284
|
curveX = 0;
|
|
289
285
|
curveY++;
|
|
290
286
|
}
|
|
291
|
-
//
|
|
292
|
-
//
|
|
287
|
+
// Glyph-local space (relative to bounds min) keeps renderCoord
|
|
288
|
+
// subtraction near zero, preserving float32 precision in the solver
|
|
293
289
|
const lp1x = curve.p1[0] - ox, lp1y = curve.p1[1] - oy;
|
|
294
290
|
const lp2x = curve.p2[0] - ox, lp2y = curve.p2[1] - oy;
|
|
295
291
|
const lp3x = curve.p3[0] - ox, lp3y = curve.p3[1] - oy;
|
|
@@ -318,12 +314,10 @@ function packSlugData(shapes, options) {
|
|
|
318
314
|
allCurves.push(entries);
|
|
319
315
|
}
|
|
320
316
|
const actualCurveTexHeight = curveY + 1;
|
|
321
|
-
//
|
|
322
|
-
//
|
|
323
|
-
// [
|
|
324
|
-
// [hBandMax+
|
|
325
|
-
// [hBandMax+vBandMax+2 .. ] : curve index lists
|
|
326
|
-
// First pass: compute total band texels needed
|
|
317
|
+
// Band texture layout per shape (relative to glyphLoc):
|
|
318
|
+
// [0 .. hBandMax] h-band headers
|
|
319
|
+
// [hBandMax+1 .. hBandMax+1+vBandMax] v-band headers
|
|
320
|
+
// [hBandMax+vBandMax+2 .. ] curve index lists
|
|
327
321
|
const shapeBandData = [];
|
|
328
322
|
let totalBandTexels = 0;
|
|
329
323
|
for (let si = 0; si < shapes.length; si++) {
|
|
@@ -343,21 +337,19 @@ function packSlugData(shapes, options) {
|
|
|
343
337
|
const vBandCount = Math.min(bandCount, 255);
|
|
344
338
|
const bandMaxY = hBandCount - 1;
|
|
345
339
|
const bandMaxX = vBandCount - 1;
|
|
346
|
-
//
|
|
340
|
+
// Horizontal bands (partition y-axis)
|
|
347
341
|
const hBands = [];
|
|
348
342
|
const hLists = [];
|
|
349
343
|
const bandH = h / hBandCount;
|
|
350
344
|
for (let bi = 0; bi < hBandCount; bi++) {
|
|
351
345
|
const bandMinY = bi * bandH;
|
|
352
346
|
const bandMaxYCoord = bandMinY + bandH;
|
|
353
|
-
// Collect curves whose y-range overlaps this band
|
|
354
347
|
const list = [];
|
|
355
348
|
for (const c of curves) {
|
|
356
349
|
if (c.maxY >= bandMinY && c.minY <= bandMaxYCoord) {
|
|
357
350
|
list.push({ curve: c, sortKey: c.maxX });
|
|
358
351
|
}
|
|
359
352
|
}
|
|
360
|
-
// Sort by descending max-x for early exit
|
|
361
353
|
list.sort((a, b) => b.sortKey - a.sortKey);
|
|
362
354
|
const flatList = [];
|
|
363
355
|
for (const item of list) {
|
|
@@ -366,7 +358,7 @@ function packSlugData(shapes, options) {
|
|
|
366
358
|
hBands.push({ curveCount: list.length, listOffset: 0 });
|
|
367
359
|
hLists.push(flatList);
|
|
368
360
|
}
|
|
369
|
-
//
|
|
361
|
+
// Vertical bands (partition x-axis)
|
|
370
362
|
const vBands = [];
|
|
371
363
|
const vLists = [];
|
|
372
364
|
const bandW = w / vBandCount;
|
|
@@ -379,7 +371,6 @@ function packSlugData(shapes, options) {
|
|
|
379
371
|
list.push({ curve: c, sortKey: c.maxY });
|
|
380
372
|
}
|
|
381
373
|
}
|
|
382
|
-
// Sort by descending max-y for early exit
|
|
383
374
|
list.sort((a, b) => b.sortKey - a.sortKey);
|
|
384
375
|
const flatList = [];
|
|
385
376
|
for (const item of list) {
|
|
@@ -388,7 +379,6 @@ function packSlugData(shapes, options) {
|
|
|
388
379
|
vBands.push({ curveCount: list.length, listOffset: 0 });
|
|
389
380
|
vLists.push(flatList);
|
|
390
381
|
}
|
|
391
|
-
// Total texels for this shape: band headers + curve lists
|
|
392
382
|
const headerTexels = hBandCount + vBandCount;
|
|
393
383
|
let listTexels = 0;
|
|
394
384
|
for (const l of hLists)
|
|
@@ -402,10 +392,8 @@ function packSlugData(shapes, options) {
|
|
|
402
392
|
});
|
|
403
393
|
totalBandTexels += total;
|
|
404
394
|
}
|
|
405
|
-
// Allocate bandTexture (extra rows for row-alignment padding of curve lists)
|
|
406
395
|
const bandTexHeight = Math.max(1, Math.ceil(totalBandTexels / TEX_WIDTH) + shapes.length * 2);
|
|
407
396
|
const bandData = new Uint32Array(TEX_WIDTH * bandTexHeight * 4);
|
|
408
|
-
// Pack band data per shape
|
|
409
397
|
let bandX = 0;
|
|
410
398
|
let bandY = 0;
|
|
411
399
|
const glyphLocs = [];
|
|
@@ -415,11 +403,8 @@ function packSlugData(shapes, options) {
|
|
|
415
403
|
glyphLocs.push({ x: 0, y: 0 });
|
|
416
404
|
continue;
|
|
417
405
|
}
|
|
418
|
-
//
|
|
419
|
-
//
|
|
420
|
-
// But the initial band header reads don't use CalcBandLoc, so glyphLoc.x + bandMax.y + 1 + bandMaxX
|
|
421
|
-
// must be reachable. CalcBandLoc handles wrapping for curve lists.
|
|
422
|
-
// To be safe, start each glyph at the beginning of a row if remaining space is tight.
|
|
406
|
+
// Band headers are read without CalcBandLoc wrapping, so all
|
|
407
|
+
// headers for a glyph must fit within a single texture row
|
|
423
408
|
const minContiguous = sd.hBands.length + sd.vBands.length;
|
|
424
409
|
if (bandX + minContiguous > TEX_WIDTH) {
|
|
425
410
|
bandX = 0;
|
|
@@ -428,11 +413,8 @@ function packSlugData(shapes, options) {
|
|
|
428
413
|
const glyphLocX = bandX;
|
|
429
414
|
const glyphLocY = bandY;
|
|
430
415
|
glyphLocs.push({ x: glyphLocX, y: glyphLocY });
|
|
431
|
-
// Curve lists start after all headers
|
|
432
416
|
let listStartOffset = sd.hBands.length + sd.vBands.length;
|
|
433
|
-
//
|
|
434
|
-
// with NO row wrapping. Each list must fit entirely within a single texture row.
|
|
435
|
-
// Pad the offset to the next row start when a list would cross a row boundary.
|
|
417
|
+
// Curve lists aren't row-wrapped by the shader, so pad to avoid crossing
|
|
436
418
|
const ensureListFits = (listLen) => {
|
|
437
419
|
if (listLen === 0)
|
|
438
420
|
return;
|
|
@@ -441,21 +423,18 @@ function packSlugData(shapes, options) {
|
|
|
441
423
|
listStartOffset += (TEX_WIDTH - startX);
|
|
442
424
|
}
|
|
443
425
|
};
|
|
444
|
-
// Assign list offsets for h-bands
|
|
445
426
|
for (let bi = 0; bi < sd.hBands.length; bi++) {
|
|
446
427
|
const listLen = sd.hLists[bi].length / 2;
|
|
447
428
|
ensureListFits(listLen);
|
|
448
429
|
sd.hBands[bi].listOffset = listStartOffset;
|
|
449
430
|
listStartOffset += listLen;
|
|
450
431
|
}
|
|
451
|
-
// Assign list offsets for v-bands
|
|
452
432
|
for (let bi = 0; bi < sd.vBands.length; bi++) {
|
|
453
433
|
const listLen = sd.vLists[bi].length / 2;
|
|
454
434
|
ensureListFits(listLen);
|
|
455
435
|
sd.vBands[bi].listOffset = listStartOffset;
|
|
456
436
|
listStartOffset += listLen;
|
|
457
437
|
}
|
|
458
|
-
// Write h-band headers
|
|
459
438
|
for (let bi = 0; bi < sd.hBands.length; bi++) {
|
|
460
439
|
const tx = glyphLocX + bi;
|
|
461
440
|
const ty = glyphLocY;
|
|
@@ -465,7 +444,6 @@ function packSlugData(shapes, options) {
|
|
|
465
444
|
bandData[idx + 2] = 0;
|
|
466
445
|
bandData[idx + 3] = 0;
|
|
467
446
|
}
|
|
468
|
-
// Write v-band headers (after h-bands)
|
|
469
447
|
const vBandStart = glyphLocX + sd.hBands.length;
|
|
470
448
|
for (let bi = 0; bi < sd.vBands.length; bi++) {
|
|
471
449
|
const tx = vBandStart + bi;
|
|
@@ -476,9 +454,7 @@ function packSlugData(shapes, options) {
|
|
|
476
454
|
bandData[idx + 2] = 0;
|
|
477
455
|
bandData[idx + 3] = 0;
|
|
478
456
|
}
|
|
479
|
-
// Write curve lists using CalcBandLoc-style wrapping
|
|
480
457
|
const texWidthMask = (1 << LOG_TEX_WIDTH) - 1;
|
|
481
|
-
// Write h-band curve lists
|
|
482
458
|
for (let bi = 0; bi < sd.hBands.length; bi++) {
|
|
483
459
|
const list = sd.hLists[bi];
|
|
484
460
|
const baseOffset = sd.hBands[bi].listOffset;
|
|
@@ -487,13 +463,12 @@ function packSlugData(shapes, options) {
|
|
|
487
463
|
const by = glyphLocY + (bx >> LOG_TEX_WIDTH);
|
|
488
464
|
bx &= texWidthMask;
|
|
489
465
|
const idx = (by * TEX_WIDTH + bx) * 4;
|
|
490
|
-
bandData[idx + 0] = list[ci];
|
|
491
|
-
bandData[idx + 1] = list[ci + 1];
|
|
466
|
+
bandData[idx + 0] = list[ci];
|
|
467
|
+
bandData[idx + 1] = list[ci + 1];
|
|
492
468
|
bandData[idx + 2] = 0;
|
|
493
469
|
bandData[idx + 3] = 0;
|
|
494
470
|
}
|
|
495
471
|
}
|
|
496
|
-
// Write v-band curve lists
|
|
497
472
|
for (let bi = 0; bi < sd.vBands.length; bi++) {
|
|
498
473
|
const list = sd.vLists[bi];
|
|
499
474
|
const baseOffset = sd.vBands[bi].listOffset;
|
|
@@ -508,24 +483,20 @@ function packSlugData(shapes, options) {
|
|
|
508
483
|
bandData[idx + 3] = 0;
|
|
509
484
|
}
|
|
510
485
|
}
|
|
511
|
-
// Advance band cursor past this shape's data
|
|
512
486
|
let endBx = glyphLocX + listStartOffset;
|
|
513
487
|
bandY = glyphLocY + (endBx >> LOG_TEX_WIDTH);
|
|
514
488
|
bandX = endBx & texWidthMask;
|
|
515
489
|
}
|
|
516
490
|
const actualBandTexHeight = bandY + 1;
|
|
517
|
-
// Build vertex attributes
|
|
518
|
-
// 5 attribs x 4 floats x 4 vertices per shape = 80 floats per shape
|
|
519
491
|
const FLOATS_PER_VERTEX = 20; // 5 attribs * 4 components
|
|
520
492
|
const VERTS_PER_SHAPE = 4;
|
|
521
493
|
const vertices = new Float32Array(shapes.length * VERTS_PER_SHAPE * FLOATS_PER_VERTEX);
|
|
522
494
|
const indices = new Uint16Array(shapes.length * 6);
|
|
523
|
-
// Corner normals (outward-pointing, un-normalized; SlugDilate normalizes)
|
|
524
495
|
const cornerNormals = [
|
|
525
|
-
[-1, -1],
|
|
526
|
-
[1, -1],
|
|
527
|
-
[1, 1],
|
|
528
|
-
[-1, 1],
|
|
496
|
+
[-1, -1],
|
|
497
|
+
[1, -1],
|
|
498
|
+
[1, 1],
|
|
499
|
+
[-1, 1],
|
|
529
500
|
];
|
|
530
501
|
for (let si = 0; si < shapes.length; si++) {
|
|
531
502
|
const shape = shapes[si];
|
|
@@ -534,28 +505,23 @@ function packSlugData(shapes, options) {
|
|
|
534
505
|
const [bMinX, bMinY, bMaxX, bMaxY] = shape.bounds;
|
|
535
506
|
const w = bMaxX - bMinX;
|
|
536
507
|
const h = bMaxY - bMinY;
|
|
537
|
-
// Corner positions in object-space
|
|
538
508
|
const corners = [
|
|
539
509
|
[bMinX, bMinY],
|
|
540
510
|
[bMaxX, bMinY],
|
|
541
511
|
[bMaxX, bMaxY],
|
|
542
512
|
[bMinX, bMaxY],
|
|
543
513
|
];
|
|
544
|
-
// Em-space sample coords in glyph-local space (origin at bounds min)
|
|
545
514
|
const emCorners = [
|
|
546
515
|
[0, 0],
|
|
547
516
|
[w, 0],
|
|
548
517
|
[w, h],
|
|
549
518
|
[0, h],
|
|
550
519
|
];
|
|
551
|
-
// Pack tex.z: glyph location in band texture
|
|
552
520
|
const texZ = uintAsFloat((glyph.x & 0xFFFF) | ((glyph.y & 0xFFFF) << 16));
|
|
553
|
-
// Pack tex.w: band max + flags
|
|
554
521
|
let texWBits = (sd.bandMaxX & 0xFF) | ((sd.bandMaxY & 0xFF) << 16);
|
|
555
522
|
if (evenOdd)
|
|
556
523
|
texWBits |= 0x10000000; // E flag at bit 28
|
|
557
524
|
const texW = uintAsFloat(texWBits);
|
|
558
|
-
// Band transform: scale to map glyph-local em-coords to band indices
|
|
559
525
|
const bandScaleX = w > 0 ? sd.vBands.length / w : 0;
|
|
560
526
|
const bandScaleY = h > 0 ? sd.hBands.length / h : 0;
|
|
561
527
|
for (let vi = 0; vi < 4; vi++) {
|
|
@@ -570,23 +536,22 @@ function packSlugData(shapes, options) {
|
|
|
570
536
|
vertices[base + 5] = emCorners[vi][1];
|
|
571
537
|
vertices[base + 6] = texZ;
|
|
572
538
|
vertices[base + 7] = texW;
|
|
573
|
-
// jac: identity
|
|
539
|
+
// jac: identity (em-space is a translation of object-space)
|
|
574
540
|
vertices[base + 8] = 1.0;
|
|
575
541
|
vertices[base + 9] = 0.0;
|
|
576
542
|
vertices[base + 10] = 0.0;
|
|
577
543
|
vertices[base + 11] = 1.0;
|
|
578
|
-
// bnd: band scale (offset
|
|
544
|
+
// bnd: band scale (offset zero in glyph-local space)
|
|
579
545
|
vertices[base + 12] = bandScaleX;
|
|
580
546
|
vertices[base + 13] = bandScaleY;
|
|
581
547
|
vertices[base + 14] = 0;
|
|
582
548
|
vertices[base + 15] = 0;
|
|
583
|
-
// col
|
|
549
|
+
// col
|
|
584
550
|
vertices[base + 16] = 1.0;
|
|
585
551
|
vertices[base + 17] = 1.0;
|
|
586
552
|
vertices[base + 18] = 1.0;
|
|
587
553
|
vertices[base + 19] = 1.0;
|
|
588
554
|
}
|
|
589
|
-
// Indices: two triangles per quad
|
|
590
555
|
const vBase = si * 4;
|
|
591
556
|
const iBase = si * 6;
|
|
592
557
|
indices[iBase + 0] = vBase + 0;
|
|
@@ -795,7 +760,6 @@ function computePlaneBounds(glyphInfos) {
|
|
|
795
760
|
}
|
|
796
761
|
return { min: { x: minX, y: minY, z: 0 }, max: { x: maxX, y: maxY, z: 0 } };
|
|
797
762
|
}
|
|
798
|
-
// Public API
|
|
799
763
|
function buildVectorResult(layoutHandle, ctx, options) {
|
|
800
764
|
const scale = layoutHandle.layoutData.pixelsPerFontUnit;
|
|
801
765
|
let cachedQuery = null;
|
package/dist/vector/index2.cjs
CHANGED
|
@@ -22,8 +22,6 @@ function _interopNamespaceDefault(e) {
|
|
|
22
22
|
|
|
23
23
|
var THREE__namespace = /*#__PURE__*/_interopNamespaceDefault(THREE);
|
|
24
24
|
|
|
25
|
-
// Restructures the tightly-packed vertex buffer from SlugPacker into
|
|
26
|
-
// separate per-attribute arrays for GPU attribute binding
|
|
27
25
|
const FLOATS_PER_VERT = 20;
|
|
28
26
|
function unpackSlugVertices(gpuData) {
|
|
29
27
|
const vertCount = gpuData.shapeCount * 4;
|
|
@@ -45,7 +43,6 @@ function unpackSlugVertices(gpuData) {
|
|
|
45
43
|
bandings[i * 4 + 1] = srcF[s + 13];
|
|
46
44
|
bandings[i * 4 + 2] = srcF[s + 14];
|
|
47
45
|
bandings[i * 4 + 3] = srcF[s + 15];
|
|
48
|
-
// Unpack glyph location and band metadata from bit-packed fields
|
|
49
46
|
const g0 = srcU[s + 6];
|
|
50
47
|
const g1 = srcU[s + 7];
|
|
51
48
|
glyphData[i * 4] = g0 & 0xFFFF;
|
|
@@ -57,7 +54,6 @@ function unpackSlugVertices(gpuData) {
|
|
|
57
54
|
colors[i * 4 + 2] = srcF[s + 18];
|
|
58
55
|
colors[i * 4 + 3] = srcF[s + 19];
|
|
59
56
|
}
|
|
60
|
-
// Per-glyph center: average of quad corner positions
|
|
61
57
|
const glyphCenters = new Float32Array(vertCount * 3);
|
|
62
58
|
const glyphIndices = new Float32Array(vertCount);
|
|
63
59
|
for (let g = 0; g < gpuData.shapeCount; g++) {
|
|
@@ -82,23 +78,14 @@ function unpackSlugVertices(gpuData) {
|
|
|
82
78
|
|
|
83
79
|
var fragGLSL = "#version 300 es\n// GLSL 300 es port of the Slug fragment shader\n// Eric Lengyel, MIT License, 2017\n\nprecision highp float;\nprecision highp int;\n\n#define kLogBandTextureWidth 12\n\nin vec4 v_color;\nin vec2 v_texcoord;\nflat in vec4 v_banding;\nflat in ivec4 v_glyph;\n\nuniform sampler2D curveTexture; // RGBA32F control points\nuniform highp usampler2D bandTexture; // RGBA32UI band data\n\nlayout(location = 0) out vec4 outColor;\n\nuint CalcRootCode(float y1, float y2, float y3) {\n uint i1 = floatBitsToUint(y1) >> 31u;\n uint i2 = floatBitsToUint(y2) >> 30u;\n uint i3 = floatBitsToUint(y3) >> 29u;\n\n uint shift = (i2 & 2u) | (i1 & ~2u);\n shift = (i3 & 4u) | (shift & ~4u);\n\n return (0x2E74u >> shift) & 0x0101u;\n}\n\nvec2 SolveHorizPoly(vec4 p12, vec2 p3) {\n vec2 a = p12.xy - p12.zw * 2.0 + p3;\n vec2 b = p12.xy - p12.zw;\n float ra = 1.0 / a.y;\n float rb = 0.5 / b.y;\n\n float d = sqrt(max(b.y * b.y - a.y * p12.y, 0.0));\n float t1 = (b.y - d) * ra;\n float t2 = (b.y + d) * ra;\n\n if (abs(a.y) < 1.0 / 65536.0) t1 = t2 = p12.y * rb;\n\n return vec2((a.x * t1 - b.x * 2.0) * t1 + p12.x, (a.x * t2 - b.x * 2.0) * t2 + p12.x);\n}\n\nvec2 SolveVertPoly(vec4 p12, vec2 p3) {\n vec2 a = p12.xy - p12.zw * 2.0 + p3;\n vec2 b = p12.xy - p12.zw;\n float ra = 1.0 / a.x;\n float rb = 0.5 / b.x;\n\n float d = sqrt(max(b.x * b.x - a.x * p12.x, 0.0));\n float t1 = (b.x - d) * ra;\n float t2 = (b.x + d) * ra;\n\n if (abs(a.x) < 1.0 / 65536.0) t1 = t2 = p12.x * rb;\n\n return vec2((a.y * t1 - b.y * 2.0) * t1 + p12.y, (a.y * t2 - b.y * 2.0) * t2 + p12.y);\n}\n\nivec2 CalcBandLoc(ivec2 glyphLoc, uint offset) {\n ivec2 bandLoc = ivec2(glyphLoc.x + int(offset), glyphLoc.y);\n bandLoc.y += bandLoc.x >> kLogBandTextureWidth;\n bandLoc.x &= (1 << kLogBandTextureWidth) - 1;\n return bandLoc;\n}\n\nfloat CalcCoverage(float xcov, float ycov, float xwgt, float ywgt, int flags) {\n float coverage = max(abs(xcov * xwgt + ycov * ywgt) / max(xwgt + ywgt, 1.0 / 65536.0), min(abs(xcov), abs(ycov)));\n\n#if defined(SLUG_EVENODD)\n if ((flags & 0x1000) == 0) {\n#endif\n coverage = clamp(coverage, 0.0, 1.0);\n#if defined(SLUG_EVENODD)\n } else {\n coverage = 1.0 - abs(1.0 - fract(coverage * 0.5) * 2.0);\n }\n#endif\n\n#if defined(SLUG_WEIGHT)\n coverage = sqrt(coverage);\n#endif\n\n return coverage;\n}\n\nfloat SlugRenderSingle(vec2 renderCoord, vec2 emsPerPixel, vec4 bandTransform, ivec4 glyphData) {\n int curveIndex;\n\n vec2 pixelsPerEm = 1.0 / emsPerPixel;\n\n ivec2 bandMax = glyphData.zw;\n bandMax.y &= 0x00FF;\n\n ivec2 bandIndex = clamp(ivec2(renderCoord * bandTransform.xy + bandTransform.zw), ivec2(0, 0), bandMax);\n ivec2 glyphLoc = glyphData.xy;\n\n float xcov = 0.0;\n float xwgt = 0.0;\n\n uvec2 hbandData = texelFetch(bandTexture, ivec2(glyphLoc.x + bandIndex.y, glyphLoc.y), 0).xy;\n ivec2 hbandLoc = CalcBandLoc(glyphLoc, hbandData.y);\n\n for (curveIndex = 0; curveIndex < int(hbandData.x); curveIndex++) {\n ivec2 curveLoc = ivec2(texelFetch(bandTexture, ivec2(hbandLoc.x + curveIndex, hbandLoc.y), 0).xy);\n\n vec4 p12 = texelFetch(curveTexture, curveLoc, 0) - vec4(renderCoord, renderCoord);\n vec2 p3 = texelFetch(curveTexture, ivec2(curveLoc.x + 1, curveLoc.y), 0).xy - renderCoord;\n\n if (max(max(p12.x, p12.z), p3.x) * pixelsPerEm.x < -0.5) break;\n\n uint code = CalcRootCode(p12.y, p12.w, p3.y);\n if (code != 0u) {\n vec2 r = SolveHorizPoly(p12, p3) * pixelsPerEm.x;\n\n if ((code & 1u) != 0u) {\n xcov += clamp(r.x + 0.5, 0.0, 1.0);\n xwgt = max(xwgt, clamp(1.0 - abs(r.x) * 2.0, 0.0, 1.0));\n }\n\n if (code > 1u) {\n xcov -= clamp(r.y + 0.5, 0.0, 1.0);\n xwgt = max(xwgt, clamp(1.0 - abs(r.y) * 2.0, 0.0, 1.0));\n }\n }\n }\n\n float ycov = 0.0;\n float ywgt = 0.0;\n\n uvec2 vbandData = texelFetch(bandTexture, ivec2(glyphLoc.x + bandMax.y + 1 + bandIndex.x, glyphLoc.y), 0).xy;\n ivec2 vbandLoc = CalcBandLoc(glyphLoc, vbandData.y);\n\n for (curveIndex = 0; curveIndex < int(vbandData.x); curveIndex++) {\n ivec2 curveLoc = ivec2(texelFetch(bandTexture, ivec2(vbandLoc.x + curveIndex, vbandLoc.y), 0).xy);\n vec4 p12 = texelFetch(curveTexture, curveLoc, 0) - vec4(renderCoord, renderCoord);\n vec2 p3 = texelFetch(curveTexture, ivec2(curveLoc.x + 1, curveLoc.y), 0).xy - renderCoord;\n\n if (max(max(p12.y, p12.w), p3.y) * pixelsPerEm.y < -0.5) break;\n\n uint code = CalcRootCode(p12.x, p12.z, p3.x);\n if (code != 0u) {\n vec2 r = SolveVertPoly(p12, p3) * pixelsPerEm.y;\n\n if ((code & 1u) != 0u) {\n ycov -= clamp(r.x + 0.5, 0.0, 1.0);\n ywgt = max(ywgt, clamp(1.0 - abs(r.x) * 2.0, 0.0, 1.0));\n }\n\n if (code > 1u) {\n ycov += clamp(r.y + 0.5, 0.0, 1.0);\n ywgt = max(ywgt, clamp(1.0 - abs(r.y) * 2.0, 0.0, 1.0));\n }\n }\n }\n\n return CalcCoverage(xcov, ycov, xwgt, ywgt, glyphData.w);\n}\n\nfloat SlugRender(vec2 renderCoord, vec4 bandTransform, ivec4 glyphData) {\n vec2 emsPerPixel = fwidth(renderCoord);\n\n#if defined(SLUG_ADAPTIVE_SUPERSAMPLE)\n // Per-pixel rotated RGSS-4. The base RGSS offsets are rotated by a\n // unique angle per fragment (interleaved gradient noise). This converts\n // structured aliasing shimmer into uncorrelated grain that the eye\n // naturally filters out, much closer to how hardware MSAA on many small\n // triangles behaves perceptually.\n float noise = fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715))));\n float angle = noise * 6.2831853;\n float ca = cos(angle), sa = sin(angle);\n\n // Base RGSS offsets rotated by per-pixel angle\n vec2 o0 = vec2(ca * -0.375 - sa * 0.125, sa * -0.375 + ca * 0.125) * emsPerPixel;\n vec2 o1 = vec2(ca * 0.125 - sa * 0.375, sa * 0.125 + ca * 0.375) * emsPerPixel;\n vec2 o2 = vec2(ca * 0.375 - sa * -0.125, sa * 0.375 + ca * -0.125) * emsPerPixel;\n vec2 o3 = vec2(ca * -0.125 - sa * -0.375, sa * -0.125 + ca * -0.375) * emsPerPixel;\n\n float coverage =\n SlugRenderSingle(renderCoord + o0, emsPerPixel, bandTransform, glyphData) +\n SlugRenderSingle(renderCoord + o1, emsPerPixel, bandTransform, glyphData) +\n SlugRenderSingle(renderCoord + o2, emsPerPixel, bandTransform, glyphData) +\n SlugRenderSingle(renderCoord + o3, emsPerPixel, bandTransform, glyphData);\n return coverage * 0.25;\n#else\n return SlugRenderSingle(renderCoord, emsPerPixel, bandTransform, glyphData);\n#endif\n}\n\nvoid main() {\n float coverage = SlugRender(v_texcoord, v_banding, v_glyph);\n outColor = v_color * coverage;\n}\n";
|
|
84
80
|
|
|
85
|
-
// Slug shader source re-exports
|
|
86
|
-
// The .glsl/.wgsl files are the single source of truth, imported as strings
|
|
87
|
-
// at build time via the glslPlugin in rollup.config.js
|
|
88
81
|
// @ts-ignore - resolved by rollup glslPlugin
|
|
89
82
|
const fragmentShaderGLSL300 = fragGLSL;
|
|
90
83
|
|
|
91
|
-
// Slug
|
|
92
|
-
//
|
|
93
|
-
// WebGPURenderer (via GLSL-to-WGSL transpilation)
|
|
84
|
+
// Slug adapter using Three.js RawShaderMaterial (GLSL)
|
|
85
|
+
// Works with any Three.js renderer (WebGL or WebGPU via transpilation)
|
|
94
86
|
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
// - Uses native Uint32 band texture (no float conversion)
|
|
98
|
-
// - Supports GLSL animation injection via animationDeclarations/animationBody
|
|
99
|
-
// - Same tradeoff: no vertex dilation (may cause sub-pixel edge clipping at extreme zoom)
|
|
100
|
-
//
|
|
101
|
-
// Requires peer dependency: three
|
|
87
|
+
// Supports GLSL animation injection and native Uint32 band textures
|
|
88
|
+
// No vertex dilation; may clip at sub-pixel edges under extreme zoom
|
|
102
89
|
// @ts-ignore - three is a peer dependency
|
|
103
90
|
// Three.js GLSL3 mode prepends #version 300 es, so strip it from the raw shader
|
|
104
91
|
const fragShader = fragmentShaderGLSL300.replace(/^#version\s+300\s+es\s*\n/, '');
|
|
@@ -149,13 +136,12 @@ function createSlugGLSLMesh(gpuData, options) {
|
|
|
149
136
|
geo.setAttribute('glyphCenter', new THREE__namespace.Float32BufferAttribute(attrs.glyphCenters, 3));
|
|
150
137
|
geo.setAttribute('glyphIndex', new THREE__namespace.Float32BufferAttribute(attrs.glyphIndices, 1));
|
|
151
138
|
geo.setIndex(new THREE__namespace.BufferAttribute(gpuData.indices, 1));
|
|
152
|
-
// Curve texture: RGBA32F
|
|
153
139
|
const curveTex = new THREE__namespace.DataTexture(gpuData.curveTexture.data, gpuData.curveTexture.width, gpuData.curveTexture.height, THREE__namespace.RGBAFormat, THREE__namespace.FloatType);
|
|
154
140
|
curveTex.minFilter = THREE__namespace.NearestFilter;
|
|
155
141
|
curveTex.magFilter = THREE__namespace.NearestFilter;
|
|
156
142
|
curveTex.generateMipmaps = false;
|
|
157
143
|
curveTex.needsUpdate = true;
|
|
158
|
-
//
|
|
144
|
+
// RGBA32UI (unlike TSL, GLSL supports integer textures natively)
|
|
159
145
|
const bandTex = new THREE__namespace.DataTexture(gpuData.bandTexture.data, gpuData.bandTexture.width, gpuData.bandTexture.height);
|
|
160
146
|
bandTex.format = THREE__namespace.RGBAIntegerFormat;
|
|
161
147
|
bandTex.type = THREE__namespace.UnsignedIntType;
|
package/dist/vector/index2.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { Text as Text$1 } from './core/index.js';
|
|
2
2
|
import * as THREE from 'three';
|
|
3
3
|
|
|
4
|
-
// Restructures the tightly-packed vertex buffer from SlugPacker into
|
|
5
|
-
// separate per-attribute arrays for GPU attribute binding
|
|
6
4
|
const FLOATS_PER_VERT = 20;
|
|
7
5
|
function unpackSlugVertices(gpuData) {
|
|
8
6
|
const vertCount = gpuData.shapeCount * 4;
|
|
@@ -24,7 +22,6 @@ function unpackSlugVertices(gpuData) {
|
|
|
24
22
|
bandings[i * 4 + 1] = srcF[s + 13];
|
|
25
23
|
bandings[i * 4 + 2] = srcF[s + 14];
|
|
26
24
|
bandings[i * 4 + 3] = srcF[s + 15];
|
|
27
|
-
// Unpack glyph location and band metadata from bit-packed fields
|
|
28
25
|
const g0 = srcU[s + 6];
|
|
29
26
|
const g1 = srcU[s + 7];
|
|
30
27
|
glyphData[i * 4] = g0 & 0xFFFF;
|
|
@@ -36,7 +33,6 @@ function unpackSlugVertices(gpuData) {
|
|
|
36
33
|
colors[i * 4 + 2] = srcF[s + 18];
|
|
37
34
|
colors[i * 4 + 3] = srcF[s + 19];
|
|
38
35
|
}
|
|
39
|
-
// Per-glyph center: average of quad corner positions
|
|
40
36
|
const glyphCenters = new Float32Array(vertCount * 3);
|
|
41
37
|
const glyphIndices = new Float32Array(vertCount);
|
|
42
38
|
for (let g = 0; g < gpuData.shapeCount; g++) {
|
|
@@ -61,23 +57,14 @@ function unpackSlugVertices(gpuData) {
|
|
|
61
57
|
|
|
62
58
|
var fragGLSL = "#version 300 es\n// GLSL 300 es port of the Slug fragment shader\n// Eric Lengyel, MIT License, 2017\n\nprecision highp float;\nprecision highp int;\n\n#define kLogBandTextureWidth 12\n\nin vec4 v_color;\nin vec2 v_texcoord;\nflat in vec4 v_banding;\nflat in ivec4 v_glyph;\n\nuniform sampler2D curveTexture; // RGBA32F control points\nuniform highp usampler2D bandTexture; // RGBA32UI band data\n\nlayout(location = 0) out vec4 outColor;\n\nuint CalcRootCode(float y1, float y2, float y3) {\n uint i1 = floatBitsToUint(y1) >> 31u;\n uint i2 = floatBitsToUint(y2) >> 30u;\n uint i3 = floatBitsToUint(y3) >> 29u;\n\n uint shift = (i2 & 2u) | (i1 & ~2u);\n shift = (i3 & 4u) | (shift & ~4u);\n\n return (0x2E74u >> shift) & 0x0101u;\n}\n\nvec2 SolveHorizPoly(vec4 p12, vec2 p3) {\n vec2 a = p12.xy - p12.zw * 2.0 + p3;\n vec2 b = p12.xy - p12.zw;\n float ra = 1.0 / a.y;\n float rb = 0.5 / b.y;\n\n float d = sqrt(max(b.y * b.y - a.y * p12.y, 0.0));\n float t1 = (b.y - d) * ra;\n float t2 = (b.y + d) * ra;\n\n if (abs(a.y) < 1.0 / 65536.0) t1 = t2 = p12.y * rb;\n\n return vec2((a.x * t1 - b.x * 2.0) * t1 + p12.x, (a.x * t2 - b.x * 2.0) * t2 + p12.x);\n}\n\nvec2 SolveVertPoly(vec4 p12, vec2 p3) {\n vec2 a = p12.xy - p12.zw * 2.0 + p3;\n vec2 b = p12.xy - p12.zw;\n float ra = 1.0 / a.x;\n float rb = 0.5 / b.x;\n\n float d = sqrt(max(b.x * b.x - a.x * p12.x, 0.0));\n float t1 = (b.x - d) * ra;\n float t2 = (b.x + d) * ra;\n\n if (abs(a.x) < 1.0 / 65536.0) t1 = t2 = p12.x * rb;\n\n return vec2((a.y * t1 - b.y * 2.0) * t1 + p12.y, (a.y * t2 - b.y * 2.0) * t2 + p12.y);\n}\n\nivec2 CalcBandLoc(ivec2 glyphLoc, uint offset) {\n ivec2 bandLoc = ivec2(glyphLoc.x + int(offset), glyphLoc.y);\n bandLoc.y += bandLoc.x >> kLogBandTextureWidth;\n bandLoc.x &= (1 << kLogBandTextureWidth) - 1;\n return bandLoc;\n}\n\nfloat CalcCoverage(float xcov, float ycov, float xwgt, float ywgt, int flags) {\n float coverage = max(abs(xcov * xwgt + ycov * ywgt) / max(xwgt + ywgt, 1.0 / 65536.0), min(abs(xcov), abs(ycov)));\n\n#if defined(SLUG_EVENODD)\n if ((flags & 0x1000) == 0) {\n#endif\n coverage = clamp(coverage, 0.0, 1.0);\n#if defined(SLUG_EVENODD)\n } else {\n coverage = 1.0 - abs(1.0 - fract(coverage * 0.5) * 2.0);\n }\n#endif\n\n#if defined(SLUG_WEIGHT)\n coverage = sqrt(coverage);\n#endif\n\n return coverage;\n}\n\nfloat SlugRenderSingle(vec2 renderCoord, vec2 emsPerPixel, vec4 bandTransform, ivec4 glyphData) {\n int curveIndex;\n\n vec2 pixelsPerEm = 1.0 / emsPerPixel;\n\n ivec2 bandMax = glyphData.zw;\n bandMax.y &= 0x00FF;\n\n ivec2 bandIndex = clamp(ivec2(renderCoord * bandTransform.xy + bandTransform.zw), ivec2(0, 0), bandMax);\n ivec2 glyphLoc = glyphData.xy;\n\n float xcov = 0.0;\n float xwgt = 0.0;\n\n uvec2 hbandData = texelFetch(bandTexture, ivec2(glyphLoc.x + bandIndex.y, glyphLoc.y), 0).xy;\n ivec2 hbandLoc = CalcBandLoc(glyphLoc, hbandData.y);\n\n for (curveIndex = 0; curveIndex < int(hbandData.x); curveIndex++) {\n ivec2 curveLoc = ivec2(texelFetch(bandTexture, ivec2(hbandLoc.x + curveIndex, hbandLoc.y), 0).xy);\n\n vec4 p12 = texelFetch(curveTexture, curveLoc, 0) - vec4(renderCoord, renderCoord);\n vec2 p3 = texelFetch(curveTexture, ivec2(curveLoc.x + 1, curveLoc.y), 0).xy - renderCoord;\n\n if (max(max(p12.x, p12.z), p3.x) * pixelsPerEm.x < -0.5) break;\n\n uint code = CalcRootCode(p12.y, p12.w, p3.y);\n if (code != 0u) {\n vec2 r = SolveHorizPoly(p12, p3) * pixelsPerEm.x;\n\n if ((code & 1u) != 0u) {\n xcov += clamp(r.x + 0.5, 0.0, 1.0);\n xwgt = max(xwgt, clamp(1.0 - abs(r.x) * 2.0, 0.0, 1.0));\n }\n\n if (code > 1u) {\n xcov -= clamp(r.y + 0.5, 0.0, 1.0);\n xwgt = max(xwgt, clamp(1.0 - abs(r.y) * 2.0, 0.0, 1.0));\n }\n }\n }\n\n float ycov = 0.0;\n float ywgt = 0.0;\n\n uvec2 vbandData = texelFetch(bandTexture, ivec2(glyphLoc.x + bandMax.y + 1 + bandIndex.x, glyphLoc.y), 0).xy;\n ivec2 vbandLoc = CalcBandLoc(glyphLoc, vbandData.y);\n\n for (curveIndex = 0; curveIndex < int(vbandData.x); curveIndex++) {\n ivec2 curveLoc = ivec2(texelFetch(bandTexture, ivec2(vbandLoc.x + curveIndex, vbandLoc.y), 0).xy);\n vec4 p12 = texelFetch(curveTexture, curveLoc, 0) - vec4(renderCoord, renderCoord);\n vec2 p3 = texelFetch(curveTexture, ivec2(curveLoc.x + 1, curveLoc.y), 0).xy - renderCoord;\n\n if (max(max(p12.y, p12.w), p3.y) * pixelsPerEm.y < -0.5) break;\n\n uint code = CalcRootCode(p12.x, p12.z, p3.x);\n if (code != 0u) {\n vec2 r = SolveVertPoly(p12, p3) * pixelsPerEm.y;\n\n if ((code & 1u) != 0u) {\n ycov -= clamp(r.x + 0.5, 0.0, 1.0);\n ywgt = max(ywgt, clamp(1.0 - abs(r.x) * 2.0, 0.0, 1.0));\n }\n\n if (code > 1u) {\n ycov += clamp(r.y + 0.5, 0.0, 1.0);\n ywgt = max(ywgt, clamp(1.0 - abs(r.y) * 2.0, 0.0, 1.0));\n }\n }\n }\n\n return CalcCoverage(xcov, ycov, xwgt, ywgt, glyphData.w);\n}\n\nfloat SlugRender(vec2 renderCoord, vec4 bandTransform, ivec4 glyphData) {\n vec2 emsPerPixel = fwidth(renderCoord);\n\n#if defined(SLUG_ADAPTIVE_SUPERSAMPLE)\n // Per-pixel rotated RGSS-4. The base RGSS offsets are rotated by a\n // unique angle per fragment (interleaved gradient noise). This converts\n // structured aliasing shimmer into uncorrelated grain that the eye\n // naturally filters out, much closer to how hardware MSAA on many small\n // triangles behaves perceptually.\n float noise = fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715))));\n float angle = noise * 6.2831853;\n float ca = cos(angle), sa = sin(angle);\n\n // Base RGSS offsets rotated by per-pixel angle\n vec2 o0 = vec2(ca * -0.375 - sa * 0.125, sa * -0.375 + ca * 0.125) * emsPerPixel;\n vec2 o1 = vec2(ca * 0.125 - sa * 0.375, sa * 0.125 + ca * 0.375) * emsPerPixel;\n vec2 o2 = vec2(ca * 0.375 - sa * -0.125, sa * 0.375 + ca * -0.125) * emsPerPixel;\n vec2 o3 = vec2(ca * -0.125 - sa * -0.375, sa * -0.125 + ca * -0.375) * emsPerPixel;\n\n float coverage =\n SlugRenderSingle(renderCoord + o0, emsPerPixel, bandTransform, glyphData) +\n SlugRenderSingle(renderCoord + o1, emsPerPixel, bandTransform, glyphData) +\n SlugRenderSingle(renderCoord + o2, emsPerPixel, bandTransform, glyphData) +\n SlugRenderSingle(renderCoord + o3, emsPerPixel, bandTransform, glyphData);\n return coverage * 0.25;\n#else\n return SlugRenderSingle(renderCoord, emsPerPixel, bandTransform, glyphData);\n#endif\n}\n\nvoid main() {\n float coverage = SlugRender(v_texcoord, v_banding, v_glyph);\n outColor = v_color * coverage;\n}\n";
|
|
63
59
|
|
|
64
|
-
// Slug shader source re-exports
|
|
65
|
-
// The .glsl/.wgsl files are the single source of truth, imported as strings
|
|
66
|
-
// at build time via the glslPlugin in rollup.config.js
|
|
67
60
|
// @ts-ignore - resolved by rollup glslPlugin
|
|
68
61
|
const fragmentShaderGLSL300 = fragGLSL;
|
|
69
62
|
|
|
70
|
-
// Slug
|
|
71
|
-
//
|
|
72
|
-
// WebGPURenderer (via GLSL-to-WGSL transpilation)
|
|
63
|
+
// Slug adapter using Three.js RawShaderMaterial (GLSL)
|
|
64
|
+
// Works with any Three.js renderer (WebGL or WebGPU via transpilation)
|
|
73
65
|
//
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
// - Uses native Uint32 band texture (no float conversion)
|
|
77
|
-
// - Supports GLSL animation injection via animationDeclarations/animationBody
|
|
78
|
-
// - Same tradeoff: no vertex dilation (may cause sub-pixel edge clipping at extreme zoom)
|
|
79
|
-
//
|
|
80
|
-
// Requires peer dependency: three
|
|
66
|
+
// Supports GLSL animation injection and native Uint32 band textures
|
|
67
|
+
// No vertex dilation; may clip at sub-pixel edges under extreme zoom
|
|
81
68
|
// @ts-ignore - three is a peer dependency
|
|
82
69
|
// Three.js GLSL3 mode prepends #version 300 es, so strip it from the raw shader
|
|
83
70
|
const fragShader = fragmentShaderGLSL300.replace(/^#version\s+300\s+es\s*\n/, '');
|
|
@@ -128,13 +115,12 @@ function createSlugGLSLMesh(gpuData, options) {
|
|
|
128
115
|
geo.setAttribute('glyphCenter', new THREE.Float32BufferAttribute(attrs.glyphCenters, 3));
|
|
129
116
|
geo.setAttribute('glyphIndex', new THREE.Float32BufferAttribute(attrs.glyphIndices, 1));
|
|
130
117
|
geo.setIndex(new THREE.BufferAttribute(gpuData.indices, 1));
|
|
131
|
-
// Curve texture: RGBA32F
|
|
132
118
|
const curveTex = new THREE.DataTexture(gpuData.curveTexture.data, gpuData.curveTexture.width, gpuData.curveTexture.height, THREE.RGBAFormat, THREE.FloatType);
|
|
133
119
|
curveTex.minFilter = THREE.NearestFilter;
|
|
134
120
|
curveTex.magFilter = THREE.NearestFilter;
|
|
135
121
|
curveTex.generateMipmaps = false;
|
|
136
122
|
curveTex.needsUpdate = true;
|
|
137
|
-
//
|
|
123
|
+
// RGBA32UI (unlike TSL, GLSL supports integer textures natively)
|
|
138
124
|
const bandTex = new THREE.DataTexture(gpuData.bandTexture.data, gpuData.bandTexture.width, gpuData.bandTexture.height);
|
|
139
125
|
bandTex.format = THREE.RGBAIntegerFormat;
|
|
140
126
|
bandTex.type = THREE.UnsignedIntType;
|
package/dist/vector/slugTSL.cjs
CHANGED
|
@@ -25,20 +25,11 @@ function _interopNamespaceDefault(e) {
|
|
|
25
25
|
|
|
26
26
|
var THREE__namespace = /*#__PURE__*/_interopNamespaceDefault(THREE);
|
|
27
27
|
|
|
28
|
-
// Slug
|
|
28
|
+
// Slug adapter using Three.js TSL (node materials)
|
|
29
|
+
// Works on both WebGPU and WebGL backends (r170+)
|
|
29
30
|
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
// band-accelerated ray-curve intersection
|
|
33
|
-
//
|
|
34
|
-
// Works on both WebGPU and WebGL backends via Three.js TSL
|
|
35
|
-
//
|
|
36
|
-
// Compared to the raw GLSL/WGSL standalone renderers, this adapter
|
|
37
|
-
// trades some features for Three.js integration:
|
|
38
|
-
// - No vertex dilation (may cause sub-pixel edge clipping at extreme zoom)
|
|
39
|
-
// - No adaptive supersampling (single-sample per fragment)
|
|
40
|
-
//
|
|
41
|
-
// Requires peer dependencies: three, three/tsl
|
|
31
|
+
// No vertex dilation or adaptive supersampling (unlike the raw
|
|
32
|
+
// GLSL/WGSL renderers); may clip at sub-pixel edges under extreme zoom
|
|
42
33
|
// @ts-ignore - three is a peer dependency
|
|
43
34
|
const LOG_BAND_TEX_W = 12;
|
|
44
35
|
const BAND_TEX_W_MASK = (1 << LOG_BAND_TEX_W) - 1;
|
|
@@ -100,10 +91,6 @@ const calcBandLoc = tsl.Fn(([glyphX, glyphY, offset]) => {
|
|
|
100
91
|
bx.assign(bx.bitAnd(BAND_TEX_W_MASK));
|
|
101
92
|
return tsl.ivec2(bx, by);
|
|
102
93
|
});
|
|
103
|
-
// Create a Three.js Mesh from SlugGPUData using TSL node materials.
|
|
104
|
-
// Returns a single transparent mesh suitable for any Three.js scene.
|
|
105
|
-
// The Slug algorithm evaluates per-fragment coverage analytically,
|
|
106
|
-
// so no stencil buffer or multi-pass rendering is required
|
|
107
94
|
function createSlugTSLMesh(gpuData, color) {
|
|
108
95
|
const attrs = index.unpackSlugVertices(gpuData);
|
|
109
96
|
const geo = new THREE__namespace.BufferGeometry();
|
|
@@ -130,16 +117,11 @@ function createSlugTSLMesh(gpuData, color) {
|
|
|
130
117
|
bandTex.magFilter = THREE__namespace.NearestFilter;
|
|
131
118
|
bandTex.generateMipmaps = false;
|
|
132
119
|
bandTex.needsUpdate = true;
|
|
133
|
-
// Varyings: vertex attributes interpolated to fragment stage
|
|
134
120
|
const vTexcoord = tsl.varying(tsl.attribute('slugTexcoord', 'vec2'), 'v_texcoord');
|
|
135
121
|
const vBanding = tsl.varying(tsl.attribute('slugBanding', 'vec4'), 'v_banding');
|
|
136
122
|
const vGlyph = tsl.varying(tsl.attribute('slugGlyph', 'vec4'), 'v_glyph');
|
|
137
123
|
const vColor = tsl.varying(tsl.attribute('slugColor', 'vec4'), 'v_color');
|
|
138
|
-
// Color uniform (allows dynamic color updates)
|
|
139
124
|
const textColor = tsl.uniform(new THREE__namespace.Color(color?.r ?? 1, color?.g ?? 1, color?.b ?? 1));
|
|
140
|
-
// Main per-fragment evaluation: SlugRenderSingle ported to TSL
|
|
141
|
-
// Evaluates horizontal and vertical band loops to compute
|
|
142
|
-
// analytic winding-number coverage
|
|
143
125
|
const slugRenderSingle = tsl.Fn(([renderCoord, emsPerPixel, bandTransform, glyphData]) => {
|
|
144
126
|
const pixelsPerEm = tsl.vec2(tsl.float(1).div(emsPerPixel.x), tsl.float(1).div(emsPerPixel.y));
|
|
145
127
|
const glyphLocX = glyphData.x.toInt();
|
|
@@ -148,7 +130,6 @@ function createSlugTSLMesh(gpuData, color) {
|
|
|
148
130
|
const bandMaxY = glyphData.w.toInt().bitAnd(0xFF);
|
|
149
131
|
const bandIdxX = tsl.max(tsl.min(renderCoord.x.mul(bandTransform.x).add(bandTransform.z).toInt(), bandMaxX), tsl.int(0));
|
|
150
132
|
const bandIdxY = tsl.max(tsl.min(renderCoord.y.mul(bandTransform.y).add(bandTransform.w).toInt(), bandMaxY), tsl.int(0));
|
|
151
|
-
// Horizontal band loop
|
|
152
133
|
const xcov = tsl.float(0).toVar();
|
|
153
134
|
const xwgt = tsl.float(0).toVar();
|
|
154
135
|
const hbandData = tsl.textureLoad(bandTex, tsl.ivec2(glyphLocX.add(bandIdxY), glyphLocY));
|
|
@@ -181,7 +162,6 @@ function createSlugTSLMesh(gpuData, color) {
|
|
|
181
162
|
});
|
|
182
163
|
hIdx.addAssign(1);
|
|
183
164
|
});
|
|
184
|
-
// Vertical band loop
|
|
185
165
|
const ycov = tsl.float(0).toVar();
|
|
186
166
|
const ywgt = tsl.float(0).toVar();
|
|
187
167
|
const vbandOffset = bandMaxY.add(1).add(bandIdxX);
|
|
@@ -215,17 +195,14 @@ function createSlugTSLMesh(gpuData, color) {
|
|
|
215
195
|
});
|
|
216
196
|
vIdx.addAssign(1);
|
|
217
197
|
});
|
|
218
|
-
// CalcCoverage (nonzero winding rule)
|
|
219
198
|
const coverage = tsl.max(tsl.abs(xcov.mul(xwgt).add(ycov.mul(ywgt))).div(tsl.max(xwgt.add(ywgt), tsl.float(1.0 / 65536.0))), tsl.min(tsl.abs(xcov), tsl.abs(ycov)));
|
|
220
199
|
return tsl.clamp(coverage, 0, 1);
|
|
221
200
|
});
|
|
222
|
-
// Top-level fragment node
|
|
223
201
|
const fragmentNode = tsl.Fn(() => {
|
|
224
202
|
const emsPerPixel = tsl.fwidth(vTexcoord);
|
|
225
203
|
const coverage = slugRenderSingle(vTexcoord, emsPerPixel, vBanding, vGlyph);
|
|
226
204
|
return tsl.vec4(textColor.x, textColor.y, textColor.z, vColor.w.mul(coverage));
|
|
227
205
|
})();
|
|
228
|
-
// Material & mesh
|
|
229
206
|
const material = new webgpu.MeshBasicNodeMaterial();
|
|
230
207
|
material.fragmentNode = fragmentNode;
|
|
231
208
|
material.transparent = true;
|
package/dist/vector/slugTSL.js
CHANGED
|
@@ -4,20 +4,11 @@ import { Fn, select, uint, float, sqrt, max, If, abs, vec2, ivec2, varying, attr
|
|
|
4
4
|
import { u as unpackSlugVertices } from './index2.js';
|
|
5
5
|
import './core/index.js';
|
|
6
6
|
|
|
7
|
-
// Slug
|
|
7
|
+
// Slug adapter using Three.js TSL (node materials)
|
|
8
|
+
// Works on both WebGPU and WebGL backends (r170+)
|
|
8
9
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// band-accelerated ray-curve intersection
|
|
12
|
-
//
|
|
13
|
-
// Works on both WebGPU and WebGL backends via Three.js TSL
|
|
14
|
-
//
|
|
15
|
-
// Compared to the raw GLSL/WGSL standalone renderers, this adapter
|
|
16
|
-
// trades some features for Three.js integration:
|
|
17
|
-
// - No vertex dilation (may cause sub-pixel edge clipping at extreme zoom)
|
|
18
|
-
// - No adaptive supersampling (single-sample per fragment)
|
|
19
|
-
//
|
|
20
|
-
// Requires peer dependencies: three, three/tsl
|
|
10
|
+
// No vertex dilation or adaptive supersampling (unlike the raw
|
|
11
|
+
// GLSL/WGSL renderers); may clip at sub-pixel edges under extreme zoom
|
|
21
12
|
// @ts-ignore - three is a peer dependency
|
|
22
13
|
const LOG_BAND_TEX_W = 12;
|
|
23
14
|
const BAND_TEX_W_MASK = (1 << LOG_BAND_TEX_W) - 1;
|
|
@@ -79,10 +70,6 @@ const calcBandLoc = Fn(([glyphX, glyphY, offset]) => {
|
|
|
79
70
|
bx.assign(bx.bitAnd(BAND_TEX_W_MASK));
|
|
80
71
|
return ivec2(bx, by);
|
|
81
72
|
});
|
|
82
|
-
// Create a Three.js Mesh from SlugGPUData using TSL node materials.
|
|
83
|
-
// Returns a single transparent mesh suitable for any Three.js scene.
|
|
84
|
-
// The Slug algorithm evaluates per-fragment coverage analytically,
|
|
85
|
-
// so no stencil buffer or multi-pass rendering is required
|
|
86
73
|
function createSlugTSLMesh(gpuData, color) {
|
|
87
74
|
const attrs = unpackSlugVertices(gpuData);
|
|
88
75
|
const geo = new THREE.BufferGeometry();
|
|
@@ -109,16 +96,11 @@ function createSlugTSLMesh(gpuData, color) {
|
|
|
109
96
|
bandTex.magFilter = THREE.NearestFilter;
|
|
110
97
|
bandTex.generateMipmaps = false;
|
|
111
98
|
bandTex.needsUpdate = true;
|
|
112
|
-
// Varyings: vertex attributes interpolated to fragment stage
|
|
113
99
|
const vTexcoord = varying(attribute('slugTexcoord', 'vec2'), 'v_texcoord');
|
|
114
100
|
const vBanding = varying(attribute('slugBanding', 'vec4'), 'v_banding');
|
|
115
101
|
const vGlyph = varying(attribute('slugGlyph', 'vec4'), 'v_glyph');
|
|
116
102
|
const vColor = varying(attribute('slugColor', 'vec4'), 'v_color');
|
|
117
|
-
// Color uniform (allows dynamic color updates)
|
|
118
103
|
const textColor = uniform(new THREE.Color(color?.r ?? 1, color?.g ?? 1, color?.b ?? 1));
|
|
119
|
-
// Main per-fragment evaluation: SlugRenderSingle ported to TSL
|
|
120
|
-
// Evaluates horizontal and vertical band loops to compute
|
|
121
|
-
// analytic winding-number coverage
|
|
122
104
|
const slugRenderSingle = Fn(([renderCoord, emsPerPixel, bandTransform, glyphData]) => {
|
|
123
105
|
const pixelsPerEm = vec2(float(1).div(emsPerPixel.x), float(1).div(emsPerPixel.y));
|
|
124
106
|
const glyphLocX = glyphData.x.toInt();
|
|
@@ -127,7 +109,6 @@ function createSlugTSLMesh(gpuData, color) {
|
|
|
127
109
|
const bandMaxY = glyphData.w.toInt().bitAnd(0xFF);
|
|
128
110
|
const bandIdxX = max(min(renderCoord.x.mul(bandTransform.x).add(bandTransform.z).toInt(), bandMaxX), int(0));
|
|
129
111
|
const bandIdxY = max(min(renderCoord.y.mul(bandTransform.y).add(bandTransform.w).toInt(), bandMaxY), int(0));
|
|
130
|
-
// Horizontal band loop
|
|
131
112
|
const xcov = float(0).toVar();
|
|
132
113
|
const xwgt = float(0).toVar();
|
|
133
114
|
const hbandData = textureLoad(bandTex, ivec2(glyphLocX.add(bandIdxY), glyphLocY));
|
|
@@ -160,7 +141,6 @@ function createSlugTSLMesh(gpuData, color) {
|
|
|
160
141
|
});
|
|
161
142
|
hIdx.addAssign(1);
|
|
162
143
|
});
|
|
163
|
-
// Vertical band loop
|
|
164
144
|
const ycov = float(0).toVar();
|
|
165
145
|
const ywgt = float(0).toVar();
|
|
166
146
|
const vbandOffset = bandMaxY.add(1).add(bandIdxX);
|
|
@@ -194,17 +174,14 @@ function createSlugTSLMesh(gpuData, color) {
|
|
|
194
174
|
});
|
|
195
175
|
vIdx.addAssign(1);
|
|
196
176
|
});
|
|
197
|
-
// CalcCoverage (nonzero winding rule)
|
|
198
177
|
const coverage = max(abs(xcov.mul(xwgt).add(ycov.mul(ywgt))).div(max(xwgt.add(ywgt), float(1.0 / 65536.0))), min(abs(xcov), abs(ycov)));
|
|
199
178
|
return clamp(coverage, 0, 1);
|
|
200
179
|
});
|
|
201
|
-
// Top-level fragment node
|
|
202
180
|
const fragmentNode = Fn(() => {
|
|
203
181
|
const emsPerPixel = fwidth(vTexcoord);
|
|
204
182
|
const coverage = slugRenderSingle(vTexcoord, emsPerPixel, vBanding, vGlyph);
|
|
205
183
|
return vec4(textColor.x, textColor.y, textColor.z, vColor.w.mul(coverage));
|
|
206
184
|
})();
|
|
207
|
-
// Material & mesh
|
|
208
185
|
const material = new MeshBasicNodeMaterial();
|
|
209
186
|
material.fragmentNode = fragmentNode;
|
|
210
187
|
material.transparent = true;
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var vertGLSL = "#version 300 es\n// GLSL 300 es port of the Slug vertex shader\n// Eric Lengyel, MIT License, 2017\n\nprecision highp float;\nprecision highp int;\n\n// Per-vertex attributes (5 x vec4, matching Slug reference layout)\nlayout(location = 0) in vec4 a_pos; // .xy = object-space position, .zw = outward normal\nlayout(location = 1) in vec4 a_tex; // .xy = em-space sample coords, .z = packed glyph loc, .w = packed band max + flags\nlayout(location = 2) in vec4 a_jac; // inverse Jacobian (2x2): (j00, j01, j10, j11)\nlayout(location = 3) in vec4 a_bnd; // (bandScaleX, bandScaleY, bandOffsetX, bandOffsetY)\nlayout(location = 4) in vec4 a_col; // vertex color RGBA\n\nuniform mat4 slug_matrix; // MVP matrix (rows as vec4s)\nuniform vec2 slug_viewport; // viewport dimensions in pixels\n\nout vec4 v_color;\nout vec2 v_texcoord;\nflat out vec4 v_banding;\nflat out ivec4 v_glyph;\n\nvoid SlugUnpack(vec4 tex, vec4 bnd, out vec4 vbnd, out ivec4 vgly) {\n uvec2 g = floatBitsToUint(tex.zw);\n vgly = ivec4(g.x & 0xFFFFu, g.x >> 16u, g.y & 0xFFFFu, g.y >> 16u);\n vbnd = bnd;\n}\n\nvec2 SlugDilate(vec4 pos, vec4 tex, vec4 jac, vec4 m0, vec4 m1, vec4 m3, vec2 dim, out vec2 vpos) {\n vec2 n = normalize(pos.zw);\n float s = dot(m3.xy, pos.xy) + m3.w;\n float t = dot(m3.xy, n);\n\n float u = (s * dot(m0.xy, n) - t * (dot(m0.xy, pos.xy) + m0.w)) * dim.x;\n float v = (s * dot(m1.xy, n) - t * (dot(m1.xy, pos.xy) + m1.w)) * dim.y;\n\n float s2 = s * s;\n float st = s * t;\n float uv = u * u + v * v;\n vec2 d = pos.zw * (s2 * (st + sqrt(uv)) / (uv - st * st));\n\n vpos = pos.xy + d;\n return vec2(tex.x + dot(d, jac.xy), tex.y + dot(d, jac.zw));\n}\n\nvoid main() {\n vec2 p;\n\n // Dynamic dilation: expand quad by a pixel to prevent edge clipping
|
|
3
|
+
var vertGLSL = "#version 300 es\n// GLSL 300 es port of the Slug vertex shader\n// Eric Lengyel, MIT License, 2017\n\nprecision highp float;\nprecision highp int;\n\n// Per-vertex attributes (5 x vec4, matching Slug reference layout)\nlayout(location = 0) in vec4 a_pos; // .xy = object-space position, .zw = outward normal\nlayout(location = 1) in vec4 a_tex; // .xy = em-space sample coords, .z = packed glyph loc, .w = packed band max + flags\nlayout(location = 2) in vec4 a_jac; // inverse Jacobian (2x2): (j00, j01, j10, j11)\nlayout(location = 3) in vec4 a_bnd; // (bandScaleX, bandScaleY, bandOffsetX, bandOffsetY)\nlayout(location = 4) in vec4 a_col; // vertex color RGBA\n\nuniform mat4 slug_matrix; // MVP matrix (rows as vec4s)\nuniform vec2 slug_viewport; // viewport dimensions in pixels\n\nout vec4 v_color;\nout vec2 v_texcoord;\nflat out vec4 v_banding;\nflat out ivec4 v_glyph;\n\nvoid SlugUnpack(vec4 tex, vec4 bnd, out vec4 vbnd, out ivec4 vgly) {\n uvec2 g = floatBitsToUint(tex.zw);\n vgly = ivec4(g.x & 0xFFFFu, g.x >> 16u, g.y & 0xFFFFu, g.y >> 16u);\n vbnd = bnd;\n}\n\nvec2 SlugDilate(vec4 pos, vec4 tex, vec4 jac, vec4 m0, vec4 m1, vec4 m3, vec2 dim, out vec2 vpos) {\n vec2 n = normalize(pos.zw);\n float s = dot(m3.xy, pos.xy) + m3.w;\n float t = dot(m3.xy, n);\n\n float u = (s * dot(m0.xy, n) - t * (dot(m0.xy, pos.xy) + m0.w)) * dim.x;\n float v = (s * dot(m1.xy, n) - t * (dot(m1.xy, pos.xy) + m1.w)) * dim.y;\n\n float s2 = s * s;\n float st = s * t;\n float uv = u * u + v * v;\n vec2 d = pos.zw * (s2 * (st + sqrt(uv)) / (uv - st * st));\n\n vpos = pos.xy + d;\n return vec2(tex.x + dot(d, jac.xy), tex.y + dot(d, jac.zw));\n}\n\nvoid main() {\n vec2 p;\n\n // Dynamic dilation: expand quad by a pixel to prevent edge clipping\n v_texcoord = SlugDilate(a_pos, a_tex, a_jac,\n slug_matrix[0], slug_matrix[1], slug_matrix[3],\n slug_viewport, p);\n\n // MVP transform on dilated position\n gl_Position.x = p.x * slug_matrix[0].x + p.y * slug_matrix[0].y + slug_matrix[0].w;\n gl_Position.y = p.x * slug_matrix[1].x + p.y * slug_matrix[1].y + slug_matrix[1].w;\n gl_Position.z = p.x * slug_matrix[2].x + p.y * slug_matrix[2].y + slug_matrix[2].w;\n gl_Position.w = p.x * slug_matrix[3].x + p.y * slug_matrix[3].y + slug_matrix[3].w;\n\n SlugUnpack(a_tex, a_bnd, v_banding, v_glyph);\n v_color = a_col;\n}\n";
|
|
4
4
|
|
|
5
5
|
var fragGLSL = "#version 300 es\n// GLSL 300 es port of the Slug fragment shader\n// Eric Lengyel, MIT License, 2017\n\nprecision highp float;\nprecision highp int;\n\n#define kLogBandTextureWidth 12\n\nin vec4 v_color;\nin vec2 v_texcoord;\nflat in vec4 v_banding;\nflat in ivec4 v_glyph;\n\nuniform sampler2D curveTexture; // RGBA32F control points\nuniform highp usampler2D bandTexture; // RGBA32UI band data\n\nlayout(location = 0) out vec4 outColor;\n\nuint CalcRootCode(float y1, float y2, float y3) {\n uint i1 = floatBitsToUint(y1) >> 31u;\n uint i2 = floatBitsToUint(y2) >> 30u;\n uint i3 = floatBitsToUint(y3) >> 29u;\n\n uint shift = (i2 & 2u) | (i1 & ~2u);\n shift = (i3 & 4u) | (shift & ~4u);\n\n return (0x2E74u >> shift) & 0x0101u;\n}\n\nvec2 SolveHorizPoly(vec4 p12, vec2 p3) {\n vec2 a = p12.xy - p12.zw * 2.0 + p3;\n vec2 b = p12.xy - p12.zw;\n float ra = 1.0 / a.y;\n float rb = 0.5 / b.y;\n\n float d = sqrt(max(b.y * b.y - a.y * p12.y, 0.0));\n float t1 = (b.y - d) * ra;\n float t2 = (b.y + d) * ra;\n\n if (abs(a.y) < 1.0 / 65536.0) t1 = t2 = p12.y * rb;\n\n return vec2((a.x * t1 - b.x * 2.0) * t1 + p12.x, (a.x * t2 - b.x * 2.0) * t2 + p12.x);\n}\n\nvec2 SolveVertPoly(vec4 p12, vec2 p3) {\n vec2 a = p12.xy - p12.zw * 2.0 + p3;\n vec2 b = p12.xy - p12.zw;\n float ra = 1.0 / a.x;\n float rb = 0.5 / b.x;\n\n float d = sqrt(max(b.x * b.x - a.x * p12.x, 0.0));\n float t1 = (b.x - d) * ra;\n float t2 = (b.x + d) * ra;\n\n if (abs(a.x) < 1.0 / 65536.0) t1 = t2 = p12.x * rb;\n\n return vec2((a.y * t1 - b.y * 2.0) * t1 + p12.y, (a.y * t2 - b.y * 2.0) * t2 + p12.y);\n}\n\nivec2 CalcBandLoc(ivec2 glyphLoc, uint offset) {\n ivec2 bandLoc = ivec2(glyphLoc.x + int(offset), glyphLoc.y);\n bandLoc.y += bandLoc.x >> kLogBandTextureWidth;\n bandLoc.x &= (1 << kLogBandTextureWidth) - 1;\n return bandLoc;\n}\n\nfloat CalcCoverage(float xcov, float ycov, float xwgt, float ywgt, int flags) {\n float coverage = max(abs(xcov * xwgt + ycov * ywgt) / max(xwgt + ywgt, 1.0 / 65536.0), min(abs(xcov), abs(ycov)));\n\n#if defined(SLUG_EVENODD)\n if ((flags & 0x1000) == 0) {\n#endif\n coverage = clamp(coverage, 0.0, 1.0);\n#if defined(SLUG_EVENODD)\n } else {\n coverage = 1.0 - abs(1.0 - fract(coverage * 0.5) * 2.0);\n }\n#endif\n\n#if defined(SLUG_WEIGHT)\n coverage = sqrt(coverage);\n#endif\n\n return coverage;\n}\n\nfloat SlugRenderSingle(vec2 renderCoord, vec2 emsPerPixel, vec4 bandTransform, ivec4 glyphData) {\n int curveIndex;\n\n vec2 pixelsPerEm = 1.0 / emsPerPixel;\n\n ivec2 bandMax = glyphData.zw;\n bandMax.y &= 0x00FF;\n\n ivec2 bandIndex = clamp(ivec2(renderCoord * bandTransform.xy + bandTransform.zw), ivec2(0, 0), bandMax);\n ivec2 glyphLoc = glyphData.xy;\n\n float xcov = 0.0;\n float xwgt = 0.0;\n\n uvec2 hbandData = texelFetch(bandTexture, ivec2(glyphLoc.x + bandIndex.y, glyphLoc.y), 0).xy;\n ivec2 hbandLoc = CalcBandLoc(glyphLoc, hbandData.y);\n\n for (curveIndex = 0; curveIndex < int(hbandData.x); curveIndex++) {\n ivec2 curveLoc = ivec2(texelFetch(bandTexture, ivec2(hbandLoc.x + curveIndex, hbandLoc.y), 0).xy);\n\n vec4 p12 = texelFetch(curveTexture, curveLoc, 0) - vec4(renderCoord, renderCoord);\n vec2 p3 = texelFetch(curveTexture, ivec2(curveLoc.x + 1, curveLoc.y), 0).xy - renderCoord;\n\n if (max(max(p12.x, p12.z), p3.x) * pixelsPerEm.x < -0.5) break;\n\n uint code = CalcRootCode(p12.y, p12.w, p3.y);\n if (code != 0u) {\n vec2 r = SolveHorizPoly(p12, p3) * pixelsPerEm.x;\n\n if ((code & 1u) != 0u) {\n xcov += clamp(r.x + 0.5, 0.0, 1.0);\n xwgt = max(xwgt, clamp(1.0 - abs(r.x) * 2.0, 0.0, 1.0));\n }\n\n if (code > 1u) {\n xcov -= clamp(r.y + 0.5, 0.0, 1.0);\n xwgt = max(xwgt, clamp(1.0 - abs(r.y) * 2.0, 0.0, 1.0));\n }\n }\n }\n\n float ycov = 0.0;\n float ywgt = 0.0;\n\n uvec2 vbandData = texelFetch(bandTexture, ivec2(glyphLoc.x + bandMax.y + 1 + bandIndex.x, glyphLoc.y), 0).xy;\n ivec2 vbandLoc = CalcBandLoc(glyphLoc, vbandData.y);\n\n for (curveIndex = 0; curveIndex < int(vbandData.x); curveIndex++) {\n ivec2 curveLoc = ivec2(texelFetch(bandTexture, ivec2(vbandLoc.x + curveIndex, vbandLoc.y), 0).xy);\n vec4 p12 = texelFetch(curveTexture, curveLoc, 0) - vec4(renderCoord, renderCoord);\n vec2 p3 = texelFetch(curveTexture, ivec2(curveLoc.x + 1, curveLoc.y), 0).xy - renderCoord;\n\n if (max(max(p12.y, p12.w), p3.y) * pixelsPerEm.y < -0.5) break;\n\n uint code = CalcRootCode(p12.x, p12.z, p3.x);\n if (code != 0u) {\n vec2 r = SolveVertPoly(p12, p3) * pixelsPerEm.y;\n\n if ((code & 1u) != 0u) {\n ycov -= clamp(r.x + 0.5, 0.0, 1.0);\n ywgt = max(ywgt, clamp(1.0 - abs(r.x) * 2.0, 0.0, 1.0));\n }\n\n if (code > 1u) {\n ycov += clamp(r.y + 0.5, 0.0, 1.0);\n ywgt = max(ywgt, clamp(1.0 - abs(r.y) * 2.0, 0.0, 1.0));\n }\n }\n }\n\n return CalcCoverage(xcov, ycov, xwgt, ywgt, glyphData.w);\n}\n\nfloat SlugRender(vec2 renderCoord, vec4 bandTransform, ivec4 glyphData) {\n vec2 emsPerPixel = fwidth(renderCoord);\n\n#if defined(SLUG_ADAPTIVE_SUPERSAMPLE)\n // Per-pixel rotated RGSS-4. The base RGSS offsets are rotated by a\n // unique angle per fragment (interleaved gradient noise). This converts\n // structured aliasing shimmer into uncorrelated grain that the eye\n // naturally filters out, much closer to how hardware MSAA on many small\n // triangles behaves perceptually.\n float noise = fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715))));\n float angle = noise * 6.2831853;\n float ca = cos(angle), sa = sin(angle);\n\n // Base RGSS offsets rotated by per-pixel angle\n vec2 o0 = vec2(ca * -0.375 - sa * 0.125, sa * -0.375 + ca * 0.125) * emsPerPixel;\n vec2 o1 = vec2(ca * 0.125 - sa * 0.375, sa * 0.125 + ca * 0.375) * emsPerPixel;\n vec2 o2 = vec2(ca * 0.375 - sa * -0.125, sa * 0.375 + ca * -0.125) * emsPerPixel;\n vec2 o3 = vec2(ca * -0.125 - sa * -0.375, sa * -0.125 + ca * -0.375) * emsPerPixel;\n\n float coverage =\n SlugRenderSingle(renderCoord + o0, emsPerPixel, bandTransform, glyphData) +\n SlugRenderSingle(renderCoord + o1, emsPerPixel, bandTransform, glyphData) +\n SlugRenderSingle(renderCoord + o2, emsPerPixel, bandTransform, glyphData) +\n SlugRenderSingle(renderCoord + o3, emsPerPixel, bandTransform, glyphData);\n return coverage * 0.25;\n#else\n return SlugRenderSingle(renderCoord, emsPerPixel, bandTransform, glyphData);\n#endif\n}\n\nvoid main() {\n float coverage = SlugRender(v_texcoord, v_banding, v_glyph);\n outColor = v_color * coverage;\n}\n";
|
|
6
6
|
|
|
7
|
-
// Slug shader source re-exports
|
|
8
|
-
// The .glsl/.wgsl files are the single source of truth, imported as strings
|
|
9
|
-
// at build time via the glslPlugin in rollup.config.js
|
|
10
7
|
// @ts-ignore - resolved by rollup glslPlugin
|
|
11
8
|
const vertexShaderGLSL300 = vertGLSL;
|
|
12
9
|
const fragmentShaderGLSL300 = fragGLSL;
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
var vertGLSL = "#version 300 es\n// GLSL 300 es port of the Slug vertex shader\n// Eric Lengyel, MIT License, 2017\n\nprecision highp float;\nprecision highp int;\n\n// Per-vertex attributes (5 x vec4, matching Slug reference layout)\nlayout(location = 0) in vec4 a_pos; // .xy = object-space position, .zw = outward normal\nlayout(location = 1) in vec4 a_tex; // .xy = em-space sample coords, .z = packed glyph loc, .w = packed band max + flags\nlayout(location = 2) in vec4 a_jac; // inverse Jacobian (2x2): (j00, j01, j10, j11)\nlayout(location = 3) in vec4 a_bnd; // (bandScaleX, bandScaleY, bandOffsetX, bandOffsetY)\nlayout(location = 4) in vec4 a_col; // vertex color RGBA\n\nuniform mat4 slug_matrix; // MVP matrix (rows as vec4s)\nuniform vec2 slug_viewport; // viewport dimensions in pixels\n\nout vec4 v_color;\nout vec2 v_texcoord;\nflat out vec4 v_banding;\nflat out ivec4 v_glyph;\n\nvoid SlugUnpack(vec4 tex, vec4 bnd, out vec4 vbnd, out ivec4 vgly) {\n uvec2 g = floatBitsToUint(tex.zw);\n vgly = ivec4(g.x & 0xFFFFu, g.x >> 16u, g.y & 0xFFFFu, g.y >> 16u);\n vbnd = bnd;\n}\n\nvec2 SlugDilate(vec4 pos, vec4 tex, vec4 jac, vec4 m0, vec4 m1, vec4 m3, vec2 dim, out vec2 vpos) {\n vec2 n = normalize(pos.zw);\n float s = dot(m3.xy, pos.xy) + m3.w;\n float t = dot(m3.xy, n);\n\n float u = (s * dot(m0.xy, n) - t * (dot(m0.xy, pos.xy) + m0.w)) * dim.x;\n float v = (s * dot(m1.xy, n) - t * (dot(m1.xy, pos.xy) + m1.w)) * dim.y;\n\n float s2 = s * s;\n float st = s * t;\n float uv = u * u + v * v;\n vec2 d = pos.zw * (s2 * (st + sqrt(uv)) / (uv - st * st));\n\n vpos = pos.xy + d;\n return vec2(tex.x + dot(d, jac.xy), tex.y + dot(d, jac.zw));\n}\n\nvoid main() {\n vec2 p;\n\n // Dynamic dilation: expand quad by a pixel to prevent edge clipping
|
|
1
|
+
var vertGLSL = "#version 300 es\n// GLSL 300 es port of the Slug vertex shader\n// Eric Lengyel, MIT License, 2017\n\nprecision highp float;\nprecision highp int;\n\n// Per-vertex attributes (5 x vec4, matching Slug reference layout)\nlayout(location = 0) in vec4 a_pos; // .xy = object-space position, .zw = outward normal\nlayout(location = 1) in vec4 a_tex; // .xy = em-space sample coords, .z = packed glyph loc, .w = packed band max + flags\nlayout(location = 2) in vec4 a_jac; // inverse Jacobian (2x2): (j00, j01, j10, j11)\nlayout(location = 3) in vec4 a_bnd; // (bandScaleX, bandScaleY, bandOffsetX, bandOffsetY)\nlayout(location = 4) in vec4 a_col; // vertex color RGBA\n\nuniform mat4 slug_matrix; // MVP matrix (rows as vec4s)\nuniform vec2 slug_viewport; // viewport dimensions in pixels\n\nout vec4 v_color;\nout vec2 v_texcoord;\nflat out vec4 v_banding;\nflat out ivec4 v_glyph;\n\nvoid SlugUnpack(vec4 tex, vec4 bnd, out vec4 vbnd, out ivec4 vgly) {\n uvec2 g = floatBitsToUint(tex.zw);\n vgly = ivec4(g.x & 0xFFFFu, g.x >> 16u, g.y & 0xFFFFu, g.y >> 16u);\n vbnd = bnd;\n}\n\nvec2 SlugDilate(vec4 pos, vec4 tex, vec4 jac, vec4 m0, vec4 m1, vec4 m3, vec2 dim, out vec2 vpos) {\n vec2 n = normalize(pos.zw);\n float s = dot(m3.xy, pos.xy) + m3.w;\n float t = dot(m3.xy, n);\n\n float u = (s * dot(m0.xy, n) - t * (dot(m0.xy, pos.xy) + m0.w)) * dim.x;\n float v = (s * dot(m1.xy, n) - t * (dot(m1.xy, pos.xy) + m1.w)) * dim.y;\n\n float s2 = s * s;\n float st = s * t;\n float uv = u * u + v * v;\n vec2 d = pos.zw * (s2 * (st + sqrt(uv)) / (uv - st * st));\n\n vpos = pos.xy + d;\n return vec2(tex.x + dot(d, jac.xy), tex.y + dot(d, jac.zw));\n}\n\nvoid main() {\n vec2 p;\n\n // Dynamic dilation: expand quad by a pixel to prevent edge clipping\n v_texcoord = SlugDilate(a_pos, a_tex, a_jac,\n slug_matrix[0], slug_matrix[1], slug_matrix[3],\n slug_viewport, p);\n\n // MVP transform on dilated position\n gl_Position.x = p.x * slug_matrix[0].x + p.y * slug_matrix[0].y + slug_matrix[0].w;\n gl_Position.y = p.x * slug_matrix[1].x + p.y * slug_matrix[1].y + slug_matrix[1].w;\n gl_Position.z = p.x * slug_matrix[2].x + p.y * slug_matrix[2].y + slug_matrix[2].w;\n gl_Position.w = p.x * slug_matrix[3].x + p.y * slug_matrix[3].y + slug_matrix[3].w;\n\n SlugUnpack(a_tex, a_bnd, v_banding, v_glyph);\n v_color = a_col;\n}\n";
|
|
2
2
|
|
|
3
3
|
var fragGLSL = "#version 300 es\n// GLSL 300 es port of the Slug fragment shader\n// Eric Lengyel, MIT License, 2017\n\nprecision highp float;\nprecision highp int;\n\n#define kLogBandTextureWidth 12\n\nin vec4 v_color;\nin vec2 v_texcoord;\nflat in vec4 v_banding;\nflat in ivec4 v_glyph;\n\nuniform sampler2D curveTexture; // RGBA32F control points\nuniform highp usampler2D bandTexture; // RGBA32UI band data\n\nlayout(location = 0) out vec4 outColor;\n\nuint CalcRootCode(float y1, float y2, float y3) {\n uint i1 = floatBitsToUint(y1) >> 31u;\n uint i2 = floatBitsToUint(y2) >> 30u;\n uint i3 = floatBitsToUint(y3) >> 29u;\n\n uint shift = (i2 & 2u) | (i1 & ~2u);\n shift = (i3 & 4u) | (shift & ~4u);\n\n return (0x2E74u >> shift) & 0x0101u;\n}\n\nvec2 SolveHorizPoly(vec4 p12, vec2 p3) {\n vec2 a = p12.xy - p12.zw * 2.0 + p3;\n vec2 b = p12.xy - p12.zw;\n float ra = 1.0 / a.y;\n float rb = 0.5 / b.y;\n\n float d = sqrt(max(b.y * b.y - a.y * p12.y, 0.0));\n float t1 = (b.y - d) * ra;\n float t2 = (b.y + d) * ra;\n\n if (abs(a.y) < 1.0 / 65536.0) t1 = t2 = p12.y * rb;\n\n return vec2((a.x * t1 - b.x * 2.0) * t1 + p12.x, (a.x * t2 - b.x * 2.0) * t2 + p12.x);\n}\n\nvec2 SolveVertPoly(vec4 p12, vec2 p3) {\n vec2 a = p12.xy - p12.zw * 2.0 + p3;\n vec2 b = p12.xy - p12.zw;\n float ra = 1.0 / a.x;\n float rb = 0.5 / b.x;\n\n float d = sqrt(max(b.x * b.x - a.x * p12.x, 0.0));\n float t1 = (b.x - d) * ra;\n float t2 = (b.x + d) * ra;\n\n if (abs(a.x) < 1.0 / 65536.0) t1 = t2 = p12.x * rb;\n\n return vec2((a.y * t1 - b.y * 2.0) * t1 + p12.y, (a.y * t2 - b.y * 2.0) * t2 + p12.y);\n}\n\nivec2 CalcBandLoc(ivec2 glyphLoc, uint offset) {\n ivec2 bandLoc = ivec2(glyphLoc.x + int(offset), glyphLoc.y);\n bandLoc.y += bandLoc.x >> kLogBandTextureWidth;\n bandLoc.x &= (1 << kLogBandTextureWidth) - 1;\n return bandLoc;\n}\n\nfloat CalcCoverage(float xcov, float ycov, float xwgt, float ywgt, int flags) {\n float coverage = max(abs(xcov * xwgt + ycov * ywgt) / max(xwgt + ywgt, 1.0 / 65536.0), min(abs(xcov), abs(ycov)));\n\n#if defined(SLUG_EVENODD)\n if ((flags & 0x1000) == 0) {\n#endif\n coverage = clamp(coverage, 0.0, 1.0);\n#if defined(SLUG_EVENODD)\n } else {\n coverage = 1.0 - abs(1.0 - fract(coverage * 0.5) * 2.0);\n }\n#endif\n\n#if defined(SLUG_WEIGHT)\n coverage = sqrt(coverage);\n#endif\n\n return coverage;\n}\n\nfloat SlugRenderSingle(vec2 renderCoord, vec2 emsPerPixel, vec4 bandTransform, ivec4 glyphData) {\n int curveIndex;\n\n vec2 pixelsPerEm = 1.0 / emsPerPixel;\n\n ivec2 bandMax = glyphData.zw;\n bandMax.y &= 0x00FF;\n\n ivec2 bandIndex = clamp(ivec2(renderCoord * bandTransform.xy + bandTransform.zw), ivec2(0, 0), bandMax);\n ivec2 glyphLoc = glyphData.xy;\n\n float xcov = 0.0;\n float xwgt = 0.0;\n\n uvec2 hbandData = texelFetch(bandTexture, ivec2(glyphLoc.x + bandIndex.y, glyphLoc.y), 0).xy;\n ivec2 hbandLoc = CalcBandLoc(glyphLoc, hbandData.y);\n\n for (curveIndex = 0; curveIndex < int(hbandData.x); curveIndex++) {\n ivec2 curveLoc = ivec2(texelFetch(bandTexture, ivec2(hbandLoc.x + curveIndex, hbandLoc.y), 0).xy);\n\n vec4 p12 = texelFetch(curveTexture, curveLoc, 0) - vec4(renderCoord, renderCoord);\n vec2 p3 = texelFetch(curveTexture, ivec2(curveLoc.x + 1, curveLoc.y), 0).xy - renderCoord;\n\n if (max(max(p12.x, p12.z), p3.x) * pixelsPerEm.x < -0.5) break;\n\n uint code = CalcRootCode(p12.y, p12.w, p3.y);\n if (code != 0u) {\n vec2 r = SolveHorizPoly(p12, p3) * pixelsPerEm.x;\n\n if ((code & 1u) != 0u) {\n xcov += clamp(r.x + 0.5, 0.0, 1.0);\n xwgt = max(xwgt, clamp(1.0 - abs(r.x) * 2.0, 0.0, 1.0));\n }\n\n if (code > 1u) {\n xcov -= clamp(r.y + 0.5, 0.0, 1.0);\n xwgt = max(xwgt, clamp(1.0 - abs(r.y) * 2.0, 0.0, 1.0));\n }\n }\n }\n\n float ycov = 0.0;\n float ywgt = 0.0;\n\n uvec2 vbandData = texelFetch(bandTexture, ivec2(glyphLoc.x + bandMax.y + 1 + bandIndex.x, glyphLoc.y), 0).xy;\n ivec2 vbandLoc = CalcBandLoc(glyphLoc, vbandData.y);\n\n for (curveIndex = 0; curveIndex < int(vbandData.x); curveIndex++) {\n ivec2 curveLoc = ivec2(texelFetch(bandTexture, ivec2(vbandLoc.x + curveIndex, vbandLoc.y), 0).xy);\n vec4 p12 = texelFetch(curveTexture, curveLoc, 0) - vec4(renderCoord, renderCoord);\n vec2 p3 = texelFetch(curveTexture, ivec2(curveLoc.x + 1, curveLoc.y), 0).xy - renderCoord;\n\n if (max(max(p12.y, p12.w), p3.y) * pixelsPerEm.y < -0.5) break;\n\n uint code = CalcRootCode(p12.x, p12.z, p3.x);\n if (code != 0u) {\n vec2 r = SolveVertPoly(p12, p3) * pixelsPerEm.y;\n\n if ((code & 1u) != 0u) {\n ycov -= clamp(r.x + 0.5, 0.0, 1.0);\n ywgt = max(ywgt, clamp(1.0 - abs(r.x) * 2.0, 0.0, 1.0));\n }\n\n if (code > 1u) {\n ycov += clamp(r.y + 0.5, 0.0, 1.0);\n ywgt = max(ywgt, clamp(1.0 - abs(r.y) * 2.0, 0.0, 1.0));\n }\n }\n }\n\n return CalcCoverage(xcov, ycov, xwgt, ywgt, glyphData.w);\n}\n\nfloat SlugRender(vec2 renderCoord, vec4 bandTransform, ivec4 glyphData) {\n vec2 emsPerPixel = fwidth(renderCoord);\n\n#if defined(SLUG_ADAPTIVE_SUPERSAMPLE)\n // Per-pixel rotated RGSS-4. The base RGSS offsets are rotated by a\n // unique angle per fragment (interleaved gradient noise). This converts\n // structured aliasing shimmer into uncorrelated grain that the eye\n // naturally filters out, much closer to how hardware MSAA on many small\n // triangles behaves perceptually.\n float noise = fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715))));\n float angle = noise * 6.2831853;\n float ca = cos(angle), sa = sin(angle);\n\n // Base RGSS offsets rotated by per-pixel angle\n vec2 o0 = vec2(ca * -0.375 - sa * 0.125, sa * -0.375 + ca * 0.125) * emsPerPixel;\n vec2 o1 = vec2(ca * 0.125 - sa * 0.375, sa * 0.125 + ca * 0.375) * emsPerPixel;\n vec2 o2 = vec2(ca * 0.375 - sa * -0.125, sa * 0.375 + ca * -0.125) * emsPerPixel;\n vec2 o3 = vec2(ca * -0.125 - sa * -0.375, sa * -0.125 + ca * -0.375) * emsPerPixel;\n\n float coverage =\n SlugRenderSingle(renderCoord + o0, emsPerPixel, bandTransform, glyphData) +\n SlugRenderSingle(renderCoord + o1, emsPerPixel, bandTransform, glyphData) +\n SlugRenderSingle(renderCoord + o2, emsPerPixel, bandTransform, glyphData) +\n SlugRenderSingle(renderCoord + o3, emsPerPixel, bandTransform, glyphData);\n return coverage * 0.25;\n#else\n return SlugRenderSingle(renderCoord, emsPerPixel, bandTransform, glyphData);\n#endif\n}\n\nvoid main() {\n float coverage = SlugRender(v_texcoord, v_banding, v_glyph);\n outColor = v_color * coverage;\n}\n";
|
|
4
4
|
|
|
5
|
-
// Slug shader source re-exports
|
|
6
|
-
// The .glsl/.wgsl files are the single source of truth, imported as strings
|
|
7
|
-
// at build time via the glslPlugin in rollup.config.js
|
|
8
5
|
// @ts-ignore - resolved by rollup glslPlugin
|
|
9
6
|
const vertexShaderGLSL300 = vertGLSL;
|
|
10
7
|
const fragmentShaderGLSL300 = fragGLSL;
|
|
@@ -4,9 +4,6 @@ var vertWGSL = "// WGSL port of the reference Slug vertex shader\n// Original HL
|
|
|
4
4
|
|
|
5
5
|
var fragWGSL = "// WGSL port of the reference Slug pixel shader\n// by Eric Lengyel, MIT License, Copyright 2017\n\nconst kLogBandTextureWidth: u32 = 12u;\n\n@group(0) @binding(1) var curveTexture: texture_2d<f32>;\n@group(0) @binding(2) var bandTexture: texture_2d<u32>;\n\nstruct FragmentInput {\n @location(0) color: vec4<f32>,\n @location(1) texcoord: vec2<f32>,\n @location(2) @interpolate(flat) banding: vec4<f32>,\n @location(3) @interpolate(flat) glyph: vec4<i32>,\n};\n\nfn CalcRootCode(y1: f32, y2: f32, y3: f32) -> u32 {\n let i1 = bitcast<u32>(y1) >> 31u;\n let i2 = bitcast<u32>(y2) >> 30u;\n let i3 = bitcast<u32>(y3) >> 29u;\n\n var shift = (i2 & 2u) | (i1 & ~2u);\n shift = (i3 & 4u) | (shift & ~4u);\n\n return (0x2E74u >> shift) & 0x0101u;\n}\n\nfn SolveHorizPoly(p12: vec4<f32>, p3: vec2<f32>) -> vec2<f32> {\n let a = p12.xy - p12.zw * 2.0 + p3;\n let b = p12.xy - p12.zw;\n let ra = 1.0 / a.y;\n let rb = 0.5 / b.y;\n\n let d = sqrt(max(b.y * b.y - a.y * p12.y, 0.0));\n var t1 = (b.y - d) * ra;\n var t2 = (b.y + d) * ra;\n\n if (abs(a.y) < 1.0 / 65536.0) {\n t1 = p12.y * rb;\n t2 = t1;\n }\n\n return vec2<f32>(\n (a.x * t1 - b.x * 2.0) * t1 + p12.x,\n (a.x * t2 - b.x * 2.0) * t2 + p12.x\n );\n}\n\nfn SolveVertPoly(p12: vec4<f32>, p3: vec2<f32>) -> vec2<f32> {\n let a = p12.xy - p12.zw * 2.0 + p3;\n let b = p12.xy - p12.zw;\n let ra = 1.0 / a.x;\n let rb = 0.5 / b.x;\n\n let d = sqrt(max(b.x * b.x - a.x * p12.x, 0.0));\n var t1 = (b.x - d) * ra;\n var t2 = (b.x + d) * ra;\n\n if (abs(a.x) < 1.0 / 65536.0) {\n t1 = p12.x * rb;\n t2 = t1;\n }\n\n return vec2<f32>(\n (a.y * t1 - b.y * 2.0) * t1 + p12.y,\n (a.y * t2 - b.y * 2.0) * t2 + p12.y\n );\n}\n\nfn CalcBandLoc(glyphLoc: vec2<i32>, offset: u32) -> vec2<i32> {\n var bandLoc = vec2<i32>(glyphLoc.x + i32(offset), glyphLoc.y);\n let bandShift = kLogBandTextureWidth;\n let bandMask = i32((1u << bandShift) - 1u);\n bandLoc.y += bandLoc.x >> bandShift;\n bandLoc.x &= bandMask;\n return bandLoc;\n}\n\nfn CalcCoverage(xcov: f32, ycov: f32, xwgt: f32, ywgt: f32) -> f32 {\n var coverage = max(\n abs(xcov * xwgt + ycov * ywgt) / max(xwgt + ywgt, 1.0 / 65536.0),\n min(abs(xcov), abs(ycov))\n );\n coverage = clamp(coverage, 0.0, 1.0);\n return coverage;\n}\n\nfn SlugRender(renderCoord: vec2<f32>, bandTransform: vec4<f32>, glyphData: vec4<i32>) -> f32 {\n let emsPerPixel = fwidth(renderCoord);\n let pixelsPerEm = 1.0 / emsPerPixel;\n\n var bandMax = glyphData.zw;\n bandMax.y &= 0x00FF;\n\n let bandIndex = clamp(\n vec2<i32>(renderCoord * bandTransform.xy + bandTransform.zw),\n vec2<i32>(0, 0),\n bandMax\n );\n let glyphLoc = glyphData.xy;\n\n var xcov = 0.0;\n var xwgt = 0.0;\n\n let hbandData = textureLoad(bandTexture, vec2<i32>(glyphLoc.x + bandIndex.y, glyphLoc.y), 0).xy;\n let hbandLoc = CalcBandLoc(glyphLoc, hbandData.y);\n\n for (var ci = 0; ci < i32(hbandData.x); ci++) {\n let curveLoc = vec2<i32>(textureLoad(bandTexture, vec2<i32>(hbandLoc.x + ci, hbandLoc.y), 0).xy);\n\n let p12 = textureLoad(curveTexture, curveLoc, 0) - vec4<f32>(renderCoord, renderCoord);\n let p3 = textureLoad(curveTexture, vec2<i32>(curveLoc.x + 1, curveLoc.y), 0).xy - renderCoord;\n\n if (max(max(p12.x, p12.z), p3.x) * pixelsPerEm.x < -0.5) { break; }\n\n let code = CalcRootCode(p12.y, p12.w, p3.y);\n if (code != 0u) {\n let r = SolveHorizPoly(p12, p3) * pixelsPerEm.x;\n\n if ((code & 1u) != 0u) {\n xcov += clamp(r.x + 0.5, 0.0, 1.0);\n xwgt = max(xwgt, clamp(1.0 - abs(r.x) * 2.0, 0.0, 1.0));\n }\n if (code > 1u) {\n xcov -= clamp(r.y + 0.5, 0.0, 1.0);\n xwgt = max(xwgt, clamp(1.0 - abs(r.y) * 2.0, 0.0, 1.0));\n }\n }\n }\n\n var ycov = 0.0;\n var ywgt = 0.0;\n\n let vbandData = textureLoad(bandTexture, vec2<i32>(glyphLoc.x + bandMax.y + 1 + bandIndex.x, glyphLoc.y), 0).xy;\n let vbandLoc = CalcBandLoc(glyphLoc, vbandData.y);\n\n for (var ci = 0; ci < i32(vbandData.x); ci++) {\n let curveLoc = vec2<i32>(textureLoad(bandTexture, vec2<i32>(vbandLoc.x + ci, vbandLoc.y), 0).xy);\n let p12 = textureLoad(curveTexture, curveLoc, 0) - vec4<f32>(renderCoord, renderCoord);\n let p3 = textureLoad(curveTexture, vec2<i32>(curveLoc.x + 1, curveLoc.y), 0).xy - renderCoord;\n\n if (max(max(p12.y, p12.w), p3.y) * pixelsPerEm.y < -0.5) { break; }\n\n let code = CalcRootCode(p12.x, p12.z, p3.x);\n if (code != 0u) {\n let r = SolveVertPoly(p12, p3) * pixelsPerEm.y;\n\n if ((code & 1u) != 0u) {\n ycov -= clamp(r.x + 0.5, 0.0, 1.0);\n ywgt = max(ywgt, clamp(1.0 - abs(r.x) * 2.0, 0.0, 1.0));\n }\n if (code > 1u) {\n ycov += clamp(r.y + 0.5, 0.0, 1.0);\n ywgt = max(ywgt, clamp(1.0 - abs(r.y) * 2.0, 0.0, 1.0));\n }\n }\n }\n\n return CalcCoverage(xcov, ycov, xwgt, ywgt);\n}\n\n@fragment\nfn fs_main(in: FragmentInput) -> @location(0) vec4<f32> {\n let coverage = SlugRender(in.texcoord, in.banding, in.glyph);\n return in.color * coverage;\n}\n";
|
|
6
6
|
|
|
7
|
-
// Slug shader source re-exports
|
|
8
|
-
// The .glsl/.wgsl files are the single source of truth, imported as strings
|
|
9
|
-
// at build time via the glslPlugin in rollup.config.js
|
|
10
7
|
// @ts-ignore - resolved by rollup glslPlugin
|
|
11
8
|
const vertexShaderWGSL = vertWGSL;
|
|
12
9
|
const fragmentShaderWGSL = fragWGSL;
|
|
@@ -2,9 +2,6 @@ var vertWGSL = "// WGSL port of the reference Slug vertex shader\n// Original HL
|
|
|
2
2
|
|
|
3
3
|
var fragWGSL = "// WGSL port of the reference Slug pixel shader\n// by Eric Lengyel, MIT License, Copyright 2017\n\nconst kLogBandTextureWidth: u32 = 12u;\n\n@group(0) @binding(1) var curveTexture: texture_2d<f32>;\n@group(0) @binding(2) var bandTexture: texture_2d<u32>;\n\nstruct FragmentInput {\n @location(0) color: vec4<f32>,\n @location(1) texcoord: vec2<f32>,\n @location(2) @interpolate(flat) banding: vec4<f32>,\n @location(3) @interpolate(flat) glyph: vec4<i32>,\n};\n\nfn CalcRootCode(y1: f32, y2: f32, y3: f32) -> u32 {\n let i1 = bitcast<u32>(y1) >> 31u;\n let i2 = bitcast<u32>(y2) >> 30u;\n let i3 = bitcast<u32>(y3) >> 29u;\n\n var shift = (i2 & 2u) | (i1 & ~2u);\n shift = (i3 & 4u) | (shift & ~4u);\n\n return (0x2E74u >> shift) & 0x0101u;\n}\n\nfn SolveHorizPoly(p12: vec4<f32>, p3: vec2<f32>) -> vec2<f32> {\n let a = p12.xy - p12.zw * 2.0 + p3;\n let b = p12.xy - p12.zw;\n let ra = 1.0 / a.y;\n let rb = 0.5 / b.y;\n\n let d = sqrt(max(b.y * b.y - a.y * p12.y, 0.0));\n var t1 = (b.y - d) * ra;\n var t2 = (b.y + d) * ra;\n\n if (abs(a.y) < 1.0 / 65536.0) {\n t1 = p12.y * rb;\n t2 = t1;\n }\n\n return vec2<f32>(\n (a.x * t1 - b.x * 2.0) * t1 + p12.x,\n (a.x * t2 - b.x * 2.0) * t2 + p12.x\n );\n}\n\nfn SolveVertPoly(p12: vec4<f32>, p3: vec2<f32>) -> vec2<f32> {\n let a = p12.xy - p12.zw * 2.0 + p3;\n let b = p12.xy - p12.zw;\n let ra = 1.0 / a.x;\n let rb = 0.5 / b.x;\n\n let d = sqrt(max(b.x * b.x - a.x * p12.x, 0.0));\n var t1 = (b.x - d) * ra;\n var t2 = (b.x + d) * ra;\n\n if (abs(a.x) < 1.0 / 65536.0) {\n t1 = p12.x * rb;\n t2 = t1;\n }\n\n return vec2<f32>(\n (a.y * t1 - b.y * 2.0) * t1 + p12.y,\n (a.y * t2 - b.y * 2.0) * t2 + p12.y\n );\n}\n\nfn CalcBandLoc(glyphLoc: vec2<i32>, offset: u32) -> vec2<i32> {\n var bandLoc = vec2<i32>(glyphLoc.x + i32(offset), glyphLoc.y);\n let bandShift = kLogBandTextureWidth;\n let bandMask = i32((1u << bandShift) - 1u);\n bandLoc.y += bandLoc.x >> bandShift;\n bandLoc.x &= bandMask;\n return bandLoc;\n}\n\nfn CalcCoverage(xcov: f32, ycov: f32, xwgt: f32, ywgt: f32) -> f32 {\n var coverage = max(\n abs(xcov * xwgt + ycov * ywgt) / max(xwgt + ywgt, 1.0 / 65536.0),\n min(abs(xcov), abs(ycov))\n );\n coverage = clamp(coverage, 0.0, 1.0);\n return coverage;\n}\n\nfn SlugRender(renderCoord: vec2<f32>, bandTransform: vec4<f32>, glyphData: vec4<i32>) -> f32 {\n let emsPerPixel = fwidth(renderCoord);\n let pixelsPerEm = 1.0 / emsPerPixel;\n\n var bandMax = glyphData.zw;\n bandMax.y &= 0x00FF;\n\n let bandIndex = clamp(\n vec2<i32>(renderCoord * bandTransform.xy + bandTransform.zw),\n vec2<i32>(0, 0),\n bandMax\n );\n let glyphLoc = glyphData.xy;\n\n var xcov = 0.0;\n var xwgt = 0.0;\n\n let hbandData = textureLoad(bandTexture, vec2<i32>(glyphLoc.x + bandIndex.y, glyphLoc.y), 0).xy;\n let hbandLoc = CalcBandLoc(glyphLoc, hbandData.y);\n\n for (var ci = 0; ci < i32(hbandData.x); ci++) {\n let curveLoc = vec2<i32>(textureLoad(bandTexture, vec2<i32>(hbandLoc.x + ci, hbandLoc.y), 0).xy);\n\n let p12 = textureLoad(curveTexture, curveLoc, 0) - vec4<f32>(renderCoord, renderCoord);\n let p3 = textureLoad(curveTexture, vec2<i32>(curveLoc.x + 1, curveLoc.y), 0).xy - renderCoord;\n\n if (max(max(p12.x, p12.z), p3.x) * pixelsPerEm.x < -0.5) { break; }\n\n let code = CalcRootCode(p12.y, p12.w, p3.y);\n if (code != 0u) {\n let r = SolveHorizPoly(p12, p3) * pixelsPerEm.x;\n\n if ((code & 1u) != 0u) {\n xcov += clamp(r.x + 0.5, 0.0, 1.0);\n xwgt = max(xwgt, clamp(1.0 - abs(r.x) * 2.0, 0.0, 1.0));\n }\n if (code > 1u) {\n xcov -= clamp(r.y + 0.5, 0.0, 1.0);\n xwgt = max(xwgt, clamp(1.0 - abs(r.y) * 2.0, 0.0, 1.0));\n }\n }\n }\n\n var ycov = 0.0;\n var ywgt = 0.0;\n\n let vbandData = textureLoad(bandTexture, vec2<i32>(glyphLoc.x + bandMax.y + 1 + bandIndex.x, glyphLoc.y), 0).xy;\n let vbandLoc = CalcBandLoc(glyphLoc, vbandData.y);\n\n for (var ci = 0; ci < i32(vbandData.x); ci++) {\n let curveLoc = vec2<i32>(textureLoad(bandTexture, vec2<i32>(vbandLoc.x + ci, vbandLoc.y), 0).xy);\n let p12 = textureLoad(curveTexture, curveLoc, 0) - vec4<f32>(renderCoord, renderCoord);\n let p3 = textureLoad(curveTexture, vec2<i32>(curveLoc.x + 1, curveLoc.y), 0).xy - renderCoord;\n\n if (max(max(p12.y, p12.w), p3.y) * pixelsPerEm.y < -0.5) { break; }\n\n let code = CalcRootCode(p12.x, p12.z, p3.x);\n if (code != 0u) {\n let r = SolveVertPoly(p12, p3) * pixelsPerEm.y;\n\n if ((code & 1u) != 0u) {\n ycov -= clamp(r.x + 0.5, 0.0, 1.0);\n ywgt = max(ywgt, clamp(1.0 - abs(r.x) * 2.0, 0.0, 1.0));\n }\n if (code > 1u) {\n ycov += clamp(r.y + 0.5, 0.0, 1.0);\n ywgt = max(ywgt, clamp(1.0 - abs(r.y) * 2.0, 0.0, 1.0));\n }\n }\n }\n\n return CalcCoverage(xcov, ycov, xwgt, ywgt);\n}\n\n@fragment\nfn fs_main(in: FragmentInput) -> @location(0) vec4<f32> {\n let coverage = SlugRender(in.texcoord, in.banding, in.glyph);\n return in.color * coverage;\n}\n";
|
|
4
4
|
|
|
5
|
-
// Slug shader source re-exports
|
|
6
|
-
// The .glsl/.wgsl files are the single source of truth, imported as strings
|
|
7
|
-
// at build time via the glslPlugin in rollup.config.js
|
|
8
5
|
// @ts-ignore - resolved by rollup glslPlugin
|
|
9
6
|
const vertexShaderWGSL = vertWGSL;
|
|
10
7
|
const fragmentShaderWGSL = fragWGSL;
|