@motion-core/motion-gpu 0.4.2 → 0.5.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 (111) hide show
  1. package/README.md +99 -0
  2. package/dist/advanced.js +3 -1
  3. package/dist/core/advanced.js +3 -1
  4. package/dist/core/compute-shader.d.ts +87 -0
  5. package/dist/core/compute-shader.d.ts.map +1 -0
  6. package/dist/core/compute-shader.js +205 -0
  7. package/dist/core/compute-shader.js.map +1 -0
  8. package/dist/core/error-report.d.ts +1 -1
  9. package/dist/core/error-report.d.ts.map +1 -1
  10. package/dist/core/error-report.js +63 -0
  11. package/dist/core/error-report.js.map +1 -1
  12. package/dist/core/frame-registry.d.ts.map +1 -1
  13. package/dist/core/frame-registry.js +1 -1
  14. package/dist/core/frame-registry.js.map +1 -1
  15. package/dist/core/index.d.ts +5 -2
  16. package/dist/core/index.d.ts.map +1 -1
  17. package/dist/core/index.js +3 -1
  18. package/dist/core/material-preprocess.d.ts.map +1 -1
  19. package/dist/core/material-preprocess.js +5 -3
  20. package/dist/core/material-preprocess.js.map +1 -1
  21. package/dist/core/material.d.ts +22 -6
  22. package/dist/core/material.d.ts.map +1 -1
  23. package/dist/core/material.js +30 -3
  24. package/dist/core/material.js.map +1 -1
  25. package/dist/core/render-graph.d.ts +7 -3
  26. package/dist/core/render-graph.d.ts.map +1 -1
  27. package/dist/core/render-graph.js +22 -6
  28. package/dist/core/render-graph.js.map +1 -1
  29. package/dist/core/renderer.d.ts.map +1 -1
  30. package/dist/core/renderer.js +418 -23
  31. package/dist/core/renderer.js.map +1 -1
  32. package/dist/core/runtime-loop.d.ts +2 -2
  33. package/dist/core/runtime-loop.d.ts.map +1 -1
  34. package/dist/core/runtime-loop.js +49 -1
  35. package/dist/core/runtime-loop.js.map +1 -1
  36. package/dist/core/shader.d.ts +9 -1
  37. package/dist/core/shader.d.ts.map +1 -1
  38. package/dist/core/shader.js +21 -2
  39. package/dist/core/shader.js.map +1 -1
  40. package/dist/core/storage-buffers.d.ts +37 -0
  41. package/dist/core/storage-buffers.d.ts.map +1 -0
  42. package/dist/core/storage-buffers.js +95 -0
  43. package/dist/core/storage-buffers.js.map +1 -0
  44. package/dist/core/texture-loader.d.ts.map +1 -1
  45. package/dist/core/texture-loader.js +4 -0
  46. package/dist/core/texture-loader.js.map +1 -1
  47. package/dist/core/textures.d.ts +12 -0
  48. package/dist/core/textures.d.ts.map +1 -1
  49. package/dist/core/textures.js +7 -2
  50. package/dist/core/textures.js.map +1 -1
  51. package/dist/core/types.d.ts +146 -4
  52. package/dist/core/types.d.ts.map +1 -1
  53. package/dist/index.js +3 -1
  54. package/dist/passes/ComputePass.d.ts +83 -0
  55. package/dist/passes/ComputePass.d.ts.map +1 -0
  56. package/dist/passes/ComputePass.js +92 -0
  57. package/dist/passes/ComputePass.js.map +1 -0
  58. package/dist/passes/PingPongComputePass.d.ts +104 -0
  59. package/dist/passes/PingPongComputePass.d.ts.map +1 -0
  60. package/dist/passes/PingPongComputePass.js +132 -0
  61. package/dist/passes/PingPongComputePass.js.map +1 -0
  62. package/dist/passes/ShaderPass.d.ts.map +1 -1
  63. package/dist/passes/ShaderPass.js +2 -1
  64. package/dist/passes/ShaderPass.js.map +1 -1
  65. package/dist/passes/index.d.ts +2 -0
  66. package/dist/passes/index.d.ts.map +1 -1
  67. package/dist/passes/index.js +3 -1
  68. package/dist/react/FragCanvas.d.ts +2 -2
  69. package/dist/react/FragCanvas.d.ts.map +1 -1
  70. package/dist/react/FragCanvas.js.map +1 -1
  71. package/dist/react/MotionGPUErrorOverlay.d.ts.map +1 -1
  72. package/dist/react/MotionGPUErrorOverlay.js +123 -20
  73. package/dist/react/MotionGPUErrorOverlay.js.map +1 -1
  74. package/dist/react/advanced.js +3 -1
  75. package/dist/react/index.d.ts +5 -2
  76. package/dist/react/index.d.ts.map +1 -1
  77. package/dist/react/index.js +3 -1
  78. package/dist/svelte/FragCanvas.svelte +2 -2
  79. package/dist/svelte/FragCanvas.svelte.d.ts +2 -2
  80. package/dist/svelte/FragCanvas.svelte.d.ts.map +1 -1
  81. package/dist/svelte/MotionGPUErrorOverlay.svelte +137 -7
  82. package/dist/svelte/MotionGPUErrorOverlay.svelte.d.ts.map +1 -1
  83. package/dist/svelte/advanced.js +3 -1
  84. package/dist/svelte/index.d.ts +5 -2
  85. package/dist/svelte/index.d.ts.map +1 -1
  86. package/dist/svelte/index.js +3 -1
  87. package/package.json +1 -1
  88. package/src/lib/core/compute-shader.ts +326 -0
  89. package/src/lib/core/error-report.ts +129 -0
  90. package/src/lib/core/frame-registry.ts +2 -1
  91. package/src/lib/core/index.ts +18 -1
  92. package/src/lib/core/material-preprocess.ts +17 -6
  93. package/src/lib/core/material.ts +101 -20
  94. package/src/lib/core/render-graph.ts +39 -9
  95. package/src/lib/core/renderer.ts +655 -41
  96. package/src/lib/core/runtime-loop.ts +82 -3
  97. package/src/lib/core/shader.ts +45 -2
  98. package/src/lib/core/storage-buffers.ts +142 -0
  99. package/src/lib/core/texture-loader.ts +6 -0
  100. package/src/lib/core/textures.ts +24 -2
  101. package/src/lib/core/types.ts +165 -4
  102. package/src/lib/passes/ComputePass.ts +136 -0
  103. package/src/lib/passes/PingPongComputePass.ts +180 -0
  104. package/src/lib/passes/ShaderPass.ts +2 -1
  105. package/src/lib/passes/index.ts +6 -0
  106. package/src/lib/react/FragCanvas.tsx +3 -3
  107. package/src/lib/react/MotionGPUErrorOverlay.tsx +137 -5
  108. package/src/lib/react/index.ts +18 -1
  109. package/src/lib/svelte/FragCanvas.svelte +2 -2
  110. package/src/lib/svelte/MotionGPUErrorOverlay.svelte +137 -7
  111. package/src/lib/svelte/index.ts +18 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/react/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EACX,sBAAsB,EACtB,UAAU,EACV,gBAAgB,EAChB,UAAU,EACV,iBAAiB,EACjB,eAAe,EACf,mBAAmB,EACnB,oBAAoB,EACpB,UAAU,EACV,YAAY,EACZ,sBAAsB,EACtB,yBAAyB,EACzB,WAAW,EACX,iBAAiB,EACjB,oBAAoB,EACpB,iBAAiB,EACjB,UAAU,EACV,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,UAAU,EACV,WAAW,EACX,YAAY,EACZ,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACX,aAAa,EACb,oBAAoB,EACpB,kBAAkB,EAClB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EACX,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACnB,eAAe,EACf,wBAAwB,EACxB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC1E,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/react/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EACN,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,WAAW,EACX,mBAAmB,EACnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EACX,sBAAsB,EACtB,UAAU,EACV,gBAAgB,EAChB,OAAO,EACP,eAAe,EACf,UAAU,EACV,iBAAiB,EACjB,eAAe,EACf,mBAAmB,EACnB,oBAAoB,EACpB,UAAU,EACV,YAAY,EACZ,sBAAsB,EACtB,yBAAyB,EACzB,WAAW,EACX,iBAAiB,EACjB,oBAAoB,EACpB,iBAAiB,EACjB,UAAU,EACV,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,UAAU,EACV,WAAW,EACX,YAAY,EACZ,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACX,aAAa,EACb,oBAAoB,EACpB,kBAAkB,EAClB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EACX,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACnB,eAAe,EACf,wBAAwB,EACxB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC1E,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,YAAY,EACX,mBAAmB,EACnB,uBAAuB,EACvB,0BAA0B,EAC1B,iBAAiB,EACjB,kBAAkB,EAClB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAC3F,YAAY,EAAE,0BAA0B,EAAE,MAAM,kCAAkC,CAAC"}
@@ -2,9 +2,11 @@ import { defineMaterial } from "../core/material.js";
2
2
  import { BlitPass } from "../passes/BlitPass.js";
3
3
  import { CopyPass } from "../passes/CopyPass.js";
4
4
  import { ShaderPass } from "../passes/ShaderPass.js";
5
+ import { ComputePass } from "../passes/ComputePass.js";
6
+ import { PingPongComputePass } from "../passes/PingPongComputePass.js";
5
7
  import "../passes/index.js";
6
8
  import { useMotionGPU } from "./motiongpu-context.js";
7
9
  import { useFrame } from "./frame-context.js";
8
10
  import { FragCanvas } from "./FragCanvas.js";
9
11
  import { useTexture } from "./use-texture.js";
10
- export { BlitPass, CopyPass, FragCanvas, ShaderPass, defineMaterial, useFrame, useMotionGPU, useTexture };
12
+ export { BlitPass, ComputePass, CopyPass, FragCanvas, PingPongComputePass, ShaderPass, defineMaterial, useFrame, useMotionGPU, useTexture };
@@ -7,9 +7,9 @@
7
7
  import MotionGPUErrorOverlay from './MotionGPUErrorOverlay.svelte';
8
8
  import { createMotionGPURuntimeLoop } from '../core/runtime-loop';
9
9
  import type {
10
+ AnyPass,
10
11
  FrameInvalidationToken,
11
12
  OutputColorSpace,
12
- RenderPass,
13
13
  RenderMode,
14
14
  RenderTargetDefinitionMap
15
15
  } from '../core/types';
@@ -19,7 +19,7 @@
19
19
  interface Props {
20
20
  material: FragMaterial;
21
21
  renderTargets?: RenderTargetDefinitionMap;
22
- passes?: RenderPass[];
22
+ passes?: AnyPass[];
23
23
  clearColor?: [number, number, number, number];
24
24
  outputColorSpace?: OutputColorSpace;
25
25
  renderMode?: RenderMode;
@@ -2,11 +2,11 @@
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { FragMaterial } from '../core/material';
4
4
  import { type MotionGPUErrorReport } from '../core/error-report';
5
- import type { OutputColorSpace, RenderPass, RenderMode, RenderTargetDefinitionMap } from '../core/types';
5
+ import type { AnyPass, OutputColorSpace, RenderMode, RenderTargetDefinitionMap } from '../core/types';
6
6
  interface Props {
7
7
  material: FragMaterial;
8
8
  renderTargets?: RenderTargetDefinitionMap;
9
- passes?: RenderPass[];
9
+ passes?: AnyPass[];
10
10
  clearColor?: [number, number, number, number];
11
11
  outputColorSpace?: OutputColorSpace;
12
12
  renderMode?: RenderMode;
@@ -1 +1 @@
1
- {"version":3,"file":"FragCanvas.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/svelte/FragCanvas.svelte"],"names":[],"mappings":";AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,OAAO,EAA0B,KAAK,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAGzF,OAAO,KAAK,EAEV,gBAAgB,EAChB,UAAU,EACV,UAAU,EACV,yBAAyB,EACzB,MAAM,eAAe,CAAC;AAKvB,UAAU,KAAK;IACd,QAAQ,EAAE,YAAY,CAAC;IACvB,aAAa,CAAC,EAAE,yBAAyB,CAAC;IAC1C,MAAM,CAAC,EAAE,UAAU,EAAE,CAAC;IACtB,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9C,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,wBAAwB,CAAC;IAC1C,gBAAgB,CAAC,EAAE,mBAAmB,CAAC;IACvC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,aAAa,CAAC,EAAE,OAAO,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC;IAChD,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACjD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,oBAAoB,EAAE,KAAK,IAAI,CAAC;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB;AAiNF,QAAA,MAAM,UAAU,2CAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"FragCanvas.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/svelte/FragCanvas.svelte"],"names":[],"mappings":";AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,OAAO,EAA0B,KAAK,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAGzF,OAAO,KAAK,EACV,OAAO,EAEP,gBAAgB,EAChB,UAAU,EACV,yBAAyB,EACzB,MAAM,eAAe,CAAC;AAKvB,UAAU,KAAK;IACd,QAAQ,EAAE,YAAY,CAAC;IACvB,aAAa,CAAC,EAAE,yBAAyB,CAAC;IAC1C,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC;IACnB,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9C,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,wBAAwB,CAAC;IAC1C,gBAAgB,CAAC,EAAE,mBAAmB,CAAC;IACvC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,aAAa,CAAC,EAAE,OAAO,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC;IAChD,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACjD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,oBAAoB,EAAE,KAAK,IAAI,CAAC;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB;AAiNF,QAAA,MAAM,UAAU,2CAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
@@ -16,7 +16,87 @@
16
16
  };
17
17
 
18
18
  const shouldShowErrorMessage = (value: MotionGPUErrorReport): boolean => {
19
- return normalizeErrorText(value.message) !== normalizeErrorText(value.title);
19
+ return resolveDisplayMessage(value).length > 0;
20
+ };
21
+
22
+ const resolveDisplayMessage = (value: MotionGPUErrorReport): string => {
23
+ const rawMessage = value.message.trim();
24
+ if (rawMessage.length === 0) {
25
+ return '';
26
+ }
27
+
28
+ const normalizedMessage = normalizeErrorText(rawMessage);
29
+ const normalizedTitle = normalizeErrorText(value.title);
30
+ if (normalizedMessage === normalizedTitle) {
31
+ return '';
32
+ }
33
+
34
+ const escapedTitle = value.title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
35
+ const prefixPattern = new RegExp(`^${escapedTitle}\\s*[:\\-|]\\s*`, 'i');
36
+ const stripped = rawMessage.replace(prefixPattern, '').trim();
37
+ return stripped.length > 0 ? stripped : rawMessage;
38
+ };
39
+
40
+ const formatRuntimeContext = (context: MotionGPUErrorReport['context']): string => {
41
+ if (!context) {
42
+ return '';
43
+ }
44
+
45
+ const indentBlock = (value: string, spaces = 2): string => {
46
+ const prefix = ' '.repeat(spaces);
47
+ return value
48
+ .split('\n')
49
+ .map((line) => `${prefix}${line}`)
50
+ .join('\n');
51
+ };
52
+
53
+ const formatMaterialSignature = (value: string): string => {
54
+ const trimmed = value.trim();
55
+ if (trimmed.length === 0) {
56
+ return '<empty>';
57
+ }
58
+ try {
59
+ return JSON.stringify(JSON.parse(trimmed), null, 2);
60
+ } catch {
61
+ return trimmed;
62
+ }
63
+ };
64
+
65
+ const lines: string[] = [];
66
+ if (context.materialSignature) {
67
+ lines.push('materialSignature:');
68
+ lines.push(indentBlock(formatMaterialSignature(context.materialSignature)));
69
+ }
70
+ if (context.passGraph) {
71
+ lines.push('passGraph:');
72
+ lines.push(` passCount: ${context.passGraph.passCount}`);
73
+ lines.push(` enabledPassCount: ${context.passGraph.enabledPassCount}`);
74
+ lines.push(' inputs:');
75
+ if (context.passGraph.inputs.length === 0) {
76
+ lines.push(' - <none>');
77
+ } else {
78
+ for (const input of context.passGraph.inputs) {
79
+ lines.push(` - ${input}`);
80
+ }
81
+ }
82
+ lines.push(' outputs:');
83
+ if (context.passGraph.outputs.length === 0) {
84
+ lines.push(' - <none>');
85
+ } else {
86
+ for (const output of context.passGraph.outputs) {
87
+ lines.push(` - ${output}`);
88
+ }
89
+ }
90
+ }
91
+ lines.push('activeRenderTargets:');
92
+ if (context.activeRenderTargets.length === 0) {
93
+ lines.push(' - <none>');
94
+ } else {
95
+ for (const target of context.activeRenderTargets) {
96
+ lines.push(` - ${target}`);
97
+ }
98
+ }
99
+ return lines.join('\n');
20
100
  };
21
101
  </script>
22
102
 
@@ -30,16 +110,28 @@
30
110
  data-testid="motiongpu-error"
31
111
  >
32
112
  <header class="motiongpu-error-header">
33
- <div class="motiongpu-error-badge-wrap">
34
- <p class="motiongpu-error-phase">
35
- {report.phase}
36
- </p>
113
+ <div class="motiongpu-error-header-top">
114
+ <div class="motiongpu-error-badges">
115
+ <div class="motiongpu-error-badge-wrap">
116
+ <p class="motiongpu-error-badge motiongpu-error-badge-phase">
117
+ {report.phase}
118
+ </p>
119
+ </div>
120
+ <div class="motiongpu-error-badge-wrap">
121
+ <p class="motiongpu-error-badge motiongpu-error-badge-severity">
122
+ {report.severity}
123
+ </p>
124
+ </div>
125
+ </div>
37
126
  </div>
38
127
  <h2 class="motiongpu-error-title">{report.title}</h2>
128
+ <p class="motiongpu-error-recoverable">
129
+ Recoverable: <span>{report.recoverable ? 'yes' : 'no'}</span>
130
+ </p>
39
131
  </header>
40
132
  <div class="motiongpu-error-body">
41
133
  {#if shouldShowErrorMessage(report)}
42
- <p class="motiongpu-error-message">{report.message}</p>
134
+ <p class="motiongpu-error-message">{resolveDisplayMessage(report)}</p>
43
135
  {/if}
44
136
  <p class="motiongpu-error-hint">{report.hint}</p>
45
137
  </div>
@@ -87,6 +179,12 @@
87
179
  <pre>{report.stack.join('\n')}</pre>
88
180
  </details>
89
181
  {/if}
182
+ {#if report.context}
183
+ <details class="motiongpu-error-details">
184
+ <summary>Runtime context</summary>
185
+ <pre>{formatRuntimeContext(report.context)}</pre>
186
+ </details>
187
+ {/if}
90
188
  </div>
91
189
  </section>
92
190
  </div>
@@ -162,6 +260,19 @@
162
260
  border-bottom: 1px solid var(--motiongpu-color-border);
163
261
  }
164
262
 
263
+ .motiongpu-error-header-top {
264
+ display: flex;
265
+ align-items: flex-start;
266
+ gap: 0.75rem;
267
+ }
268
+
269
+ .motiongpu-error-badges {
270
+ display: inline-flex;
271
+ align-items: center;
272
+ gap: 0.4rem;
273
+ flex-wrap: wrap;
274
+ }
275
+
165
276
  .motiongpu-error-badge-wrap {
166
277
  display: inline-flex;
167
278
  align-items: center;
@@ -173,7 +284,7 @@
173
284
  background: var(--motiongpu-color-background-muted);
174
285
  }
175
286
 
176
- .motiongpu-error-phase {
287
+ .motiongpu-error-badge {
177
288
  display: inline-flex;
178
289
  align-items: center;
179
290
  margin: 0;
@@ -193,6 +304,20 @@
193
304
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.24);
194
305
  }
195
306
 
307
+ .motiongpu-error-recoverable {
308
+ margin: 0;
309
+ font-size: 0.67rem;
310
+ line-height: 1.2;
311
+ letter-spacing: 0.06em;
312
+ text-transform: uppercase;
313
+ color: var(--motiongpu-color-foreground-muted);
314
+ }
315
+
316
+ .motiongpu-error-recoverable span {
317
+ font-family: var(--motiongpu-font-mono);
318
+ color: var(--motiongpu-color-foreground);
319
+ }
320
+
196
321
  .motiongpu-error-title {
197
322
  margin: 0;
198
323
  font-size: clamp(1.02rem, 1vw + 0.72rem, 1.32rem);
@@ -372,6 +497,11 @@
372
497
  .motiongpu-error-title {
373
498
  font-size: 1.02rem;
374
499
  }
500
+
501
+ .motiongpu-error-header-top {
502
+ flex-direction: column;
503
+ align-items: flex-start;
504
+ }
375
505
  }
376
506
 
377
507
  @media (prefers-reduced-motion: reduce) {
@@ -1 +1 @@
1
- {"version":3,"file":"MotionGPUErrorOverlay.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/svelte/MotionGPUErrorOverlay.svelte"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAIhE,UAAU,KAAK;IACd,MAAM,EAAE,oBAAoB,CAAC;CAC7B;AAkFF,QAAA,MAAM,qBAAqB,2CAAwC,CAAC;AACpE,KAAK,qBAAqB,GAAG,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAC;AACtE,eAAe,qBAAqB,CAAC"}
1
+ {"version":3,"file":"MotionGPUErrorOverlay.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/svelte/MotionGPUErrorOverlay.svelte"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAIhE,UAAU,KAAK;IACd,MAAM,EAAE,oBAAoB,CAAC;CAC7B;AAoLF,QAAA,MAAM,qBAAqB,2CAAwC,CAAC;AACpE,KAAK,qBAAqB,GAAG,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAC;AACtE,eAAe,qBAAqB,CAAC"}
@@ -2,10 +2,12 @@ import { defineMaterial } from "../core/material.js";
2
2
  import { BlitPass } from "../passes/BlitPass.js";
3
3
  import { CopyPass } from "../passes/CopyPass.js";
4
4
  import { ShaderPass } from "../passes/ShaderPass.js";
5
+ import { ComputePass } from "../passes/ComputePass.js";
6
+ import { PingPongComputePass } from "../passes/PingPongComputePass.js";
5
7
  import { applySchedulerPreset, captureSchedulerDebugSnapshot } from "../core/scheduler-helpers.js";
6
8
  import { useMotionGPU } from "./motiongpu-context.js";
7
9
  import { useFrame } from "./frame-context.js";
8
10
  import { useTexture } from "./use-texture.js";
9
11
  import { FragCanvas } from "./index.js";
10
12
  import { setMotionGPUUserContext, useMotionGPUUserContext } from "./use-motiongpu-user-context.js";
11
- export { BlitPass, CopyPass, FragCanvas, ShaderPass, applySchedulerPreset, captureSchedulerDebugSnapshot, defineMaterial, setMotionGPUUserContext, useFrame, useMotionGPU, useMotionGPUUserContext, useTexture };
13
+ export { BlitPass, ComputePass, CopyPass, FragCanvas, PingPongComputePass, ShaderPass, applySchedulerPreset, captureSchedulerDebugSnapshot, defineMaterial, setMotionGPUUserContext, useFrame, useMotionGPU, useMotionGPUUserContext, useTexture };
@@ -3,14 +3,17 @@
3
3
  */
4
4
  export { default as FragCanvas } from './FragCanvas.svelte';
5
5
  export { defineMaterial } from '../core/material.js';
6
- export { BlitPass, CopyPass, ShaderPass } from '../passes/index.js';
6
+ export { BlitPass, CopyPass, ShaderPass, ComputePass, PingPongComputePass } from '../passes/index.js';
7
7
  export { useMotionGPU } from './motiongpu-context.js';
8
8
  export { useFrame } from './frame-context.js';
9
9
  export { useTexture } from './use-texture.js';
10
- export type { FrameInvalidationToken, FrameState, OutputColorSpace, RenderPass, RenderPassContext, RenderPassFlags, RenderPassInputSlot, RenderPassOutputSlot, RenderMode, RenderTarget, RenderTargetDefinition, RenderTargetDefinitionMap, TextureData, TextureDefinition, TextureDefinitionMap, TextureUpdateMode, TextureMap, TextureSource, TextureValue, TypedUniform, UniformMat4Value, UniformMap, UniformType, UniformValue } from '../core/types.js';
10
+ export type { FrameInvalidationToken, FrameState, OutputColorSpace, AnyPass, ComputePassLike, RenderPass, RenderPassContext, RenderPassFlags, RenderPassInputSlot, RenderPassOutputSlot, RenderMode, RenderTarget, RenderTargetDefinition, RenderTargetDefinitionMap, TextureData, TextureDefinition, TextureDefinitionMap, TextureUpdateMode, TextureMap, TextureSource, TextureValue, TypedUniform, UniformMat4Value, UniformMap, UniformType, UniformValue } from '../core/types.js';
11
11
  export type { LoadedTexture, TextureDecodeOptions, TextureLoadOptions } from '../core/texture-loader.js';
12
12
  export type { FragMaterial, FragMaterialInput, MaterialIncludes, MaterialDefineValue, MaterialDefines, TypedMaterialDefineValue } from '../core/material.js';
13
13
  export type { MotionGPUContext } from './motiongpu-context.js';
14
14
  export type { UseFrameOptions, UseFrameResult } from './frame-context.js';
15
15
  export type { TextureUrlInput, UseTextureResult } from './use-texture.js';
16
+ export type { StorageBufferAccess, StorageBufferDefinition, StorageBufferDefinitionMap, StorageBufferType, ComputePassContext } from '../core/types.js';
17
+ export type { ComputePassOptions, ComputeDispatchContext } from '../passes/ComputePass.js';
18
+ export type { PingPongComputePassOptions } from '../passes/PingPongComputePass.js';
16
19
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/svelte/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EACX,sBAAsB,EACtB,UAAU,EACV,gBAAgB,EAChB,UAAU,EACV,iBAAiB,EACjB,eAAe,EACf,mBAAmB,EACnB,oBAAoB,EACpB,UAAU,EACV,YAAY,EACZ,sBAAsB,EACtB,yBAAyB,EACzB,WAAW,EACX,iBAAiB,EACjB,oBAAoB,EACpB,iBAAiB,EACjB,UAAU,EACV,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,UAAU,EACV,WAAW,EACX,YAAY,EACZ,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACX,aAAa,EACb,oBAAoB,EACpB,kBAAkB,EAClB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EACX,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACnB,eAAe,EACf,wBAAwB,EACxB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC1E,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/svelte/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EACN,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,WAAW,EACX,mBAAmB,EACnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EACX,sBAAsB,EACtB,UAAU,EACV,gBAAgB,EAChB,OAAO,EACP,eAAe,EACf,UAAU,EACV,iBAAiB,EACjB,eAAe,EACf,mBAAmB,EACnB,oBAAoB,EACpB,UAAU,EACV,YAAY,EACZ,sBAAsB,EACtB,yBAAyB,EACzB,WAAW,EACX,iBAAiB,EACjB,oBAAoB,EACpB,iBAAiB,EACjB,UAAU,EACV,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,UAAU,EACV,WAAW,EACX,YAAY,EACZ,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACX,aAAa,EACb,oBAAoB,EACpB,kBAAkB,EAClB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EACX,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACnB,eAAe,EACf,wBAAwB,EACxB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC1E,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,YAAY,EACX,mBAAmB,EACnB,uBAAuB,EACvB,0BAA0B,EAC1B,iBAAiB,EACjB,kBAAkB,EAClB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAC3F,YAAY,EAAE,0BAA0B,EAAE,MAAM,kCAAkC,CAAC"}
@@ -2,9 +2,11 @@ import { defineMaterial } from "../core/material.js";
2
2
  import { BlitPass } from "../passes/BlitPass.js";
3
3
  import { CopyPass } from "../passes/CopyPass.js";
4
4
  import { ShaderPass } from "../passes/ShaderPass.js";
5
+ import { ComputePass } from "../passes/ComputePass.js";
6
+ import { PingPongComputePass } from "../passes/PingPongComputePass.js";
5
7
  import "../passes/index.js";
6
8
  import { useMotionGPU } from "./motiongpu-context.js";
7
9
  import { useFrame } from "./frame-context.js";
8
10
  import { useTexture } from "./use-texture.js";
9
11
  import FragCanvas from "./FragCanvas.svelte";
10
- export { BlitPass, CopyPass, FragCanvas, ShaderPass, defineMaterial, useFrame, useMotionGPU, useTexture };
12
+ export { BlitPass, ComputePass, CopyPass, FragCanvas, PingPongComputePass, ShaderPass, defineMaterial, useFrame, useMotionGPU, useTexture };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@motion-core/motion-gpu",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "description": "Framework-agnostic WebGPU runtime for fullscreen WGSL shaders with explicit Svelte and React adapter entrypoints.",
5
5
  "keywords": [
6
6
  "svelte",
@@ -0,0 +1,326 @@
1
+ import type { StorageBufferAccess, StorageBufferType, UniformLayout } from './types.js';
2
+
3
+ /**
4
+ * Regex contract for compute entrypoint.
5
+ * Matches: @compute @workgroup_size(...) fn compute(
6
+ * with @builtin(global_invocation_id) parameter.
7
+ */
8
+ export const COMPUTE_ENTRY_CONTRACT = /@compute\s+@workgroup_size\s*\([^)]+\)\s*fn\s+compute\s*\(/;
9
+
10
+ /**
11
+ * Regex to extract @workgroup_size values.
12
+ */
13
+ const WORKGROUP_SIZE_PATTERN =
14
+ /@workgroup_size\s*\(\s*(\d+)(?:\s*,\s*(\d+))?(?:\s*,\s*(\d+))?\s*\)/;
15
+
16
+ /**
17
+ * Regex to verify @builtin(global_invocation_id) parameter.
18
+ */
19
+ const GLOBAL_INVOCATION_ID_PATTERN = /@builtin\s*\(\s*global_invocation_id\s*\)/;
20
+ const WORKGROUP_DIMENSION_MIN = 1;
21
+ const WORKGROUP_DIMENSION_MAX = 65535;
22
+
23
+ function extractComputeParamList(compute: string): string | null {
24
+ const computeFnIndex = compute.indexOf('fn compute');
25
+ if (computeFnIndex === -1) {
26
+ return null;
27
+ }
28
+
29
+ const openParenIndex = compute.indexOf('(', computeFnIndex);
30
+ if (openParenIndex === -1) {
31
+ return null;
32
+ }
33
+
34
+ let depth = 0;
35
+ for (let index = openParenIndex; index < compute.length; index += 1) {
36
+ const char = compute[index];
37
+ if (char === '(') {
38
+ depth += 1;
39
+ continue;
40
+ }
41
+
42
+ if (char === ')') {
43
+ depth -= 1;
44
+ if (depth === 0) {
45
+ return compute.slice(openParenIndex + 1, index);
46
+ }
47
+ }
48
+ }
49
+
50
+ return null;
51
+ }
52
+
53
+ function assertWorkgroupDimension(value: number): void {
54
+ if (
55
+ !Number.isFinite(value) ||
56
+ !Number.isInteger(value) ||
57
+ value < WORKGROUP_DIMENSION_MIN ||
58
+ value > WORKGROUP_DIMENSION_MAX
59
+ ) {
60
+ throw new Error(
61
+ `@workgroup_size dimensions must be integers in range ${WORKGROUP_DIMENSION_MIN}-${WORKGROUP_DIMENSION_MAX}, got ${value}.`
62
+ );
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Default uniform field used when no custom uniforms are provided in compute.
68
+ */
69
+ const DEFAULT_UNIFORM_FIELD = 'motiongpu_unused: vec4f,';
70
+
71
+ /**
72
+ * Validates compute shader user code matches the compute contract.
73
+ *
74
+ * @param compute - User compute shader WGSL source.
75
+ * @throws {Error} When shader does not match the compute contract.
76
+ */
77
+ export function assertComputeContract(compute: string): void {
78
+ if (!COMPUTE_ENTRY_CONTRACT.test(compute)) {
79
+ throw new Error(
80
+ 'Compute shader must declare `@compute @workgroup_size(...) fn compute(...)`. ' +
81
+ 'Ensure the function is named `compute` and includes @compute and @workgroup_size annotations.'
82
+ );
83
+ }
84
+
85
+ const params = extractComputeParamList(compute);
86
+ if (!params || !GLOBAL_INVOCATION_ID_PATTERN.test(params)) {
87
+ throw new Error('Compute shader must include a `@builtin(global_invocation_id)` parameter.');
88
+ }
89
+
90
+ extractWorkgroupSize(compute);
91
+ }
92
+
93
+ /**
94
+ * Extracts @workgroup_size values from WGSL compute shader.
95
+ *
96
+ * @param compute - Validated compute shader source.
97
+ * @returns Tuple [x, y, z] with defaults of 1 for omitted dimensions.
98
+ */
99
+ export function extractWorkgroupSize(compute: string): [number, number, number] {
100
+ const match = compute.match(WORKGROUP_SIZE_PATTERN);
101
+ if (!match) {
102
+ throw new Error('Could not extract @workgroup_size from compute shader source.');
103
+ }
104
+
105
+ const x = Number.parseInt(match[1] ?? '1', 10);
106
+ const y = Number.parseInt(match[2] ?? '1', 10);
107
+ const z = Number.parseInt(match[3] ?? '1', 10);
108
+ assertWorkgroupDimension(x);
109
+ assertWorkgroupDimension(y);
110
+ assertWorkgroupDimension(z);
111
+
112
+ return [x, y, z];
113
+ }
114
+
115
+ /**
116
+ * Maps StorageBufferAccess to WGSL var qualifier.
117
+ */
118
+ function toWgslAccessMode(access: StorageBufferAccess): string {
119
+ switch (access) {
120
+ case 'read':
121
+ return 'read';
122
+ case 'read-write':
123
+ return 'read_write';
124
+ default:
125
+ throw new Error(`Unsupported storage buffer access mode "${String(access)}".`);
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Builds WGSL struct fields for uniforms used in compute shader preamble.
131
+ */
132
+ function buildUniformStructForCompute(layout: UniformLayout): string {
133
+ if (layout.entries.length === 0) {
134
+ return DEFAULT_UNIFORM_FIELD;
135
+ }
136
+
137
+ return layout.entries.map((entry) => `${entry.name}: ${entry.type},`).join('\n\t');
138
+ }
139
+
140
+ /**
141
+ * Builds storage buffer binding declarations for compute shader.
142
+ *
143
+ * @param storageBufferKeys - Sorted buffer keys.
144
+ * @param definitions - Type/access definitions per key.
145
+ * @param groupIndex - Bind group index for storage buffers.
146
+ * @returns WGSL binding declaration string.
147
+ */
148
+ export function buildComputeStorageBufferBindings(
149
+ storageBufferKeys: string[],
150
+ definitions: Record<string, { type: StorageBufferType; access: StorageBufferAccess }>,
151
+ groupIndex: number
152
+ ): string {
153
+ if (storageBufferKeys.length === 0) {
154
+ return '';
155
+ }
156
+
157
+ const declarations: string[] = [];
158
+
159
+ for (let index = 0; index < storageBufferKeys.length; index += 1) {
160
+ const key = storageBufferKeys[index];
161
+ if (key === undefined) {
162
+ continue;
163
+ }
164
+
165
+ const definition = definitions[key];
166
+ if (!definition) {
167
+ continue;
168
+ }
169
+
170
+ const accessMode = toWgslAccessMode(definition.access);
171
+ declarations.push(
172
+ `@group(${groupIndex}) @binding(${index}) var<storage, ${accessMode}> ${key}: ${definition.type};`
173
+ );
174
+ }
175
+
176
+ return declarations.join('\n');
177
+ }
178
+
179
+ /**
180
+ * Builds storage texture binding declarations for compute shader.
181
+ *
182
+ * @param storageTextureKeys - Sorted storage texture keys.
183
+ * @param definitions - Format definitions per key.
184
+ * @param groupIndex - Bind group index for storage textures.
185
+ * @returns WGSL binding declaration string.
186
+ */
187
+ export function buildComputeStorageTextureBindings(
188
+ storageTextureKeys: string[],
189
+ definitions: Record<string, { format: GPUTextureFormat }>,
190
+ groupIndex: number
191
+ ): string {
192
+ if (storageTextureKeys.length === 0) {
193
+ return '';
194
+ }
195
+
196
+ const declarations: string[] = [];
197
+
198
+ for (let index = 0; index < storageTextureKeys.length; index += 1) {
199
+ const key = storageTextureKeys[index];
200
+ if (key === undefined) {
201
+ continue;
202
+ }
203
+
204
+ const definition = definitions[key];
205
+ if (!definition) {
206
+ continue;
207
+ }
208
+
209
+ declarations.push(
210
+ `@group(${groupIndex}) @binding(${index}) var ${key}: texture_storage_2d<${definition.format}, write>;`
211
+ );
212
+ }
213
+
214
+ return declarations.join('\n');
215
+ }
216
+
217
+ /**
218
+ * Maps storage texture format to sampled texture scalar type for `texture_2d<T>`.
219
+ */
220
+ export function storageTextureSampleScalarType(format: GPUTextureFormat): 'f32' | 'u32' | 'i32' {
221
+ const normalized = String(format).toLowerCase();
222
+ if (normalized.endsWith('uint')) {
223
+ return 'u32';
224
+ }
225
+ if (normalized.endsWith('sint')) {
226
+ return 'i32';
227
+ }
228
+ return 'f32';
229
+ }
230
+
231
+ /**
232
+ * Assembles compute shader WGSL for ping-pong workflows.
233
+ *
234
+ * Exposes two generated bindings under group(2):
235
+ * - `${target}A`: sampled read texture (`texture_2d<T>`)
236
+ * - `${target}B`: storage write texture (`texture_storage_2d<format, write>`)
237
+ */
238
+ export function buildPingPongComputeShaderSource(options: {
239
+ compute: string;
240
+ uniformLayout: UniformLayout;
241
+ storageBufferKeys: string[];
242
+ storageBufferDefinitions: Record<
243
+ string,
244
+ { type: StorageBufferType; access: StorageBufferAccess }
245
+ >;
246
+ target: string;
247
+ targetFormat: GPUTextureFormat;
248
+ }): string {
249
+ const uniformFields = buildUniformStructForCompute(options.uniformLayout);
250
+ const storageBufferBindings = buildComputeStorageBufferBindings(
251
+ options.storageBufferKeys,
252
+ options.storageBufferDefinitions,
253
+ 1
254
+ );
255
+ const sampledType = storageTextureSampleScalarType(options.targetFormat);
256
+ const pingPongTextureBindings = [
257
+ `@group(2) @binding(0) var ${options.target}A: texture_2d<${sampledType}>;`,
258
+ `@group(2) @binding(1) var ${options.target}B: texture_storage_2d<${options.targetFormat}, write>;`
259
+ ].join('\n');
260
+
261
+ return `struct MotionGPUFrame {
262
+ time: f32,
263
+ delta: f32,
264
+ resolution: vec2f,
265
+ };
266
+
267
+ struct MotionGPUUniforms {
268
+ ${uniformFields}
269
+ };
270
+
271
+ @group(0) @binding(0) var<uniform> motiongpuFrame: MotionGPUFrame;
272
+ @group(0) @binding(1) var<uniform> motiongpuUniforms: MotionGPUUniforms;
273
+ ${storageBufferBindings ? '\n' + storageBufferBindings : ''}
274
+ ${pingPongTextureBindings ? '\n' + pingPongTextureBindings : ''}
275
+
276
+ ${options.compute}
277
+ `;
278
+ }
279
+
280
+ /**
281
+ * Assembles full compute shader WGSL with preamble.
282
+ *
283
+ * @param options - Compute shader build options.
284
+ * @returns Complete WGSL source for compute stage.
285
+ */
286
+ export function buildComputeShaderSource(options: {
287
+ compute: string;
288
+ uniformLayout: UniformLayout;
289
+ storageBufferKeys: string[];
290
+ storageBufferDefinitions: Record<
291
+ string,
292
+ { type: StorageBufferType; access: StorageBufferAccess }
293
+ >;
294
+ storageTextureKeys: string[];
295
+ storageTextureDefinitions: Record<string, { format: GPUTextureFormat }>;
296
+ }): string {
297
+ const uniformFields = buildUniformStructForCompute(options.uniformLayout);
298
+ const storageBufferBindings = buildComputeStorageBufferBindings(
299
+ options.storageBufferKeys,
300
+ options.storageBufferDefinitions,
301
+ 1
302
+ );
303
+ const storageTextureBindings = buildComputeStorageTextureBindings(
304
+ options.storageTextureKeys,
305
+ options.storageTextureDefinitions,
306
+ 2
307
+ );
308
+
309
+ return `struct MotionGPUFrame {
310
+ time: f32,
311
+ delta: f32,
312
+ resolution: vec2f,
313
+ };
314
+
315
+ struct MotionGPUUniforms {
316
+ ${uniformFields}
317
+ };
318
+
319
+ @group(0) @binding(0) var<uniform> motiongpuFrame: MotionGPUFrame;
320
+ @group(0) @binding(1) var<uniform> motiongpuUniforms: MotionGPUUniforms;
321
+ ${storageBufferBindings ? '\n' + storageBufferBindings : ''}
322
+ ${storageTextureBindings ? '\n' + storageTextureBindings : ''}
323
+
324
+ ${options.compute}
325
+ `;
326
+ }