three-text 0.5.1 → 0.6.0
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/LICENSE_THIRD_PARTY +15 -0
- package/README.md +80 -50
- package/dist/index.cjs +66 -20
- package/dist/index.d.ts +8 -0
- package/dist/index.js +66 -20
- package/dist/index.min.cjs +310 -307
- package/dist/index.min.js +265 -262
- package/dist/index.umd.js +68 -21
- package/dist/index.umd.min.js +268 -265
- package/dist/three/index.cjs +2 -1
- package/dist/three/index.d.ts +1 -0
- package/dist/three/index.js +2 -1
- package/dist/three/react.cjs +35 -17
- package/dist/three/react.d.ts +8 -0
- package/dist/three/react.js +35 -17
- package/dist/types/core/Text.d.ts +6 -0
- package/dist/types/core/types.d.ts +2 -33
- package/dist/types/three/index.d.ts +1 -0
- package/dist/types/vector/core/index.d.ts +28 -0
- package/dist/types/vector/index.d.ts +17 -12
- package/dist/types/vector/react.d.ts +3 -4
- package/dist/types/vector/slug/SlugPacker.d.ts +2 -0
- package/dist/types/vector/slug/curveUtils.d.ts +6 -0
- package/dist/types/vector/slug/index.d.ts +8 -0
- package/dist/types/vector/slug/shaderStrings.d.ts +4 -0
- package/dist/types/vector/slug/slugGLSL.d.ts +21 -0
- package/dist/types/vector/slug/slugTSL.d.ts +13 -0
- package/dist/types/vector/slug/types.d.ts +30 -0
- package/dist/types/vector/slug/unpackVertices.d.ts +11 -0
- package/dist/types/vector/webgl/index.d.ts +7 -3
- package/dist/types/vector/webgpu/index.d.ts +4 -4
- package/dist/vector/all.cjs +21 -0
- package/dist/vector/all.d.ts +134 -0
- package/dist/vector/all.js +2 -0
- package/dist/vector/core/index.cjs +856 -0
- package/dist/vector/core/index.d.ts +63 -0
- package/dist/vector/core/index.js +854 -0
- package/dist/vector/core.cjs +5489 -0
- package/dist/vector/core.d.ts +402 -0
- package/dist/vector/core.js +5486 -0
- package/dist/vector/index.cjs +5 -1305
- package/dist/vector/index.d.ts +41 -67
- package/dist/vector/index.js +3 -1306
- package/dist/vector/index2.cjs +287 -0
- package/dist/vector/index2.js +264 -0
- package/dist/vector/loopBlinnTSL.d.ts +69 -0
- package/dist/vector/react.cjs +54 -40
- package/dist/vector/react.d.ts +11 -2
- package/dist/vector/react.js +55 -41
- package/dist/vector/slugTSL.cjs +252 -0
- package/dist/vector/slugTSL.js +231 -0
- package/dist/vector/webgl/index.cjs +131 -201
- package/dist/vector/webgl/index.d.ts +19 -44
- package/dist/vector/webgl/index.js +131 -201
- package/dist/vector/webgpu/index.cjs +100 -283
- package/dist/vector/webgpu/index.d.ts +16 -45
- package/dist/vector/webgpu/index.js +100 -283
- package/package.json +6 -1
- package/dist/types/vector/GlyphVectorGeometryBuilder.d.ts +0 -26
- package/dist/types/vector/LoopBlinnGeometry.d.ts +0 -68
- package/dist/types/vector/loopBlinnTSL.d.ts +0 -11
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { MeshBasicNodeMaterial } from 'three/webgpu';
|
|
3
|
+
import { Fn, select, uint, float, sqrt, max, If, abs, vec2, ivec2, varying, attribute, uniform, min, int, textureLoad, Loop, vec4, Break, clamp, fwidth } from 'three/tsl';
|
|
4
|
+
import { u as unpackSlugVertices } from './index2.js';
|
|
5
|
+
import './core/index.js';
|
|
6
|
+
|
|
7
|
+
// Slug TSL adapter for Three.js WebGPURenderer (and WebGL via r170+)
|
|
8
|
+
//
|
|
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
|
|
21
|
+
// @ts-ignore - three is a peer dependency
|
|
22
|
+
const LOG_BAND_TEX_W = 12;
|
|
23
|
+
const BAND_TEX_W_MASK = (1 << LOG_BAND_TEX_W) - 1;
|
|
24
|
+
// Determines which quadratic roots to evaluate from the signs of
|
|
25
|
+
// control-point y-coordinates, using direct sign comparisons instead
|
|
26
|
+
// of the original floatBitsToUint encoding.
|
|
27
|
+
// Returns a 16-bit value where bit 0 = evaluate root 1,
|
|
28
|
+
// bit 8 = evaluate root 2 (matching the 0x0101 mask convention)
|
|
29
|
+
const calcRootCode = Fn(([y1, y2, y3]) => {
|
|
30
|
+
const s1 = select(y1.lessThan(0), uint(1), uint(0));
|
|
31
|
+
const s2 = select(y2.lessThan(0), uint(1), uint(0));
|
|
32
|
+
const s3 = select(y3.lessThan(0), uint(1), uint(0));
|
|
33
|
+
const shift = s1.bitOr(s2.shiftLeft(1)).bitOr(s3.shiftLeft(2));
|
|
34
|
+
return uint(0x2E74).shiftRight(shift).bitAnd(uint(0x0101));
|
|
35
|
+
});
|
|
36
|
+
// Solve horizontal quadratic: finds x-intercepts where the curve
|
|
37
|
+
// crosses the fragment's y = 0 line
|
|
38
|
+
const solveHorizPoly = Fn(([p12, p3]) => {
|
|
39
|
+
const ax = p12.x.sub(p12.z.mul(2)).add(p3.x);
|
|
40
|
+
const ay = p12.y.sub(p12.w.mul(2)).add(p3.y);
|
|
41
|
+
const bx = p12.x.sub(p12.z);
|
|
42
|
+
const by = p12.y.sub(p12.w);
|
|
43
|
+
const ra = float(1).div(ay);
|
|
44
|
+
const rb = float(0.5).div(by);
|
|
45
|
+
const d = sqrt(max(by.mul(by).sub(ay.mul(p12.y)), 0));
|
|
46
|
+
const t1 = by.sub(d).mul(ra).toVar();
|
|
47
|
+
const t2 = by.add(d).mul(ra).toVar();
|
|
48
|
+
If(abs(ay).lessThan(float(1.0 / 65536.0)), () => {
|
|
49
|
+
const fb = p12.y.mul(rb);
|
|
50
|
+
t1.assign(fb);
|
|
51
|
+
t2.assign(fb);
|
|
52
|
+
});
|
|
53
|
+
return vec2(ax.mul(t1).sub(bx.mul(2)).mul(t1).add(p12.x), ax.mul(t2).sub(bx.mul(2)).mul(t2).add(p12.x));
|
|
54
|
+
});
|
|
55
|
+
// Solve vertical quadratic: finds y-intercepts where the curve
|
|
56
|
+
// crosses the fragment's x = 0 line
|
|
57
|
+
const solveVertPoly = Fn(([p12, p3]) => {
|
|
58
|
+
const ax = p12.x.sub(p12.z.mul(2)).add(p3.x);
|
|
59
|
+
const ay = p12.y.sub(p12.w.mul(2)).add(p3.y);
|
|
60
|
+
const bx = p12.x.sub(p12.z);
|
|
61
|
+
const by = p12.y.sub(p12.w);
|
|
62
|
+
const ra = float(1).div(ax);
|
|
63
|
+
const rb = float(0.5).div(bx);
|
|
64
|
+
const d = sqrt(max(bx.mul(bx).sub(ax.mul(p12.x)), 0));
|
|
65
|
+
const t1 = bx.sub(d).mul(ra).toVar();
|
|
66
|
+
const t2 = bx.add(d).mul(ra).toVar();
|
|
67
|
+
If(abs(ax).lessThan(float(1.0 / 65536.0)), () => {
|
|
68
|
+
const fb = p12.x.mul(rb);
|
|
69
|
+
t1.assign(fb);
|
|
70
|
+
t2.assign(fb);
|
|
71
|
+
});
|
|
72
|
+
return vec2(ay.mul(t1).sub(by.mul(2)).mul(t1).add(p12.y), ay.mul(t2).sub(by.mul(2)).mul(t2).add(p12.y));
|
|
73
|
+
});
|
|
74
|
+
// Compute a band-texture coordinate with row wrapping
|
|
75
|
+
// Equivalent to CalcBandLoc in the GLSL reference
|
|
76
|
+
const calcBandLoc = Fn(([glyphX, glyphY, offset]) => {
|
|
77
|
+
const bx = glyphX.add(offset).toVar();
|
|
78
|
+
const by = glyphY.add(bx.shiftRight(LOG_BAND_TEX_W)).toVar();
|
|
79
|
+
bx.assign(bx.bitAnd(BAND_TEX_W_MASK));
|
|
80
|
+
return ivec2(bx, by);
|
|
81
|
+
});
|
|
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
|
+
function createSlugTSLMesh(gpuData, color) {
|
|
87
|
+
const attrs = unpackSlugVertices(gpuData);
|
|
88
|
+
const geo = new THREE.BufferGeometry();
|
|
89
|
+
geo.setAttribute('position', new THREE.Float32BufferAttribute(attrs.positions, 3));
|
|
90
|
+
geo.setAttribute('slugTexcoord', new THREE.Float32BufferAttribute(attrs.texcoords, 2));
|
|
91
|
+
geo.setAttribute('slugBanding', new THREE.Float32BufferAttribute(attrs.bandings, 4));
|
|
92
|
+
geo.setAttribute('slugGlyph', new THREE.Float32BufferAttribute(attrs.glyphData, 4));
|
|
93
|
+
geo.setAttribute('slugColor', new THREE.Float32BufferAttribute(attrs.colors, 4));
|
|
94
|
+
geo.setAttribute('glyphCenter', new THREE.Float32BufferAttribute(attrs.glyphCenters, 3));
|
|
95
|
+
geo.setAttribute('glyphIndex', new THREE.Float32BufferAttribute(attrs.glyphIndices, 1));
|
|
96
|
+
geo.setIndex(new THREE.BufferAttribute(gpuData.indices, 1));
|
|
97
|
+
const curveTex = new THREE.DataTexture(gpuData.curveTexture.data, gpuData.curveTexture.width, gpuData.curveTexture.height, THREE.RGBAFormat, THREE.FloatType);
|
|
98
|
+
curveTex.minFilter = THREE.NearestFilter;
|
|
99
|
+
curveTex.magFilter = THREE.NearestFilter;
|
|
100
|
+
curveTex.generateMipmaps = false;
|
|
101
|
+
curveTex.needsUpdate = true;
|
|
102
|
+
// Band texture: convert Uint32 to Float32 (values are small ints, exact in f32)
|
|
103
|
+
const bandFloat = new Float32Array(gpuData.bandTexture.data.length);
|
|
104
|
+
for (let i = 0; i < bandFloat.length; i++) {
|
|
105
|
+
bandFloat[i] = gpuData.bandTexture.data[i];
|
|
106
|
+
}
|
|
107
|
+
const bandTex = new THREE.DataTexture(bandFloat, gpuData.bandTexture.width, gpuData.bandTexture.height, THREE.RGBAFormat, THREE.FloatType);
|
|
108
|
+
bandTex.minFilter = THREE.NearestFilter;
|
|
109
|
+
bandTex.magFilter = THREE.NearestFilter;
|
|
110
|
+
bandTex.generateMipmaps = false;
|
|
111
|
+
bandTex.needsUpdate = true;
|
|
112
|
+
// Varyings: vertex attributes interpolated to fragment stage
|
|
113
|
+
const vTexcoord = varying(attribute('slugTexcoord', 'vec2'), 'v_texcoord');
|
|
114
|
+
const vBanding = varying(attribute('slugBanding', 'vec4'), 'v_banding');
|
|
115
|
+
const vGlyph = varying(attribute('slugGlyph', 'vec4'), 'v_glyph');
|
|
116
|
+
const vColor = varying(attribute('slugColor', 'vec4'), 'v_color');
|
|
117
|
+
// Color uniform (allows dynamic color updates)
|
|
118
|
+
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
|
+
const slugRenderSingle = Fn(([renderCoord, emsPerPixel, bandTransform, glyphData]) => {
|
|
123
|
+
const pixelsPerEm = vec2(float(1).div(emsPerPixel.x), float(1).div(emsPerPixel.y));
|
|
124
|
+
const glyphLocX = glyphData.x.toInt();
|
|
125
|
+
const glyphLocY = glyphData.y.toInt();
|
|
126
|
+
const bandMaxX = glyphData.z.toInt();
|
|
127
|
+
const bandMaxY = glyphData.w.toInt().bitAnd(0xFF);
|
|
128
|
+
const bandIdxX = max(min(renderCoord.x.mul(bandTransform.x).add(bandTransform.z).toInt(), bandMaxX), int(0));
|
|
129
|
+
const bandIdxY = max(min(renderCoord.y.mul(bandTransform.y).add(bandTransform.w).toInt(), bandMaxY), int(0));
|
|
130
|
+
// Horizontal band loop
|
|
131
|
+
const xcov = float(0).toVar();
|
|
132
|
+
const xwgt = float(0).toVar();
|
|
133
|
+
const hbandData = textureLoad(bandTex, ivec2(glyphLocX.add(bandIdxY), glyphLocY));
|
|
134
|
+
const hCurveCount = hbandData.x.toInt();
|
|
135
|
+
const hListOffset = hbandData.y.toInt();
|
|
136
|
+
const hbandLoc = calcBandLoc(glyphLocX, glyphLocY, hListOffset);
|
|
137
|
+
const hIdx = int(0).toVar();
|
|
138
|
+
Loop(hIdx.lessThan(hCurveCount), () => {
|
|
139
|
+
const clEntry = textureLoad(bandTex, ivec2(hbandLoc.x.add(hIdx), hbandLoc.y));
|
|
140
|
+
const cLocX = clEntry.x.toInt();
|
|
141
|
+
const cLocY = clEntry.y.toInt();
|
|
142
|
+
const rawP12 = textureLoad(curveTex, ivec2(cLocX, cLocY));
|
|
143
|
+
const rawP3 = textureLoad(curveTex, ivec2(cLocX.add(1), cLocY));
|
|
144
|
+
const p12 = vec4(rawP12.x.sub(renderCoord.x), rawP12.y.sub(renderCoord.y), rawP12.z.sub(renderCoord.x), rawP12.w.sub(renderCoord.y));
|
|
145
|
+
const p3 = vec2(rawP3.x.sub(renderCoord.x), rawP3.y.sub(renderCoord.y));
|
|
146
|
+
If(max(max(p12.x, p12.z), p3.x).mul(pixelsPerEm.x).lessThan(-0.5), () => {
|
|
147
|
+
Break();
|
|
148
|
+
});
|
|
149
|
+
const code = calcRootCode(p12.y, p12.w, p3.y);
|
|
150
|
+
If(code.notEqual(uint(0)), () => {
|
|
151
|
+
const r = solveHorizPoly(p12, p3).mul(pixelsPerEm.x);
|
|
152
|
+
If(code.bitAnd(uint(1)).notEqual(uint(0)), () => {
|
|
153
|
+
xcov.addAssign(clamp(r.x.add(0.5), 0, 1));
|
|
154
|
+
xwgt.assign(max(xwgt, clamp(float(1).sub(abs(r.x).mul(2)), 0, 1)));
|
|
155
|
+
});
|
|
156
|
+
If(code.greaterThan(uint(1)), () => {
|
|
157
|
+
xcov.subAssign(clamp(r.y.add(0.5), 0, 1));
|
|
158
|
+
xwgt.assign(max(xwgt, clamp(float(1).sub(abs(r.y).mul(2)), 0, 1)));
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
hIdx.addAssign(1);
|
|
162
|
+
});
|
|
163
|
+
// Vertical band loop
|
|
164
|
+
const ycov = float(0).toVar();
|
|
165
|
+
const ywgt = float(0).toVar();
|
|
166
|
+
const vbandOffset = bandMaxY.add(1).add(bandIdxX);
|
|
167
|
+
const vbandData = textureLoad(bandTex, ivec2(glyphLocX.add(vbandOffset), glyphLocY));
|
|
168
|
+
const vCurveCount = vbandData.x.toInt();
|
|
169
|
+
const vListOffset = vbandData.y.toInt();
|
|
170
|
+
const vbandLoc = calcBandLoc(glyphLocX, glyphLocY, vListOffset);
|
|
171
|
+
const vIdx = int(0).toVar();
|
|
172
|
+
Loop(vIdx.lessThan(vCurveCount), () => {
|
|
173
|
+
const clEntry = textureLoad(bandTex, ivec2(vbandLoc.x.add(vIdx), vbandLoc.y));
|
|
174
|
+
const cLocX = clEntry.x.toInt();
|
|
175
|
+
const cLocY = clEntry.y.toInt();
|
|
176
|
+
const rawP12 = textureLoad(curveTex, ivec2(cLocX, cLocY));
|
|
177
|
+
const rawP3 = textureLoad(curveTex, ivec2(cLocX.add(1), cLocY));
|
|
178
|
+
const p12 = vec4(rawP12.x.sub(renderCoord.x), rawP12.y.sub(renderCoord.y), rawP12.z.sub(renderCoord.x), rawP12.w.sub(renderCoord.y));
|
|
179
|
+
const p3 = vec2(rawP3.x.sub(renderCoord.x), rawP3.y.sub(renderCoord.y));
|
|
180
|
+
If(max(max(p12.y, p12.w), p3.y).mul(pixelsPerEm.y).lessThan(-0.5), () => {
|
|
181
|
+
Break();
|
|
182
|
+
});
|
|
183
|
+
const code = calcRootCode(p12.x, p12.z, p3.x);
|
|
184
|
+
If(code.notEqual(uint(0)), () => {
|
|
185
|
+
const r = solveVertPoly(p12, p3).mul(pixelsPerEm.y);
|
|
186
|
+
If(code.bitAnd(uint(1)).notEqual(uint(0)), () => {
|
|
187
|
+
ycov.subAssign(clamp(r.x.add(0.5), 0, 1));
|
|
188
|
+
ywgt.assign(max(ywgt, clamp(float(1).sub(abs(r.x).mul(2)), 0, 1)));
|
|
189
|
+
});
|
|
190
|
+
If(code.greaterThan(uint(1)), () => {
|
|
191
|
+
ycov.addAssign(clamp(r.y.add(0.5), 0, 1));
|
|
192
|
+
ywgt.assign(max(ywgt, clamp(float(1).sub(abs(r.y).mul(2)), 0, 1)));
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
vIdx.addAssign(1);
|
|
196
|
+
});
|
|
197
|
+
// CalcCoverage (nonzero winding rule)
|
|
198
|
+
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
|
+
return clamp(coverage, 0, 1);
|
|
200
|
+
});
|
|
201
|
+
// Top-level fragment node
|
|
202
|
+
const fragmentNode = Fn(() => {
|
|
203
|
+
const emsPerPixel = fwidth(vTexcoord);
|
|
204
|
+
const coverage = slugRenderSingle(vTexcoord, emsPerPixel, vBanding, vGlyph);
|
|
205
|
+
return vec4(textColor.x, textColor.y, textColor.z, vColor.w.mul(coverage));
|
|
206
|
+
})();
|
|
207
|
+
// Material & mesh
|
|
208
|
+
const material = new MeshBasicNodeMaterial();
|
|
209
|
+
material.fragmentNode = fragmentNode;
|
|
210
|
+
material.transparent = true;
|
|
211
|
+
material.depthWrite = false;
|
|
212
|
+
material.side = THREE.DoubleSide;
|
|
213
|
+
const mesh = new THREE.Mesh(geo, material);
|
|
214
|
+
return {
|
|
215
|
+
mesh,
|
|
216
|
+
setOffset(x, y, z = 0) {
|
|
217
|
+
mesh.position.set(x, y, z);
|
|
218
|
+
},
|
|
219
|
+
setColor(r, g, b) {
|
|
220
|
+
textColor.value.setRGB(r, g, b);
|
|
221
|
+
},
|
|
222
|
+
dispose() {
|
|
223
|
+
geo.dispose();
|
|
224
|
+
material.dispose();
|
|
225
|
+
curveTex.dispose();
|
|
226
|
+
bandTex.dispose();
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export { createSlugTSLMesh };
|
|
@@ -1,229 +1,159 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
+
|
|
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
|
+
|
|
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
|
+
// @ts-ignore - resolved by rollup glslPlugin
|
|
11
|
+
const vertexShaderGLSL300 = vertGLSL;
|
|
12
|
+
const fragmentShaderGLSL300 = fragGLSL;
|
|
13
|
+
|
|
14
|
+
// Raw WebGL2 Slug vector text renderer
|
|
15
|
+
// Standalone, no dependency on Three.js
|
|
9
16
|
function compileShader(gl, type, source) {
|
|
10
|
-
const shader =
|
|
17
|
+
const shader = gl.createShader(type);
|
|
18
|
+
if (!shader)
|
|
19
|
+
throw new Error('Failed to create shader');
|
|
11
20
|
gl.shaderSource(shader, source);
|
|
12
21
|
gl.compileShader(shader);
|
|
13
22
|
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
14
|
-
const info = gl.getShaderInfoLog(shader)
|
|
23
|
+
const info = gl.getShaderInfoLog(shader);
|
|
15
24
|
gl.deleteShader(shader);
|
|
16
|
-
throw new Error(info);
|
|
25
|
+
throw new Error(`Shader compile failed: ${info}`);
|
|
17
26
|
}
|
|
18
27
|
return shader;
|
|
19
28
|
}
|
|
20
|
-
function
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
gl.
|
|
27
|
-
gl.
|
|
28
|
-
gl.
|
|
29
|
-
if (!gl.getProgramParameter(
|
|
30
|
-
const info = gl.getProgramInfoLog(
|
|
31
|
-
gl.deleteProgram(
|
|
32
|
-
throw new Error(info);
|
|
29
|
+
function createProgram(gl, vsSrc, fsSrc) {
|
|
30
|
+
const vs = compileShader(gl, gl.VERTEX_SHADER, vsSrc);
|
|
31
|
+
const fs = compileShader(gl, gl.FRAGMENT_SHADER, fsSrc);
|
|
32
|
+
const prog = gl.createProgram();
|
|
33
|
+
if (!prog)
|
|
34
|
+
throw new Error('Failed to create program');
|
|
35
|
+
gl.attachShader(prog, vs);
|
|
36
|
+
gl.attachShader(prog, fs);
|
|
37
|
+
gl.linkProgram(prog);
|
|
38
|
+
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
|
|
39
|
+
const info = gl.getProgramInfoLog(prog);
|
|
40
|
+
gl.deleteProgram(prog);
|
|
41
|
+
throw new Error(`Program link failed: ${info}`);
|
|
33
42
|
}
|
|
34
|
-
|
|
43
|
+
gl.deleteShader(vs);
|
|
44
|
+
gl.deleteShader(fs);
|
|
45
|
+
return prog;
|
|
35
46
|
}
|
|
36
|
-
function
|
|
37
|
-
const
|
|
38
|
-
if (!
|
|
39
|
-
throw new Error(
|
|
40
|
-
|
|
41
|
-
|
|
47
|
+
function createRGBA32FTexture(gl, data, width, height) {
|
|
48
|
+
const tex = gl.createTexture();
|
|
49
|
+
if (!tex)
|
|
50
|
+
throw new Error('Failed to create texture');
|
|
51
|
+
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
52
|
+
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
|
|
53
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
54
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
55
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
56
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
57
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, width, height, 0, gl.RGBA, gl.FLOAT, data);
|
|
58
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
59
|
+
return tex;
|
|
42
60
|
}
|
|
43
|
-
function
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
61
|
+
function createRGBA32UITexture(gl, data, width, height) {
|
|
62
|
+
const tex = gl.createTexture();
|
|
63
|
+
if (!tex)
|
|
64
|
+
throw new Error('Failed to create texture');
|
|
65
|
+
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
66
|
+
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
|
|
67
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
68
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
69
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
70
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
71
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32UI, width, height, 0, gl.RGBA_INTEGER, gl.UNSIGNED_INT, data);
|
|
72
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
73
|
+
return tex;
|
|
49
74
|
}
|
|
50
|
-
function
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
75
|
+
function createResources(gl, gpuData, fragSrc) {
|
|
76
|
+
gl.getExtension('EXT_color_buffer_float');
|
|
77
|
+
const program = createProgram(gl, vertexShaderGLSL300, fragSrc);
|
|
78
|
+
const uniforms = {
|
|
79
|
+
slug_matrix: gl.getUniformLocation(program, 'slug_matrix'),
|
|
80
|
+
slug_viewport: gl.getUniformLocation(program, 'slug_viewport'),
|
|
81
|
+
curveTexture: gl.getUniformLocation(program, 'curveTexture'),
|
|
82
|
+
bandTexture: gl.getUniformLocation(program, 'bandTexture'),
|
|
56
83
|
};
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
gl.
|
|
65
|
-
gl.
|
|
66
|
-
|
|
67
|
-
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, interiorIndexBuffer);
|
|
68
|
-
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, data.interiorIndices, gl.STATIC_DRAW);
|
|
69
|
-
const curveVAO = assertCreate(gl.createVertexArray(), 'curve VAO');
|
|
70
|
-
const curvePositionBuffer = assertCreate(gl.createBuffer(), 'curve position buffer');
|
|
71
|
-
gl.bindVertexArray(curveVAO);
|
|
72
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, curvePositionBuffer);
|
|
73
|
-
gl.bufferData(gl.ARRAY_BUFFER, data.curvePositions, gl.STATIC_DRAW);
|
|
74
|
-
gl.enableVertexAttribArray(0);
|
|
75
|
-
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
|
|
76
|
-
const fillVAO = assertCreate(gl.createVertexArray(), 'fill VAO');
|
|
77
|
-
const fillPositionBuffer = assertCreate(gl.createBuffer(), 'fill position buffer');
|
|
78
|
-
const fillIndexBuffer = assertCreate(gl.createBuffer(), 'fill index buffer');
|
|
79
|
-
gl.bindVertexArray(fillVAO);
|
|
80
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, fillPositionBuffer);
|
|
81
|
-
gl.bufferData(gl.ARRAY_BUFFER, data.fillPositions, gl.STATIC_DRAW);
|
|
84
|
+
const vao = gl.createVertexArray();
|
|
85
|
+
if (!vao)
|
|
86
|
+
throw new Error('Failed to create VAO');
|
|
87
|
+
gl.bindVertexArray(vao);
|
|
88
|
+
const vbo = gl.createBuffer();
|
|
89
|
+
if (!vbo)
|
|
90
|
+
throw new Error('Failed to create VBO');
|
|
91
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
|
|
92
|
+
gl.bufferData(gl.ARRAY_BUFFER, gpuData.vertices, gl.STATIC_DRAW);
|
|
93
|
+
const stride = 20 * 4;
|
|
82
94
|
gl.enableVertexAttribArray(0);
|
|
83
|
-
gl.vertexAttribPointer(0,
|
|
84
|
-
gl.
|
|
85
|
-
gl.
|
|
95
|
+
gl.vertexAttribPointer(0, 4, gl.FLOAT, false, stride, 0);
|
|
96
|
+
gl.enableVertexAttribArray(1);
|
|
97
|
+
gl.vertexAttribPointer(1, 4, gl.FLOAT, false, stride, 4 * 4);
|
|
98
|
+
gl.enableVertexAttribArray(2);
|
|
99
|
+
gl.vertexAttribPointer(2, 4, gl.FLOAT, false, stride, 8 * 4);
|
|
100
|
+
gl.enableVertexAttribArray(3);
|
|
101
|
+
gl.vertexAttribPointer(3, 4, gl.FLOAT, false, stride, 12 * 4);
|
|
102
|
+
gl.enableVertexAttribArray(4);
|
|
103
|
+
gl.vertexAttribPointer(4, 4, gl.FLOAT, false, stride, 16 * 4);
|
|
104
|
+
const ibo = gl.createBuffer();
|
|
105
|
+
if (!ibo)
|
|
106
|
+
throw new Error('Failed to create IBO');
|
|
107
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
|
|
108
|
+
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, gpuData.indices, gl.STATIC_DRAW);
|
|
86
109
|
gl.bindVertexArray(null);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return {
|
|
90
|
-
interiorVAO,
|
|
91
|
-
interiorPositionBuffer,
|
|
92
|
-
interiorIndexBuffer,
|
|
93
|
-
interiorIndexCount: data.interiorIndices.length,
|
|
94
|
-
curveVAO,
|
|
95
|
-
curvePositionBuffer,
|
|
96
|
-
curveVertexCount: data.curvePositions.length / 3,
|
|
97
|
-
fillVAO,
|
|
98
|
-
fillPositionBuffer,
|
|
99
|
-
fillIndexBuffer,
|
|
100
|
-
fillIndexCount: data.fillIndices.length
|
|
101
|
-
};
|
|
110
|
+
const curveTexture = createRGBA32FTexture(gl, gpuData.curveTexture.data, gpuData.curveTexture.width, gpuData.curveTexture.height);
|
|
111
|
+
const bandTexture = createRGBA32UITexture(gl, gpuData.bandTexture.data, gpuData.bandTexture.width, gpuData.bandTexture.height);
|
|
112
|
+
return { program, vao, vbo, ibo, curveTexture, bandTexture, uniforms, indexCount: gpuData.indices.length };
|
|
102
113
|
}
|
|
103
|
-
function
|
|
104
|
-
gl.
|
|
105
|
-
gl.
|
|
106
|
-
gl.
|
|
107
|
-
gl.
|
|
108
|
-
gl.
|
|
109
|
-
gl.
|
|
110
|
-
gl.
|
|
111
|
-
gl.
|
|
114
|
+
function draw(gl, res, mvpMatrix, viewportWidth, viewportHeight) {
|
|
115
|
+
gl.useProgram(res.program);
|
|
116
|
+
gl.uniformMatrix4fv(res.uniforms.slug_matrix, false, mvpMatrix);
|
|
117
|
+
gl.uniform2f(res.uniforms.slug_viewport, viewportWidth, viewportHeight);
|
|
118
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
119
|
+
gl.bindTexture(gl.TEXTURE_2D, res.curveTexture);
|
|
120
|
+
gl.uniform1i(res.uniforms.curveTexture, 0);
|
|
121
|
+
gl.activeTexture(gl.TEXTURE1);
|
|
122
|
+
gl.bindTexture(gl.TEXTURE_2D, res.bandTexture);
|
|
123
|
+
gl.uniform1i(res.uniforms.bandTexture, 1);
|
|
124
|
+
gl.bindVertexArray(res.vao);
|
|
125
|
+
gl.enable(gl.BLEND);
|
|
126
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
127
|
+
gl.drawElements(gl.TRIANGLES, res.indexCount, gl.UNSIGNED_SHORT, 0);
|
|
128
|
+
gl.bindVertexArray(null);
|
|
112
129
|
}
|
|
113
|
-
function createWebGLVectorRenderer(gl) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
gl_Position = u_mvp * vec4(a_position, 1.0);
|
|
119
|
-
}`;
|
|
120
|
-
const interiorFragmentShader = `#version 300 es
|
|
121
|
-
precision highp float;
|
|
122
|
-
out vec4 outColor;
|
|
123
|
-
void main() {
|
|
124
|
-
outColor = vec4(1.0);
|
|
125
|
-
}`;
|
|
126
|
-
const curveVertexShader = `#version 300 es
|
|
127
|
-
layout(location = 0) in vec3 a_position;
|
|
128
|
-
uniform mat4 u_mvp;
|
|
129
|
-
out vec2 v_uv;
|
|
130
|
-
void main() {
|
|
131
|
-
int localVertex = gl_VertexID % 3;
|
|
132
|
-
float u = float(localVertex) * 0.5;
|
|
133
|
-
v_uv = vec2(u, floor(u));
|
|
134
|
-
gl_Position = u_mvp * vec4(a_position, 1.0);
|
|
135
|
-
}`;
|
|
136
|
-
const curveFragmentShader = `#version 300 es
|
|
137
|
-
precision highp float;
|
|
138
|
-
in vec2 v_uv;
|
|
139
|
-
out vec4 outColor;
|
|
140
|
-
void main() {
|
|
141
|
-
vec2 px = dFdx(v_uv);
|
|
142
|
-
vec2 py = dFdy(v_uv);
|
|
143
|
-
float fx = 2.0 * v_uv.x * px.x - px.y;
|
|
144
|
-
float fy = 2.0 * v_uv.x * py.x - py.y;
|
|
145
|
-
float denom = sqrt(fx * fx + fy * fy);
|
|
146
|
-
if (denom < 1e-6) {
|
|
147
|
-
discard;
|
|
148
|
-
}
|
|
149
|
-
float sd = (v_uv.x * v_uv.x - v_uv.y) / denom;
|
|
150
|
-
float alpha = clamp(0.5 - sd, 0.0, 1.0);
|
|
151
|
-
if (alpha <= 0.0) {
|
|
152
|
-
discard;
|
|
153
|
-
}
|
|
154
|
-
outColor = vec4(1.0, 1.0, 1.0, alpha);
|
|
155
|
-
}`;
|
|
156
|
-
const colorVertexShader = interiorVertexShader;
|
|
157
|
-
const colorFragmentShader = `#version 300 es
|
|
158
|
-
precision highp float;
|
|
159
|
-
uniform vec4 u_color;
|
|
160
|
-
out vec4 outColor;
|
|
161
|
-
void main() {
|
|
162
|
-
outColor = u_color;
|
|
163
|
-
}`;
|
|
164
|
-
const interiorProgram = createProgramWithMvp(gl, interiorVertexShader, interiorFragmentShader);
|
|
165
|
-
const curveProgram = createProgramWithMvp(gl, curveVertexShader, curveFragmentShader);
|
|
166
|
-
const colorProgram = createColorProgram(gl, colorVertexShader, colorFragmentShader);
|
|
167
|
-
let geometryResources = null;
|
|
130
|
+
function createWebGLVectorRenderer(gl, options) {
|
|
131
|
+
let resources = null;
|
|
132
|
+
const fragSrc = options?.adaptiveSupersampling
|
|
133
|
+
? '#define SLUG_ADAPTIVE_SUPERSAMPLE\n' + fragmentShaderGLSL300
|
|
134
|
+
: fragmentShaderGLSL300;
|
|
168
135
|
return {
|
|
169
136
|
setGeometry(data) {
|
|
170
|
-
if (
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
geometryResources = createGeometryResources(gl, data);
|
|
137
|
+
if (resources)
|
|
138
|
+
this.dispose();
|
|
139
|
+
resources = createResources(gl, data, fragSrc);
|
|
174
140
|
},
|
|
175
|
-
render(mvp,
|
|
176
|
-
if (!
|
|
141
|
+
render(mvp, _color) {
|
|
142
|
+
if (!resources)
|
|
177
143
|
return;
|
|
178
|
-
|
|
179
|
-
gl.disable(gl.CULL_FACE);
|
|
180
|
-
gl.disable(gl.DEPTH_TEST);
|
|
181
|
-
gl.depthMask(false);
|
|
182
|
-
// No stencil clear needed - the fill pass resets stencil to 0
|
|
183
|
-
// via passOp ZERO wherever stencil was non-zero, and per-glyph
|
|
184
|
-
// fill quads cover all stencil writes from interior/curve passes
|
|
185
|
-
gl.enable(gl.STENCIL_TEST);
|
|
186
|
-
gl.stencilMask(0xff);
|
|
187
|
-
gl.stencilFunc(gl.ALWAYS, 0, 0xff);
|
|
188
|
-
// Nonzero winding: front faces increment, back faces decrement
|
|
189
|
-
gl.stencilOpSeparate(gl.FRONT, gl.KEEP, gl.KEEP, gl.INCR_WRAP);
|
|
190
|
-
gl.stencilOpSeparate(gl.BACK, gl.KEEP, gl.KEEP, gl.DECR_WRAP);
|
|
191
|
-
gl.colorMask(false, false, false, false);
|
|
192
|
-
if (geometryResources.interiorIndexCount > 0) {
|
|
193
|
-
gl.useProgram(interiorProgram.program);
|
|
194
|
-
gl.uniformMatrix4fv(interiorProgram.mvp, false, mvp);
|
|
195
|
-
gl.bindVertexArray(geometryResources.interiorVAO);
|
|
196
|
-
gl.drawElements(gl.TRIANGLES, geometryResources.interiorIndexCount, gl.UNSIGNED_INT, 0);
|
|
197
|
-
}
|
|
198
|
-
if (geometryResources.curveVertexCount > 0) {
|
|
199
|
-
gl.enable(gl.SAMPLE_ALPHA_TO_COVERAGE);
|
|
200
|
-
gl.useProgram(curveProgram.program);
|
|
201
|
-
gl.uniformMatrix4fv(curveProgram.mvp, false, mvp);
|
|
202
|
-
gl.bindVertexArray(geometryResources.curveVAO);
|
|
203
|
-
gl.drawArrays(gl.TRIANGLES, 0, geometryResources.curveVertexCount);
|
|
204
|
-
gl.disable(gl.SAMPLE_ALPHA_TO_COVERAGE);
|
|
205
|
-
}
|
|
206
|
-
gl.stencilFunc(gl.NOTEQUAL, 0, 0xff);
|
|
207
|
-
gl.stencilOp(gl.KEEP, gl.KEEP, gl.ZERO);
|
|
208
|
-
gl.colorMask(true, true, true, true);
|
|
209
|
-
gl.useProgram(colorProgram.program);
|
|
210
|
-
gl.uniformMatrix4fv(colorProgram.mvp, false, mvp);
|
|
211
|
-
gl.uniform4fv(colorProgram.color, color);
|
|
212
|
-
gl.bindVertexArray(geometryResources.fillVAO);
|
|
213
|
-
gl.drawElements(gl.TRIANGLES, geometryResources.fillIndexCount, gl.UNSIGNED_INT, 0);
|
|
214
|
-
gl.bindVertexArray(null);
|
|
215
|
-
gl.useProgram(null);
|
|
216
|
-
gl.disable(gl.STENCIL_TEST);
|
|
217
|
-
gl.depthMask(true);
|
|
144
|
+
draw(gl, resources, mvp, gl.drawingBufferWidth, gl.drawingBufferHeight);
|
|
218
145
|
},
|
|
219
146
|
dispose() {
|
|
220
|
-
if (
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
147
|
+
if (!resources)
|
|
148
|
+
return;
|
|
149
|
+
const gl2 = gl;
|
|
150
|
+
gl2.deleteTexture(resources.curveTexture);
|
|
151
|
+
gl2.deleteTexture(resources.bandTexture);
|
|
152
|
+
gl2.deleteBuffer(resources.vbo);
|
|
153
|
+
gl2.deleteBuffer(resources.ibo);
|
|
154
|
+
gl2.deleteVertexArray(resources.vao);
|
|
155
|
+
gl2.deleteProgram(resources.program);
|
|
156
|
+
resources = null;
|
|
227
157
|
}
|
|
228
158
|
};
|
|
229
159
|
}
|