Skip to content

Commit b6680de

Browse files
committed
fix: move more logic to idiomatic TypeGPU
1 parent d62b536 commit b6680de

11 files changed

Lines changed: 372 additions & 281 deletions

File tree

‎packages/text/package.json‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,8 @@
9595
"devDependencies": {
9696
"@react-three/fiber": "10.0.0-alpha.2",
9797
"@react-three/test-renderer": "9.1.0",
98-
"@typegpu/gl": "https://pkg.pr.new/software-mansion/TypeGPU/@typegpu/gl@c98cc339b6730d5857caa51a6152618b283fe7a2",
99-
"@typegpu/three": "https://pkg.pr.new/software-mansion/TypeGPU/@typegpu/three@c98cc339b6730d5857caa51a6152618b283fe7a2",
98+
"@typegpu/gl": "^0.12.0",
99+
"@typegpu/three": "https://pkg.pr.new/software-mansion/TypeGPU/@typegpu/three@d6f3c7d9d0dbc79739cf2175e7d3e5e7eb36a6ad",
100100
"@types/node": "24.13.3",
101101
"@types/react": "19.2.14",
102102
"@types/three": "0.185.1",
@@ -108,17 +108,17 @@
108108
"react": "19.2.8",
109109
"three": "0.185.1",
110110
"tsdown": "0.22.14",
111-
"typegpu": "https://pkg.pr.new/software-mansion/TypeGPU/typegpu@c98cc339b6730d5857caa51a6152618b283fe7a2",
111+
"typegpu": "https://pkg.pr.new/software-mansion/TypeGPU/typegpu@9baee4027197723e8720fef5a1d207dcff167cff",
112112
"unicode-property-value-aliases": "3.9.0",
113-
"unplugin-typegpu": "0.11.6"
113+
"unplugin-typegpu": "^0.12.1"
114114
},
115115
"peerDependencies": {
116116
"@react-three/fiber": ">=10.0.0-alpha.2 <11",
117-
"@typegpu/gl": ">=0.11.0-alpha.2 <0.12",
118-
"@typegpu/three": ">=0.11.0 <0.12",
117+
"@typegpu/gl": ">=0.12.0",
118+
"@typegpu/three": ">=0.12.0",
119119
"react": ">=19 <19.3",
120120
"three": ">=0.185.1",
121-
"typegpu": ">=0.11.9 <0.12"
121+
"typegpu": ">=0.12.0"
122122
},
123123
"peerDependenciesMeta": {
124124
"@react-three/fiber": {

‎packages/text/src/internal/slug-shaders/core/band.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export function slugPixelsPerEm(renderCoordinate: d.v2f): d.v3f {
3434
export function slugThickenFactor(thicken: number, pixelsPerEm: number): number {
3535
'use gpu';
3636

37-
return 1 + thicken * std.max(d.f32(0), 1 - pixelsPerEm / 24);
37+
return 1 + thicken * std.max(d.f32(0), 1 - d.f32(pixelsPerEm) / 24);
3838
}
3939

4040
/** Clamp a glyph-em coordinate into its axis band grid through the glyph's band transform. */
@@ -48,7 +48,7 @@ export function slugBandIndex(coordinate: number, scale: number, offset: number,
4848
export function slugBandCurveCount(header: number): number {
4949
'use gpu';
5050

51-
return std.min(header >> HEADER_COUNT_SHIFT, d.u32(MAX_SAFE_SLUG_BAND_CURVES));
51+
return std.min(header >>> HEADER_COUNT_SHIFT, d.u32(MAX_SAFE_SLUG_BAND_CURVES));
5252
}
5353

5454
/** Glyph-local reference offset of a V0 `(count << 16) | offset` band header. */
@@ -62,7 +62,7 @@ export function slugBandReferenceOffset(header: number): number {
6262
export function slugReferenceFromPair(pair: number, referenceIndex: number): number {
6363
'use gpu';
6464

65-
return (pair >> ((referenceIndex & d.u32(1)) * 16)) & d.u32(HEADER_REFERENCE_MASK);
65+
return (pair >>> ((referenceIndex & d.u32(1)) * 16)) & d.u32(HEADER_REFERENCE_MASK);
6666
}
6767

6868
/**

‎packages/text/src/internal/slug-shaders/core/coverage.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export function calcCoverage(
2626
const evenOddCoverage = 1 - std.abs(1 - std.fract(rawCoverage * 0.5) * 2);
2727
const filledCoverage = std.select(std.saturate(rawCoverage), evenOddCoverage, evenOdd);
2828
const boostedCoverage = std.select(filledCoverage, std.sqrt(filledCoverage), weightBoost);
29-
const darken = stemDarken * std.max(d.f32(0), 1 - pixelsPerEm / 24);
29+
const darken = stemDarken * std.max(d.f32(0), 1 - d.f32(pixelsPerEm) / 24);
3030

3131
return std.min(boostedCoverage + darken * boostedCoverage * (1 - boostedCoverage), 1);
3232
}

‎packages/text/src/internal/slug-shaders/core/root-code.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@ export function calcRootCode(y1: number, y2: number, y3: number): number {
1616
const s3 = std.select(d.u32(0), d.u32(1), y3 < 0);
1717
const shift = s1 | (s2 << 1) | (s3 << 2);
1818

19-
return (d.u32(0x2e74) >> shift) & d.u32(0x0101);
19+
return (d.u32(0x2e74) >>> shift) & d.u32(0x0101);
2020
}

‎packages/text/src/internal/slug-shaders/index.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export {
2323
MAX_SAFE_SLUG_BAND_CURVES,
2424
slugRender,
2525
type SlugRenderOptions,
26-
type SlugShaderGlyph,
26+
SlugShaderGlyph,
2727
type SlugShaderPage,
2828
} from './slug-render.js';
29+
export { pageSlot } from './slug-texture.js';

‎packages/text/src/internal/slug-shaders/slug-band.ts‎

Lines changed: 143 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,42 @@
55
* bound to Three.js control flow and to this page's texture layout. Each candidate
66
* curve's coverage, weight, and terminator come from one portable core call.
77
*/
8-
import type { Node } from 'three/webgpu';
9-
import { If, float, int, lessThan, max, uint } from 'three/tsl';
10-
import { d } from 'typegpu';
11-
import * as t3 from '@typegpu/three';
12-
import { coreValue } from './core-boundary.js';
8+
import tgpu, { d, std, type TgpuMutable } from 'typegpu';
139
import {
1410
slugBandCurveCount,
1511
slugBandIndex,
1612
slugBandReferenceOffset,
1713
slugHorizontalCurveContribution,
1814
slugVerticalCurveContribution,
1915
} from './core/band.js';
20-
import { loadCurve, loadHeader, loadReference, type SlugShaderCurve, type SlugShaderPage } from './slug-texture.js';
21-
import { intLessThan, uintAdd, whileLoop } from './tsl-compat.js';
22-
23-
export interface SlugShaderGlyph {
24-
readonly curveBaseTexel: Node<'uint'>;
25-
readonly horizontalHeaderBase: Node<'uint'>;
26-
readonly verticalHeaderBase: Node<'uint'>;
27-
readonly referenceBase: Node<'uint'>;
28-
readonly horizontalBandCount: Node<'uint'>;
29-
readonly verticalBandCount: Node<'uint'>;
30-
readonly bandTransform: Node<'vec4'>;
31-
}
16+
import { loadCurve, loadHeader, loadReference, type SlugShaderCurve } from './slug-texture.js';
3217

33-
interface SlugBandEvaluation {
34-
readonly coverage: Node<'float'>;
35-
readonly weight: Node<'float'>;
36-
}
18+
export const SlugShaderGlyph: d.WgslStruct<{
19+
curveBaseTexel: d.U32;
20+
horizontalHeaderBase: d.U32;
21+
verticalHeaderBase: d.U32;
22+
referenceBase: d.U32;
23+
horizontalBandCount: d.U32;
24+
verticalBandCount: d.U32;
25+
bandTransform: d.Vec4f;
26+
}> = d.struct({
27+
curveBaseTexel: d.u32,
28+
horizontalHeaderBase: d.u32,
29+
verticalHeaderBase: d.u32,
30+
referenceBase: d.u32,
31+
horizontalBandCount: d.u32,
32+
verticalBandCount: d.u32,
33+
bandTransform: d.vec4f,
34+
});
35+
36+
export type SlugShaderGlyph = d.InferGPU<typeof SlugShaderGlyph>;
37+
38+
export const SlugBandEvaluation: d.WgslStruct<{ coverage: d.F32; weight: d.F32 }> = d.struct({
39+
coverage: d.f32,
40+
weight: d.f32,
41+
});
42+
43+
type SlugBandEvaluation = d.InferGPU<typeof SlugBandEvaluation>;
3744

3845
/** Signed coverage delta, antialiasing weight, and sorted-reference terminator of one curve. */
3946
type SlugCurveContribution = (
@@ -45,116 +52,141 @@ type SlugCurveContribution = (
4552
thickenFactor: number,
4653
) => d.v3f;
4754

48-
function curveContribution(
49-
contribute: SlugCurveContribution,
50-
name: string,
51-
curve: SlugShaderCurve,
52-
renderCoordinate: Node<'vec2'>,
53-
pixelsPerEm: Node<'float'>,
54-
thickenFactor: Node<'float'>,
55-
): Node<'vec3'> {
56-
return coreValue('vec3', name, () => {
55+
const contributeSlot = tgpu.slot<SlugCurveContribution>();
56+
57+
/**
58+
* @note Uses `contributeSlot`
59+
*/
60+
const curveContribution = tgpu.fn(
61+
(
62+
curve: SlugShaderCurve,
63+
renderCoordinate: d.v2f,
64+
pixelsPerEm: /* f32 */ number,
65+
thickenFactor: /* f32 */ number,
66+
): d.v3f => {
5767
'use gpu';
58-
return contribute(
59-
t3.fromTSL(curve.p0, d.vec2f).$,
60-
t3.fromTSL(curve.p1, d.vec2f).$,
61-
t3.fromTSL(curve.p2, d.vec2f).$,
62-
t3.fromTSL(renderCoordinate, d.vec2f).$,
63-
t3.fromTSL(pixelsPerEm, d.f32).$,
64-
t3.fromTSL(thickenFactor, d.f32).$,
65-
);
66-
});
67-
}
68+
return contributeSlot.$(curve.p0, curve.p1, curve.p2, renderCoordinate, pixelsPerEm, thickenFactor);
69+
},
70+
);
6871

72+
/**
73+
* @note Uses `contributeSlot`
74+
* @note Uses `pageSlot`
75+
*/
6976
function evaluateBandCurve(
70-
index: Node<'int'>,
71-
curveCount: Node<'int'>,
72-
page: SlugShaderPage,
77+
index: /* u32 */ d.ref<number>,
78+
curveCount: /* u32 */ number,
7379
glyph: SlugShaderGlyph,
74-
renderCoordinate: Node<'vec2'>,
75-
axis: 'horizontal' | 'vertical',
76-
pixelsPerEm: Node<'float'>,
77-
thickenFactor: Node<'float'>,
78-
localReferenceOffset: Node<'uint'>,
79-
coverage: Node<'float'>,
80-
weight: Node<'float'>,
80+
renderCoordinate: d.v2f,
81+
pixelsPerEm: /* f32 */ number,
82+
thickenFactor: /* f32 */ number,
83+
localReferenceOffset: /* u32 */ number,
84+
coverage: /* f32 */ d.ref<number>,
85+
weight: /* f32 */ d.ref<number>,
8186
): void {
82-
const referenceIndex: Node<'uint'> = uintAdd(uintAdd(glyph.referenceBase, localReferenceOffset), uint(index));
83-
const curveReference: Node<'uint'> = loadReference(page, referenceIndex);
84-
const curve = loadCurve(page, uintAdd(glyph.curveBaseTexel, curveReference));
85-
const namePrefix = axis === 'horizontal' ? 'slugHorizontal' : 'slugVertical';
86-
const contribution: Node<'vec3'> = curveContribution(
87-
axis === 'horizontal' ? slugHorizontalCurveContribution : slugVerticalCurveContribution,
88-
`${namePrefix}Contribution`,
89-
curve,
90-
renderCoordinate,
91-
pixelsPerEm,
92-
thickenFactor,
93-
);
87+
'use gpu';
88+
const referenceIndex = /* u32 */ d.u32(glyph.referenceBase + localReferenceOffset + index.$);
89+
const curveReference = /* u32 */ loadReference(referenceIndex);
90+
const curve = loadCurve(glyph.curveBaseTexel + curveReference);
91+
const contribution = curveContribution(curve, renderCoordinate, pixelsPerEm, thickenFactor);
9492

9593
// The references of a band are sorted by descending ray-axis maximum, so the
9694
// first curve that cannot reach this fragment ends the band.
97-
If(lessThan(contribution.z, -0.5), () => {
98-
index.assign(curveCount);
99-
}).Else(() => {
100-
coverage.addAssign(contribution.x);
101-
weight.assign(max(weight, contribution.y));
102-
index.addAssign(1);
103-
});
95+
if (contribution.z < -0.5) {
96+
index.$ = curveCount;
97+
} else {
98+
coverage.$ += contribution.x;
99+
weight.$ = std.max(weight.$, contribution.y);
100+
index.$++;
101+
}
104102
}
105103

106-
export function evaluateBand(
107-
page: SlugShaderPage,
104+
const getEvaluateBandCurve = tgpu.comptime((axis: 'vertical' | 'horizontal') =>
105+
tgpu
106+
.fn(evaluateBandCurve)
107+
.with(contributeSlot, axis === 'horizontal' ? slugHorizontalCurveContribution : slugVerticalCurveContribution),
108+
);
109+
110+
const axisSlot = tgpu.slot<'vertical' | 'horizontal'>();
111+
112+
const tempSlot = tgpu.slot<null | { mutable: TgpuMutable<d.U32> }>(null);
113+
114+
/**
115+
* @note Uses `pageSlot`
116+
*/
117+
function genericEvaluateBand(
108118
glyph: SlugShaderGlyph,
109-
renderCoordinate: Node<'vec2'>,
110-
axis: 'horizontal' | 'vertical',
111-
pixelsPerEm: Node<'float'>,
112-
thickenFactor: Node<'float'>,
119+
renderCoordinate: d.v2f,
120+
pixelsPerEm: /* f32 */ number,
121+
thickenFactor: /* f32 */ number,
113122
): SlugBandEvaluation {
114-
const namePrefix = axis === 'horizontal' ? 'slugHorizontal' : 'slugVertical';
115-
const coordinate: Node<'float'> = axis === 'horizontal' ? renderCoordinate.y : renderCoordinate.x;
116-
const transformScale: Node<'float'> = axis === 'horizontal' ? glyph.bandTransform.y : glyph.bandTransform.x;
117-
const transformOffset: Node<'float'> = axis === 'horizontal' ? glyph.bandTransform.w : glyph.bandTransform.z;
118-
const declaredBandCount: Node<'uint'> = axis === 'horizontal' ? glyph.horizontalBandCount : glyph.verticalBandCount;
119-
const headerBase: Node<'uint'> = axis === 'horizontal' ? glyph.horizontalHeaderBase : glyph.verticalHeaderBase;
120-
const bandIndex: Node<'uint'> = coreValue('uint', `${namePrefix}BandIndex`, () => {
121-
'use gpu';
122-
return slugBandIndex(
123-
t3.fromTSL(coordinate, d.f32).$,
124-
t3.fromTSL(transformScale, d.f32).$,
125-
t3.fromTSL(transformOffset, d.f32).$,
126-
t3.fromTSL(declaredBandCount, d.u32).$,
127-
);
128-
});
129-
const header: Node<'uint'> = loadHeader(page, uintAdd(headerBase, bandIndex), axis);
130-
const localReferenceOffset: Node<'uint'> = coreValue('uint', `${namePrefix}ReferenceOffset`, () => {
131-
'use gpu';
132-
return slugBandReferenceOffset(t3.fromTSL(header, d.u32).$);
133-
});
134-
const curveCount: Node<'int'> = int(
135-
coreValue('uint', `${namePrefix}CurveCount`, () => {
136-
'use gpu';
137-
return slugBandCurveCount(t3.fromTSL(header, d.u32).$);
138-
}),
139-
);
140-
const coverage: Node<'float'> = float(0).toVar(axis === 'horizontal' ? 'slugXCoverage' : 'slugYCoverage');
141-
const weight: Node<'float'> = float(0).toVar(axis === 'horizontal' ? 'slugXWeight' : 'slugYWeight');
142-
const curveIndex: Node<'int'> = int(0).toVar(`${namePrefix}CurveIndex`);
143-
whileLoop(intLessThan(curveIndex, curveCount), () =>
144-
evaluateBandCurve(
123+
'use gpu';
124+
const coordinate: /* f32 */ number = axisSlot.$ === 'horizontal' ? renderCoordinate.y : renderCoordinate.x;
125+
const transformScale: /* f32 */ number = axisSlot.$ === 'horizontal' ? glyph.bandTransform.y : glyph.bandTransform.x;
126+
const transformOffset: /* f32 */ number = axisSlot.$ === 'horizontal' ? glyph.bandTransform.w : glyph.bandTransform.z;
127+
const declaredBandCount /* u32 */ = axisSlot.$ === 'horizontal' ? glyph.horizontalBandCount : glyph.verticalBandCount;
128+
const headerBase /* u32 */ = axisSlot.$ === 'horizontal' ? glyph.horizontalHeaderBase : glyph.verticalHeaderBase;
129+
const bandIndex /* u32 */ = slugBandIndex(coordinate, transformScale, transformOffset, declaredBandCount);
130+
const header /* u32 */ = loadHeader(headerBase + bandIndex);
131+
const localReferenceOffset /* u32 */ = slugBandReferenceOffset(header);
132+
const curveCount = slugBandCurveCount(header);
133+
134+
if (!!tempSlot.$) {
135+
tempSlot.$.mutable.$ = curveCount;
136+
}
137+
138+
const coverage /* f32 */ = d.ref(d.f32());
139+
const weight /* f32 */ = d.ref(d.f32());
140+
const curveIndex /* u32 */ = d.ref(d.u32(0));
141+
142+
while (curveIndex.$ < curveCount) {
143+
getEvaluateBandCurve(axisSlot.$)(
145144
curveIndex,
146145
curveCount,
147-
page,
148146
glyph,
149147
renderCoordinate,
150-
axis,
151148
pixelsPerEm,
152149
thickenFactor,
153150
localReferenceOffset,
154151
coverage,
155152
weight,
156-
),
157-
);
153+
);
154+
}
158155

159-
return { coverage, weight };
156+
return SlugBandEvaluation({ coverage: coverage.$, weight: weight.$ });
160157
}
158+
159+
export const evaluateBand: (axis: 'vertical' | 'horizontal') => typeof genericEvaluateBand = tgpu.comptime(
160+
(axis: 'vertical' | 'horizontal'): typeof genericEvaluateBand => tgpu.fn(genericEvaluateBand).with(axisSlot, axis),
161+
);
162+
163+
(async () => {
164+
// const root = await tgpu.init();
165+
// const mutable = root.createMutable(d.u32);
166+
// const pipeline = root
167+
// .with(tempSlot, { mutable })
168+
// .with(pageSlot, {
169+
// curveWidth: 1,
170+
// headerWidth: 1,
171+
// referenceWidth: 1,
172+
// loadCurve: () => {
173+
// 'use gpu';
174+
// return d.vec4f();
175+
// },
176+
// loadHeader: () => {
177+
// 'use gpu';
178+
// return d.vec4u();
179+
// },
180+
// loadReference: () => {
181+
// 'use gpu';
182+
// return d.vec4u();
183+
// },
184+
// })
185+
// .createGuardedComputePipeline(() => {
186+
// 'use gpu';
187+
// const value = evaluateBand('vertical')(SlugShaderGlyph(), d.vec2f(0.5, 0.5), 1.1, 1.1);
188+
// });
189+
// pipeline.dispatchThreads();
190+
// console.log(tgpu.resolve([pipeline.pipeline]));
191+
// console.log(await mutable.read());
192+
})();

0 commit comments

Comments
 (0)