11import { NodeTypeCatalog as NODE } from 'tinyest' ;
22import type { Expression , Return } from 'tinyest' ;
3- import { tgpu , d , type ShaderStage } from 'typegpu' ;
3+ import { tgpu , d , type ShaderStage , std } from 'typegpu' ;
44import { abstractInt , getName , snip , UnknownData , WgslGenerator } from 'typegpu/~internal' ;
55import type {
66 ResolutionCtx ,
@@ -10,6 +10,7 @@ import type {
1010 Origin ,
1111 Snippet ,
1212 ResolvedSnippet ,
13+ BinaryOperator ,
1314} from 'typegpu/~internal' ;
1415
1516// ----------
@@ -175,6 +176,28 @@ export function getCrossShaderStageState(ctx: ResolutionCtx) {
175176 return state ;
176177}
177178
179+ function isF32VecfSchema (
180+ schema : d . BaseData | UnknownData ,
181+ ) : schema is d . F32 | d . Vec2f | d . Vec3f | d . Vec4f {
182+ return (
183+ typeof schema !== 'symbol' &&
184+ ( schema . type === 'f32' ||
185+ schema . type === 'vec2f' ||
186+ schema . type === 'vec3f' ||
187+ schema . type === 'vec4f' )
188+ ) ;
189+ }
190+
191+ const HELPERS = {
192+ // TODO(#2821): Make signature more accurate when std.sign and std.abs
193+ // accept a wider union
194+ remainder : ( x : number , y : number ) : number => {
195+ 'use gpu' ;
196+ const truncDiv = std . sign ( x / y ) * std . floor ( std . abs ( x / y ) ) ;
197+ return x - y * truncDiv ;
198+ } ,
199+ } ;
200+
178201/**
179202 * A GLSL ES 3.0 shader generator that extends WgslGenerator.
180203 * Overrides `dataType` to emit GLSL type names instead of WGSL ones,
@@ -247,7 +270,7 @@ export class GlslGenerator extends WgslGenerator {
247270 return snip ( options . id , options . dataType , options . scope ) ;
248271 }
249272
250- override typeAnnotation ( data : d . BaseData ) : string {
273+ override emitTypeAnnotation ( data : d . BaseData ) : string {
251274 if ( ! d . isLooseData ( data ) ) {
252275 const glslName = WGSL_TO_GLSL_TYPE [ data . type ] ;
253276 if ( glslName !== undefined ) {
@@ -257,17 +280,17 @@ export class GlslGenerator extends WgslGenerator {
257280
258281 if ( d . isWgslArray ( data ) ) {
259282 // The array size suffix is handled elsewhere
260- return this . typeAnnotation ( data . elementType ) ;
283+ return this . emitTypeAnnotation ( data . elementType ) ;
261284 }
262285
263286 if ( d . isWgslStruct ( data ) ) {
264287 return resolveStruct ( this . ctx , data ) ;
265288 }
266289
267- return super . typeAnnotation ( data ) ;
290+ return super . emitTypeAnnotation ( data ) ;
268291 }
269292
270- override call (
293+ override emitCall (
271294 name : string ,
272295 templateParams : readonly Snippet [ ] ,
273296 args : readonly Snippet [ ] ,
@@ -291,16 +314,16 @@ export class GlslGenerator extends WgslGenerator {
291314 : sourceSchema ;
292315
293316 if ( sourcePrimitive . type === 'u32' && targetPrimitive . type === 'f32' ) {
294- return super . call ( 'uintBitsToFloat' , [ ] , [ source ] ) ;
317+ return super . emitCall ( 'uintBitsToFloat' , [ ] , [ source ] ) ;
295318 }
296319 if ( sourcePrimitive . type === 'i32' && targetPrimitive . type === 'f32' ) {
297- return super . call ( 'intBitsToFloat' , [ ] , [ source ] ) ;
320+ return super . emitCall ( 'intBitsToFloat' , [ ] , [ source ] ) ;
298321 }
299322 if ( sourcePrimitive . type === 'f32' && targetPrimitive . type === 'u32' ) {
300- return super . call ( 'floatBitsToUint' , [ ] , [ source ] ) ;
323+ return super . emitCall ( 'floatBitsToUint' , [ ] , [ source ] ) ;
301324 }
302325 if ( sourcePrimitive . type === 'f32' && targetPrimitive . type === 'i32' ) {
303- return super . call ( 'floatBitsToInt' , [ ] , [ source ] ) ;
326+ return super . emitCall ( 'floatBitsToInt' , [ ] , [ source ] ) ;
304327 }
305328 if ( sourceSchema . type === targetSchema . type ) {
306329 return this . ctx . resolveSnippet ( source ) . value ;
@@ -317,9 +340,9 @@ export class GlslGenerator extends WgslGenerator {
317340
318341 if ( falsy . dataType !== UnknownData && falsy . dataType . type . startsWith ( 'vec' ) ) {
319342 if ( cond . dataType !== UnknownData && cond . dataType . type . startsWith ( 'vec' ) ) {
320- return super . call ( 'mix' , templateParams , args ) ;
343+ return super . emitCall ( 'mix' , templateParams , args ) ;
321344 }
322- return super . call ( 'mix' , templateParams , [
345+ return super . emitCall ( 'mix' , templateParams , [
323346 falsy ,
324347 truthy ,
325348 this . typeInstantiation ( correspondingBooleanVectorSchema ( falsy . dataType ) , [ cond ] ) ,
@@ -334,10 +357,14 @@ export class GlslGenerator extends WgslGenerator {
334357 if ( ! arg ) {
335358 throw new Error ( `Invalid number of arguments for 'saturate'` ) ;
336359 }
337- return super . call ( 'clamp' , [ ] , [ arg , snip ( 0 , d . f32 , 'constant' ) , snip ( 1 , d . f32 , 'constant' ) ] ) ;
360+ return super . emitCall (
361+ 'clamp' ,
362+ [ ] ,
363+ [ arg , snip ( 0 , d . f32 , 'constant' ) , snip ( 1 , d . f32 , 'constant' ) ] ,
364+ ) ;
338365 }
339366
340- return super . call ( name , templateParams , args ) ;
367+ return super . emitCall ( name , templateParams , args ) ;
341368 }
342369
343370 override typeInstantiation ( schema : d . BaseData , args : Snippet [ ] ) : ResolvedSnippet {
@@ -393,6 +420,22 @@ export class GlslGenerator extends WgslGenerator {
393420 return `${ this . ctx . pre } ${ glslTypeName } ${ name } ${ resolveArraySizeSuffix ( this . ctx , dataType ) } = ${ rhsStr } ;` ;
394421 }
395422
423+ override emitBinaryOp ( lhs : Snippet , op : BinaryOperator , rhs : Snippet ) : string {
424+ if ( op === '%' && ( isF32VecfSchema ( lhs . dataType ) || isF32VecfSchema ( rhs . dataType ) ) ) {
425+ const result = this . _callShellless ( HELPERS . remainder , [ lhs , rhs ] ) ;
426+ if ( ! result ) {
427+ const lhsStr = this . ctx . resolveSnippet ( lhs ) . value ;
428+ const rhsStr = this . ctx . resolveSnippet ( rhs ) . value ;
429+ throw new Error (
430+ `[@typegpu/gl] Invalid use of '%', incompatible with the GLSL generator: ${ lhsStr } (type: ${ String ( lhs . dataType ) } ) ${ op } ${ rhsStr } (type: ${ String ( rhs . dataType ) } )` ,
431+ ) ;
432+ }
433+ return result . value ;
434+ }
435+
436+ return super . emitBinaryOp ( lhs , op , rhs ) ;
437+ }
438+
396439 /**
397440 * GLSL has no pointers, so `const x = <alias>;` cannot be turned into an implicit
398441 * pointer definition like it is in WGSL. Instead:
0 commit comments