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' ;
139import {
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. */
3946type 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+ */
6976function 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