three-text 0.6.0 → 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.
@@ -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 TSL adapter for Three.js WebGPURenderer (and WebGL via r170+)
7
+ // Slug adapter using Three.js TSL (node materials)
8
+ // Works on both WebGPU and WebGL backends (r170+)
8
9
  //
9
- // Creates a single Three.js Mesh with a NodeMaterial that implements
10
- // the Slug algorithm: per-fragment winding number evaluation via
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.\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";
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.\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";
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;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "three-text",
3
- "version": "0.6.0",
4
- "description": "3D mesh font geometry and text layout engine for the web",
3
+ "version": "0.6.2",
4
+ "description": "3D font rendering and text layout engine for the web",
5
5
  "main": "dist/three/index.cjs",
6
6
  "module": "dist/three/index.js",
7
7
  "type": "module",