p5 2.2.3 → 2.3.0-rc.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.
Files changed (137) hide show
  1. package/dist/accessibility/color_namer.js +9 -11
  2. package/dist/accessibility/describe.js +0 -1
  3. package/dist/accessibility/gridOutput.js +0 -1
  4. package/dist/accessibility/index.js +9 -10
  5. package/dist/accessibility/outputs.js +0 -1
  6. package/dist/accessibility/textOutput.js +0 -1
  7. package/dist/app.js +11 -10
  8. package/dist/app.node.js +122 -0
  9. package/dist/color/color_conversion.js +9 -11
  10. package/dist/color/creating_reading.js +1 -1
  11. package/dist/color/index.js +2 -2
  12. package/dist/color/p5.Color.js +1 -1
  13. package/dist/color/setting.js +25 -12
  14. package/dist/{constants-BdTiYOQI.js → constants-CYF6mp5_.js} +2 -2
  15. package/dist/core/States.js +1 -1
  16. package/dist/core/constants.js +1 -1
  17. package/dist/core/environment.js +28 -29
  18. package/dist/core/filterShaders.js +1 -1
  19. package/dist/core/friendly_errors/fes_core.js +9 -8
  20. package/dist/core/friendly_errors/file_errors.js +1 -2
  21. package/dist/core/friendly_errors/index.js +1 -1
  22. package/dist/core/friendly_errors/param_validator.js +737 -640
  23. package/dist/core/friendly_errors/sketch_verifier.js +1 -1
  24. package/dist/core/friendly_errors/stacktrace.js +0 -1
  25. package/dist/core/helpers.js +3 -4
  26. package/dist/core/init.js +24 -21
  27. package/dist/core/internationalization.js +1 -1
  28. package/dist/core/legacy.js +9 -11
  29. package/dist/core/main.js +9 -10
  30. package/dist/core/p5.Graphics.js +5 -5
  31. package/dist/core/p5.Renderer.js +3 -3
  32. package/dist/core/p5.Renderer2D.js +9 -10
  33. package/dist/core/p5.Renderer3D.js +5 -5
  34. package/dist/core/rendering.js +5 -5
  35. package/dist/core/structure.js +0 -1
  36. package/dist/core/transform.js +7 -16
  37. package/dist/{creating_reading-C7hu6sg1.js → creating_reading-DLkHH80h.js} +11 -8
  38. package/dist/data/local_storage.js +0 -1
  39. package/dist/dom/dom.js +2 -3
  40. package/dist/dom/index.js +2 -2
  41. package/dist/dom/p5.Element.js +2 -2
  42. package/dist/dom/p5.MediaElement.js +2 -2
  43. package/dist/events/acceleration.js +5 -3
  44. package/dist/events/keyboard.js +0 -1
  45. package/dist/events/pointer.js +0 -2
  46. package/dist/image/const.js +1 -1
  47. package/dist/image/filterRenderer2D.js +19 -12
  48. package/dist/image/image.js +5 -5
  49. package/dist/image/index.js +5 -5
  50. package/dist/image/loading_displaying.js +5 -5
  51. package/dist/image/p5.Image.js +3 -3
  52. package/dist/image/pixels.js +0 -1
  53. package/dist/io/files.js +5 -5
  54. package/dist/io/index.js +5 -5
  55. package/dist/io/p5.Table.js +0 -1
  56. package/dist/io/p5.TableRow.js +0 -1
  57. package/dist/io/p5.XML.js +0 -1
  58. package/dist/{ir_builders-Cd6rU9Vm.js → ir_builders-C2ebb6Lu.js} +234 -1
  59. package/dist/{main-H_nu4eDs.js → main-D2MtO721.js} +107 -136
  60. package/dist/math/Matrices/Matrix.js +1 -1
  61. package/dist/math/Matrices/MatrixNumjs.js +1 -1
  62. package/dist/math/calculation.js +0 -1
  63. package/dist/math/index.js +3 -1
  64. package/dist/math/math.js +3 -17
  65. package/dist/math/noise.js +0 -1
  66. package/dist/math/p5.Matrix.js +1 -2
  67. package/dist/math/p5.Vector.js +233 -279
  68. package/dist/math/patch-vector.js +75 -0
  69. package/dist/math/random.js +0 -1
  70. package/dist/math/trigonometry.js +3 -4
  71. package/dist/{p5.Renderer-BmD2P6Wv.js → p5.Renderer-C0Kzy71d.js} +31 -24
  72. package/dist/{rendering-CC8JNTwG.js → rendering-CvNr0bB8.js} +732 -44
  73. package/dist/shape/2d_primitives.js +1 -4
  74. package/dist/shape/attributes.js +43 -8
  75. package/dist/shape/curves.js +0 -1
  76. package/dist/shape/custom_shapes.js +260 -5
  77. package/dist/shape/index.js +2 -2
  78. package/dist/shape/vertex.js +0 -2
  79. package/dist/strands/ir_builders.js +1 -1
  80. package/dist/strands/ir_types.js +5 -1
  81. package/dist/strands/p5.strands.js +286 -31
  82. package/dist/strands/strands_api.js +179 -8
  83. package/dist/strands/strands_codegen.js +26 -8
  84. package/dist/strands/strands_conditionals.js +1 -1
  85. package/dist/strands/strands_for.js +1 -1
  86. package/dist/strands/strands_node.js +1 -1
  87. package/dist/strands/strands_ternary.js +56 -0
  88. package/dist/strands/strands_transpiler.js +416 -251
  89. package/dist/strands_glslBackend-i-ReKgZo.js +423 -0
  90. package/dist/type/index.js +3 -3
  91. package/dist/type/lib/Typr.js +1 -1
  92. package/dist/type/p5.Font.js +3 -3
  93. package/dist/type/textCore.js +31 -24
  94. package/dist/utilities/conversion.js +0 -1
  95. package/dist/utilities/time_date.js +0 -1
  96. package/dist/utilities/utility_functions.js +0 -1
  97. package/dist/webgl/3d_primitives.js +5 -5
  98. package/dist/webgl/GeometryBuilder.js +1 -1
  99. package/dist/webgl/ShapeBuilder.js +26 -1
  100. package/dist/webgl/enums.js +1 -1
  101. package/dist/webgl/index.js +8 -9
  102. package/dist/webgl/interaction.js +8 -4
  103. package/dist/webgl/light.js +5 -5
  104. package/dist/webgl/loading.js +60 -21
  105. package/dist/webgl/material.js +5 -5
  106. package/dist/webgl/p5.Camera.js +5 -5
  107. package/dist/webgl/p5.Framebuffer.js +5 -5
  108. package/dist/webgl/p5.Geometry.js +3 -5
  109. package/dist/webgl/p5.Quat.js +1 -1
  110. package/dist/webgl/p5.RendererGL.js +17 -21
  111. package/dist/webgl/p5.Shader.js +129 -36
  112. package/dist/webgl/p5.Texture.js +5 -5
  113. package/dist/webgl/strands_glslBackend.js +5 -386
  114. package/dist/webgl/text.js +5 -5
  115. package/dist/webgl/utils.js +5 -5
  116. package/dist/webgl2Compatibility-DA7DLMuq.js +7 -0
  117. package/dist/webgpu/index.js +7 -3
  118. package/dist/webgpu/p5.RendererWebGPU.js +1036 -180
  119. package/dist/webgpu/shaders/color.js +1 -1
  120. package/dist/webgpu/shaders/compute.js +32 -0
  121. package/dist/webgpu/shaders/functions/randomComputeWGSL.js +31 -0
  122. package/dist/webgpu/shaders/functions/randomVertWGSL.js +30 -0
  123. package/dist/webgpu/shaders/functions/randomWGSL.js +30 -0
  124. package/dist/webgpu/shaders/line.js +1 -1
  125. package/dist/webgpu/shaders/material.js +3 -3
  126. package/dist/webgpu/strands_wgslBackend.js +137 -15
  127. package/lib/p5.esm.js +4088 -1950
  128. package/lib/p5.esm.min.js +1 -1
  129. package/lib/p5.js +4088 -1950
  130. package/lib/p5.min.js +1 -1
  131. package/lib/p5.webgpu.esm.js +1638 -306
  132. package/lib/p5.webgpu.js +1637 -305
  133. package/lib/p5.webgpu.min.js +1 -1
  134. package/package.json +6 -1
  135. package/types/global.d.ts +4137 -2396
  136. package/types/p5.d.ts +2702 -1658
  137. package/dist/noise3DGLSL-Bwrdi4gi.js +0 -9
@@ -1,8 +1,9 @@
1
- import { u as unaryOpNode, c as createStrandsNode, S as StrandsNode, p as primitiveConstructorNode, v as variableNode, b as binaryOpNode, s as statementNode, f as functionCallNode, a as structInstanceNode, d as structConstructorNode } from '../ir_builders-Cd6rU9Vm.js';
1
+ import { u as unaryOpNode, c as createStrandsNode, S as StrandsNode, p as primitiveConstructorNode, v as variableNode, b as binaryOpNode, a as statementNode, f as functionCallNode, d as structInstanceNode, e as structConstructorNode } from '../ir_builders-C2ebb6Lu.js';
2
2
  import { OperatorTable, StatementType, NodeType, DataType, BaseType, BlockType, isStructType, structType } from './ir_types.js';
3
3
  import { strandsBuiltinFunctions } from './strands_builtins.js';
4
4
  import { StrandsConditional } from './strands_conditionals.js';
5
5
  import { StrandsFor } from './strands_for.js';
6
+ import { buildTernary } from './strands_ternary.js';
6
7
  import { createBasicBlock, addEdge, pushBlock, recordInBasicBlock, popBlock } from './ir_cfg.js';
7
8
  import { createNodeData, getOrCreateNode, getNodeDataFromID } from './ir_dag.js';
8
9
  import { userError } from './strands_FES.js';
@@ -181,6 +182,10 @@ function initGlobalStrandsAPI(p5, fn, strandsContext) {
181
182
  return new StrandsFor(strandsContext, initialCb, conditionCb, updateCb, bodyCb, initialVars).build();
182
183
  };
183
184
  augmentFn(fn, p5, 'strandsFor', p5.strandsFor);
185
+ p5.strandsTernary = function(condition, ifTrue, ifFalse) {
186
+ return buildTernary(strandsContext, condition, ifTrue, ifFalse);
187
+ };
188
+ augmentFn(fn, p5, 'strandsTernary', p5.strandsTernary);
184
189
  p5.strandsEarlyReturn = function(value) {
185
190
  const { dag, cfg } = strandsContext;
186
191
 
@@ -201,7 +206,7 @@ function initGlobalStrandsAPI(p5, fn, strandsContext) {
201
206
  const nodeData = createNodeData({
202
207
  nodeType: NodeType.STATEMENT,
203
208
  statementType: StatementType.EARLY_RETURN,
204
- dependsOn: [valueNode.id]
209
+ dependsOn: value !== undefined ? [valueNode.id] : []
205
210
  });
206
211
  const earlyReturnID = getOrCreateNode(dag, nodeData);
207
212
  recordInBasicBlock(cfg, cfg.currentBlock, earlyReturnID);
@@ -266,6 +271,33 @@ function initGlobalStrandsAPI(p5, fn, strandsContext) {
266
271
  }
267
272
  }
268
273
 
274
+ // Alias lerp to GLSL mix in strands context
275
+ const originalLerp = fn.lerp;
276
+ augmentFn(fn, p5, 'lerp', function (...args) {
277
+ if (strandsContext.active) {
278
+ return fn.mix(...args);
279
+ } else {
280
+ return originalLerp.apply(this, args);
281
+ }
282
+ });
283
+
284
+ const originalMap = fn.map;
285
+ augmentFn(fn, p5, 'map', function (...args) {
286
+ if (!strandsContext.active) {
287
+ return originalMap.apply(this, args);
288
+ }
289
+ const [n, start1, stop1, start2, stop2, withinBounds] = args;
290
+ const nNode = p5.strandsNode(n);
291
+ const start1Node = p5.strandsNode(start1);
292
+ const stop1Node = p5.strandsNode(stop1);
293
+ const t = nNode.sub(start1Node).div(stop1Node.sub(start1Node));
294
+ const result = this.mix(start2, stop2, t);
295
+ if (withinBounds) {
296
+ return this.clamp(result, this.min(start2, stop2), this.max(start2, stop2));
297
+ }
298
+ return result;
299
+ });
300
+
269
301
  augmentFn(fn, p5, 'getTexture', function (...rawArgs) {
270
302
  if (strandsContext.active) {
271
303
  const { id, dimension } = strandsContext.backend.createGetTextureCall(strandsContext, rawArgs);
@@ -290,6 +322,8 @@ function initGlobalStrandsAPI(p5, fn, strandsContext) {
290
322
  // Add noise function with backend-agnostic implementation
291
323
  const originalNoise = fn.noise;
292
324
  const originalNoiseDetail = fn.noiseDetail;
325
+ const originalRandom = fn.random;
326
+ const originalRandomSeed = fn.randomSeed;
293
327
  const originalMillis = fn.millis;
294
328
 
295
329
  strandsContext._noiseOctaves = null;
@@ -309,9 +343,10 @@ function initGlobalStrandsAPI(p5, fn, strandsContext) {
309
343
  return originalNoise.apply(this, args); // fallback to regular p5.js noise
310
344
  }
311
345
  // Get noise shader snippet from the current renderer
312
- const noiseSnippet = this._renderer.getNoiseShaderSnippet();
346
+ const noiseSnippet = strandsContext.backend.getNoiseShaderSnippet();
313
347
  strandsContext.vertexDeclarations.add(noiseSnippet);
314
348
  strandsContext.fragmentDeclarations.add(noiseSnippet);
349
+ strandsContext.computeDeclarations.add(noiseSnippet);
315
350
 
316
351
  // Make each input into a strands node so that we can check their dimensions
317
352
  const strandsArgs = args.flat().map(arg => p5.strandsNode(arg));
@@ -353,6 +388,84 @@ function initGlobalStrandsAPI(p5, fn, strandsContext) {
353
388
  return createStrandsNode(id, dimension, strandsContext);
354
389
  });
355
390
 
391
+ strandsContext._randomSeed = null;
392
+
393
+ augmentFn(fn, p5, 'randomSeed', function (seed) {
394
+ if (!strandsContext.active) {
395
+ return originalRandomSeed.apply(this, arguments);
396
+ }
397
+ strandsContext._randomSeed = seed;
398
+ });
399
+
400
+ augmentFn(fn, p5, 'random', function (...args) {
401
+ if (!strandsContext.active) {
402
+ return originalRandom.apply(this, args);
403
+ }
404
+
405
+ const randomVertSnippet = strandsContext.backend.getRandomVertexShaderSnippet();
406
+ const randomFragSnippet = strandsContext.backend.getRandomFragmentShaderSnippet();
407
+
408
+ strandsContext.vertexDeclarations.add(randomVertSnippet);
409
+ strandsContext.fragmentDeclarations.add(randomFragSnippet);
410
+
411
+ if (strandsContext.backend.getRandomComputeShaderSnippet) {
412
+ const randomComputeSnippet = strandsContext.backend.getRandomComputeShaderSnippet();
413
+ strandsContext.computeDeclarations.add(randomComputeSnippet);
414
+ }
415
+
416
+ let seedNode;
417
+ if (strandsContext._randomSeed !== null && strandsContext._randomSeed.isStrandsNode) {
418
+ seedNode = strandsContext._randomSeed;
419
+ } else {
420
+ const userSeed = strandsContext._randomSeed;
421
+ seedNode = getOrCreateUniformNode(
422
+ strandsContext,
423
+ '_p5_randomSeed',
424
+ DataType.float1,
425
+ userSeed !== null
426
+ ? () => userSeed
427
+ : () => performance.now(),
428
+ );
429
+ }
430
+
431
+ // The shader-side random() owns a private per-invocation counter, so a
432
+ // single AST node still produces distinct values across runtime loop
433
+ // iterations. We just pass the seed.
434
+ const nodeArgs = [seedNode];
435
+ const randomOverloads = [{
436
+ params: [DataType.float1],
437
+ returnType: DataType.float1,
438
+ }];
439
+
440
+ if (args.length === 0) {
441
+ const { id, dimension } = functionCallNode(strandsContext, 'random', nodeArgs, {
442
+ overloads: randomOverloads,
443
+ });
444
+ return createStrandsNode(id, dimension, strandsContext);
445
+ } else if (args.length === 1) {
446
+ // random(max) → [0, max)
447
+ const rawNode = functionCallNode(strandsContext, 'random', nodeArgs, {
448
+ overloads: randomOverloads,
449
+ });
450
+ const rawStrandsNode = createStrandsNode(rawNode.id, rawNode.dimension, strandsContext);
451
+ return rawStrandsNode.mult(p5.strandsNode(args[0]));
452
+ } else if (args.length === 2) {
453
+ // random(min, max) → [min, max)
454
+ const rawNode = functionCallNode(strandsContext, 'random', nodeArgs, {
455
+ overloads: randomOverloads,
456
+ });
457
+ const rawStrandsNode = createStrandsNode(rawNode.id, rawNode.dimension, strandsContext);
458
+ const minNode = p5.strandsNode(args[0]);
459
+ const maxNode = p5.strandsNode(args[1]);
460
+ // min + raw * (max - min)
461
+ return rawStrandsNode.mult(maxNode.sub(minNode)).add(minNode);
462
+ } else {
463
+ p5._friendlyError(
464
+ `It looks like you've called random() with ${args.length} arguments. In strands, random() supports 0, 1, or 2 numeric arguments.`
465
+ );
466
+ }
467
+ });
468
+
356
469
  augmentFn(fn, p5, 'millis', function (...args) {
357
470
  if (!strandsContext.active) {
358
471
  return originalMillis.apply(this, args);
@@ -464,6 +577,53 @@ function initGlobalStrandsAPI(p5, fn, strandsContext) {
464
577
  }
465
578
  });
466
579
  }
580
+
581
+ // Storage buffer uniform function for compute shaders
582
+ fn.uniformStorage = function(name, bufferOrSchema) {
583
+ let schema = null;
584
+ let defaultValue = null;
585
+
586
+ // If it's a function, evaluate it immediately to infer schema,
587
+ // then store the function so it gets called each frame.
588
+ let value = bufferOrSchema;
589
+ if (typeof bufferOrSchema === 'function') {
590
+ value = bufferOrSchema();
591
+ if (value?._schema) {
592
+ defaultValue = bufferOrSchema;
593
+ }
594
+ }
595
+
596
+ if (value?._schema) {
597
+ // Struct storage buffer with pre-computed schema
598
+ schema = value._schema;
599
+ if (defaultValue === null) defaultValue = value;
600
+ } else if (value && typeof value === 'object' && !value._isStorageBuffer) {
601
+ // Plain object schema template -- only used to infer struct layout, not as a default value
602
+ schema = strandsContext.renderer?._inferStructSchema(value) ?? null;
603
+ } else if (value?._isStorageBuffer) {
604
+ defaultValue = bufferOrSchema;
605
+ }
606
+
607
+ const { id, dimension } = variableNode(
608
+ strandsContext,
609
+ { baseType: 'storage', dimension: 1 },
610
+ name
611
+ );
612
+ strandsContext.uniforms.push({
613
+ name,
614
+ typeInfo: { baseType: 'storage', dimension: 1, schema },
615
+ defaultValue,
616
+ });
617
+
618
+ // Create StrandsNode with _originalIdentifier set (like varying variables)
619
+ // This enables proper assignment node creation and ordering preservation
620
+ const node = createStrandsNode(id, dimension, strandsContext);
621
+ node._originalIdentifier = name;
622
+ node._originalBaseType = 'storage';
623
+ node._originalDimension = 1;
624
+ node._schema = schema;
625
+ return node;
626
+ };
467
627
  }
468
628
  //////////////////////////////////////////////
469
629
  // Per-Hook functions
@@ -582,10 +742,14 @@ function createShaderHooksFunctions(strandsContext, fn, shader) {
582
742
  const fragmentHooksWithContext = Object.fromEntries(
583
743
  Object.entries(shader.hooks.fragment).map(([name, hook]) => [name, { ...hook, shaderContext: 'fragment' }])
584
744
  );
745
+ const computeHooksWithContext = Object.fromEntries(
746
+ Object.entries(shader.hooks.compute).map(([name, hook]) => [name, { ...hook, shaderContext: 'compute' }])
747
+ );
585
748
 
586
749
  const availableHooks = {
587
750
  ...vertexHooksWithContext,
588
751
  ...fragmentHooksWithContext,
752
+ ...computeHooksWithContext,
589
753
  };
590
754
  const hookTypes = Object.keys(availableHooks).map(name => shader.hookTypes(name));
591
755
 
@@ -615,9 +779,11 @@ function createShaderHooksFunctions(strandsContext, fn, shader) {
615
779
  if (numStructArgs === 1) {
616
780
  argIdx = hookType.parameters.findIndex(param => param.type.properties);
617
781
  }
782
+ hook._properties = [];
618
783
  for (let i = 0; i < args.length; i++) {
619
784
  if (i === argIdx) {
620
785
  for (const key of args[argIdx].structProperties || []) {
786
+ hook._properties.push(key);
621
787
  Object.defineProperty(hook, key, {
622
788
  get() {
623
789
  return args[argIdx][key];
@@ -632,6 +798,7 @@ function createShaderHooksFunctions(strandsContext, fn, shader) {
632
798
  hook.set(args[argIdx]);
633
799
  }
634
800
  } else {
801
+ hook._properties.push(hookType.parameters[i].name);
635
802
  hook[hookType.parameters[i].name] = args[i];
636
803
  }
637
804
  }
@@ -702,17 +869,21 @@ function createShaderHooksFunctions(strandsContext, fn, shader) {
702
869
  return newStruct.id;
703
870
  }
704
871
  }
872
+ else if (!expectedReturnType.dataType || expectedReturnType.typeName?.trim() === 'void') {
873
+ return null;
874
+ }
705
875
  else /*if(isNativeType(expectedReturnType.typeName))*/ {
706
- if (!expectedReturnType.dataType) {
707
- throw new Error(`Missing dataType for return type ${expectedReturnType.typeName}`);
708
- }
709
876
  const expectedTypeInfo = expectedReturnType.dataType;
710
877
  return enforceReturnTypeMatch(strandsContext, expectedTypeInfo, retNode, hookType.name);
711
878
  }
712
879
  };
713
880
  for (const { valueNode, earlyReturnID } of hook.earlyReturns) {
714
881
  const id = handleRetVal(valueNode);
715
- dag.dependsOn[earlyReturnID] = [id];
882
+ if (id !== null) {
883
+ dag.dependsOn[earlyReturnID] = [id];
884
+ } else {
885
+ dag.dependsOn[earlyReturnID] = [];
886
+ }
716
887
  }
717
888
  rootNodeID = userReturned ? handleRetVal(userReturned) : undefined;
718
889
  const fullHookName = `${hookType.returnType.typeName} ${hookType.name}`;
@@ -721,7 +892,7 @@ function createShaderHooksFunctions(strandsContext, fn, shader) {
721
892
  hookType,
722
893
  entryBlockID,
723
894
  rootNodeID,
724
- shaderContext: hookInfo?.shaderContext, // 'vertex' or 'fragment'
895
+ shaderContext: hookInfo?.shaderContext, // 'vertex', 'fragment', or 'compute'
725
896
  });
726
897
  popBlock(cfg);
727
898
  } hook.begin = setupHook;
@@ -7,18 +7,26 @@ function generateShaderCode(strandsContext) {
7
7
  cfg,
8
8
  backend,
9
9
  vertexDeclarations,
10
- fragmentDeclarations
10
+ fragmentDeclarations,
11
+ computeDeclarations
11
12
  } = strandsContext;
12
13
 
13
14
  const hooksObj = {
14
15
  uniforms: {},
16
+ storageUniforms: {},
15
17
  varyingVariables: [],
16
18
  };
17
19
 
18
20
  for (const {name, typeInfo, defaultValue} of strandsContext.uniforms) {
19
- const key = backend.generateHookUniformKey(name, typeInfo);
20
- if (key !== null) {
21
- hooksObj.uniforms[key] = defaultValue;
21
+ if (typeInfo.baseType === 'storage') {
22
+ if (defaultValue !== null && defaultValue !== undefined) {
23
+ hooksObj.storageUniforms[name] = defaultValue;
24
+ }
25
+ } else {
26
+ const key = backend.generateHookUniformKey(name, typeInfo);
27
+ if (key !== null) {
28
+ hooksObj.uniforms[key] = defaultValue;
29
+ }
22
30
  }
23
31
  }
24
32
 
@@ -27,6 +35,11 @@ function generateShaderCode(strandsContext) {
27
35
  backend.addTextureBindingsToDeclarations(strandsContext);
28
36
  }
29
37
 
38
+ // Add storage buffer bindings to declarations for WebGPU backend
39
+ if (backend.addStorageBufferBindingsToDeclarations) {
40
+ backend.addStorageBufferBindingsToDeclarations(strandsContext);
41
+ }
42
+
30
43
  for (const { hookType, rootNodeID, entryBlockID, shaderContext } of strandsContext.hooks) {
31
44
  const generationContext = {
32
45
  indent: 1,
@@ -51,14 +64,13 @@ function generateShaderCode(strandsContext) {
51
64
  let returnType;
52
65
  if (hookType.returnType.properties) {
53
66
  returnType = structType(hookType.returnType);
67
+ } else if (!hookType.returnType.dataType || hookType.returnType.typeName?.trim() === 'void') {
68
+ returnType = null;
54
69
  } else {
55
- if (!hookType.returnType.dataType) {
56
- throw new Error(`Missing dataType for return type ${hookType.returnType.typeName}`);
57
- }
58
70
  returnType = hookType.returnType.dataType;
59
71
  }
60
72
 
61
- if (rootNodeID) {
73
+ if (rootNodeID !== undefined) {
62
74
  backend.generateReturnStatement(strandsContext, generationContext, rootNodeID, returnType);
63
75
  }
64
76
  hooksObj[`${hookType.returnType.typeName} ${hookType.name}`] = [firstLine, ...generationContext.codeLines, '}'].join('\n');
@@ -81,8 +93,14 @@ function generateShaderCode(strandsContext) {
81
93
  }
82
94
  }
83
95
 
96
+ // Register instanceID varying if used in a fragment hook
97
+ if (strandsContext._instanceIDUsedInFragment) {
98
+ hooksObj.instanceIDVarying = backend.generateInstanceIDVarying();
99
+ }
100
+
84
101
  hooksObj.vertexDeclarations = [...vertexDeclarations].join('\n');
85
102
  hooksObj.fragmentDeclarations = [...fragmentDeclarations].join('\n');
103
+ hooksObj.computeDeclarations = [...computeDeclarations].join('\n');
86
104
 
87
105
  return hooksObj;
88
106
  }
@@ -1,7 +1,7 @@
1
1
  import { createBasicBlock, addEdge, pushBlock, popBlock, pushBlockForModification, recordInBasicBlock } from './ir_cfg.js';
2
2
  import { getOrCreateNode } from './ir_dag.js';
3
3
  import { BlockType, NodeType } from './ir_types.js';
4
- import { c as createStrandsNode } from '../ir_builders-Cd6rU9Vm.js';
4
+ import { c as createStrandsNode } from '../ir_builders-C2ebb6Lu.js';
5
5
  import { createPhiNode } from './strands_phi_utils.js';
6
6
  import './strands_FES.js';
7
7
  import './strands_builtins.js';
@@ -1,7 +1,7 @@
1
1
  import { createBasicBlock, addEdge, pushBlock, popBlock, pushBlockForModification, recordInBasicBlock } from './ir_cfg.js';
2
2
  import { getNodeDataFromID, createNodeData, getOrCreateNode } from './ir_dag.js';
3
3
  import { BlockType, NodeType, StatementType, OpCode, BaseType } from './ir_types.js';
4
- import { c as createStrandsNode, p as primitiveConstructorNode } from '../ir_builders-Cd6rU9Vm.js';
4
+ import { c as createStrandsNode, p as primitiveConstructorNode } from '../ir_builders-C2ebb6Lu.js';
5
5
  import { createPhiNode } from './strands_phi_utils.js';
6
6
  import './strands_FES.js';
7
7
  import './strands_builtins.js';
@@ -1,4 +1,4 @@
1
- export { S as StrandsNode, c as createStrandsNode } from '../ir_builders-Cd6rU9Vm.js';
1
+ export { S as StrandsNode, c as createStrandsNode } from '../ir_builders-C2ebb6Lu.js';
2
2
  import './ir_types.js';
3
3
  import './ir_dag.js';
4
4
  import './ir_cfg.js';
@@ -0,0 +1,56 @@
1
+ import { extractNodeTypeInfo, propagateTypeToAssignOnUse, createNodeData, getOrCreateNode } from './ir_dag.js';
2
+ import { recordInBasicBlock } from './ir_cfg.js';
3
+ import { BaseType, OpCode, NodeType } from './ir_types.js';
4
+ import { c as createStrandsNode } from '../ir_builders-C2ebb6Lu.js';
5
+ import { userError } from './strands_FES.js';
6
+ import './strands_builtins.js';
7
+
8
+ function buildTernary(strandsContext, condition, ifTrue, ifFalse) {
9
+ const { dag, cfg, p5 } = strandsContext;
10
+
11
+ // Ensure all inputs are StrandsNodes
12
+ const condNode = condition?.isStrandsNode ? condition : p5.strandsNode(condition);
13
+ const trueNode = ifTrue?.isStrandsNode ? ifTrue : p5.strandsNode(ifTrue);
14
+ const falseNode = ifFalse?.isStrandsNode ? ifFalse : p5.strandsNode(ifFalse);
15
+
16
+ // Get type info for both nodes
17
+ let trueType = extractNodeTypeInfo(dag, trueNode.id);
18
+ let falseType = extractNodeTypeInfo(dag, falseNode.id);
19
+
20
+ // Propagate type from the known branch to any ASSIGN_ON_USE branch
21
+ if (trueType.baseType === BaseType.ASSIGN_ON_USE && falseType.baseType !== BaseType.ASSIGN_ON_USE) {
22
+ propagateTypeToAssignOnUse(dag, trueNode.id, falseType.baseType, falseType.dimension);
23
+ trueType = extractNodeTypeInfo(dag, trueNode.id);
24
+ } else if (falseType.baseType === BaseType.ASSIGN_ON_USE && trueType.baseType !== BaseType.ASSIGN_ON_USE) {
25
+ propagateTypeToAssignOnUse(dag, falseNode.id, trueType.baseType, trueType.dimension);
26
+ falseType = extractNodeTypeInfo(dag, falseNode.id);
27
+ }
28
+
29
+ // After ASSIGN_ON_USE propagation, if both types are known, they must match
30
+ if (
31
+ trueType.baseType !== BaseType.ASSIGN_ON_USE &&
32
+ falseType.baseType !== BaseType.ASSIGN_ON_USE &&
33
+ (trueType.baseType !== falseType.baseType || trueType.dimension !== falseType.dimension)
34
+ ) {
35
+ userError('type error',
36
+ 'The true and false branches of a ternary expression must have the same type. ' +
37
+ `Right now, the true branch is a ${trueType.baseType}${trueType.dimension}, and the false branch is a ${falseType.baseType}${falseType.dimension}.`
38
+ );
39
+ }
40
+
41
+ const resultType = trueType;
42
+
43
+ const nodeData = createNodeData({
44
+ nodeType: NodeType.OPERATION,
45
+ opCode: OpCode.Nary.TERNARY,
46
+ dependsOn: [condNode.id, trueNode.id, falseNode.id],
47
+ baseType: resultType.baseType,
48
+ dimension: resultType.dimension,
49
+ });
50
+
51
+ const id = getOrCreateNode(dag, nodeData);
52
+ recordInBasicBlock(cfg, cfg.currentBlock, id);
53
+ return createStrandsNode(id, resultType.dimension, strandsContext);
54
+ }
55
+
56
+ export { buildTernary };