@aelionsdk/material-compiler 0.1.0-beta.1

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.
@@ -0,0 +1,174 @@
1
+ import type { Diagnostic, JsonObject, JsonValue } from '@aelionsdk/core';
2
+ export type MaterialValueType = 'float' | 'enum' | 'visual-frame';
3
+ export type GraphBinding = {
4
+ readonly value: JsonValue;
5
+ } | {
6
+ readonly parameter: string;
7
+ } | {
8
+ readonly inputPort: string;
9
+ } | {
10
+ readonly node: string;
11
+ readonly output: string;
12
+ } | {
13
+ readonly system: string;
14
+ } | {
15
+ readonly resource: string;
16
+ };
17
+ export interface MaterialGraphNode extends JsonObject {
18
+ id: string;
19
+ type: string;
20
+ typeVersion: string;
21
+ inputs: Record<string, GraphBinding>;
22
+ }
23
+ export interface MaterialGraph extends JsonObject {
24
+ $schema: string;
25
+ graphVersion: string;
26
+ nodeSet: string;
27
+ nodes: MaterialGraphNode[];
28
+ outputs: Record<string, {
29
+ readonly node: string;
30
+ readonly output: string;
31
+ }>;
32
+ }
33
+ export interface MaterialNodeDefinition {
34
+ readonly type: string;
35
+ readonly version: string;
36
+ readonly inputs: Readonly<Record<string, MaterialValueType>>;
37
+ readonly outputs: Readonly<Record<string, MaterialValueType>>;
38
+ readonly estimatedTextureSamples: number;
39
+ readonly estimatedPasses: number;
40
+ }
41
+ export interface ExecutableMaterialNode {
42
+ readonly id: string;
43
+ readonly type: string;
44
+ readonly typeVersion: string;
45
+ readonly inputs: Readonly<Record<string, GraphBinding>>;
46
+ }
47
+ export interface MaterialUniformBinding {
48
+ readonly name: string;
49
+ readonly type: 'float';
50
+ readonly source: {
51
+ readonly kind: 'parameter';
52
+ readonly id: string;
53
+ } | {
54
+ readonly kind: 'system';
55
+ readonly id: string;
56
+ };
57
+ }
58
+ export interface WebGl2MaterialProgram {
59
+ readonly backend: 'webgl2';
60
+ readonly nodeSet: string;
61
+ readonly graphHash: string;
62
+ readonly inputPorts: readonly string[];
63
+ readonly uniforms: readonly MaterialUniformBinding[];
64
+ readonly fragmentShader: string;
65
+ readonly webgpu?: WebGpuMaterialProgram;
66
+ readonly executionPlan: MaterialExecutionPlan;
67
+ readonly passes?: readonly WebGl2MaterialPass[];
68
+ }
69
+ export interface WebGl2MaterialPassInput {
70
+ readonly sampler: string;
71
+ readonly source: {
72
+ readonly kind: 'external';
73
+ readonly port: string;
74
+ } | {
75
+ readonly kind: 'pass';
76
+ readonly passId: string;
77
+ };
78
+ }
79
+ export interface WebGl2MaterialPass {
80
+ readonly id: string;
81
+ readonly inputs: readonly WebGl2MaterialPassInput[];
82
+ readonly uniforms: readonly MaterialUniformBinding[];
83
+ readonly fragmentShader: string;
84
+ }
85
+ export interface WebGpuMaterialProgram {
86
+ readonly backend: 'webgpu';
87
+ readonly nodeSet: string;
88
+ readonly graphHash: string;
89
+ readonly inputPorts: readonly string[];
90
+ readonly uniforms: readonly MaterialUniformBinding[];
91
+ readonly shader: string;
92
+ readonly executionPlan: MaterialExecutionPlan;
93
+ }
94
+ export interface MaterialExecutionPass {
95
+ readonly id: string;
96
+ readonly kind: 'draw' | 'blur-horizontal' | 'blur-vertical';
97
+ readonly nodes: readonly string[];
98
+ readonly estimatedTextureSamples: number;
99
+ }
100
+ export interface MaterialExecutionPlan {
101
+ readonly passes: readonly MaterialExecutionPass[];
102
+ readonly intermediateTextureCount: number;
103
+ }
104
+ export interface MaterialGraphBudget {
105
+ readonly maxNodes: number;
106
+ readonly maxDepth: number;
107
+ readonly maxPasses: number;
108
+ readonly maxTextureSamples: number;
109
+ }
110
+ export interface CompiledMaterialGraph {
111
+ readonly nodeSet: string;
112
+ readonly order: readonly string[];
113
+ readonly depth: number;
114
+ readonly estimatedPasses: number;
115
+ readonly estimatedTextureSamples: number;
116
+ readonly executableNodes: readonly ExecutableMaterialNode[];
117
+ readonly diagnostics: readonly Diagnostic[];
118
+ }
119
+ export interface CompileMaterialOptions {
120
+ readonly parameters: Readonly<Record<string, MaterialValueType>>;
121
+ /** Values for parameters whose Definition declares `affects: specialization`. */
122
+ readonly specializationValues?: Readonly<Record<string, JsonValue>>;
123
+ readonly inputPorts: Readonly<Record<string, MaterialValueType>>;
124
+ readonly systems?: Readonly<Record<string, MaterialValueType>>;
125
+ readonly budget?: Partial<MaterialGraphBudget>;
126
+ }
127
+ export interface MaterialParameterDefinition {
128
+ readonly id: string;
129
+ readonly type: 'boolean' | 'integer' | 'float' | 'enum' | 'string';
130
+ readonly default: JsonValue;
131
+ readonly range?: {
132
+ readonly min?: number;
133
+ readonly max?: number;
134
+ };
135
+ readonly values?: readonly string[];
136
+ readonly animatable: boolean;
137
+ }
138
+ export interface MaterialPortDefinition {
139
+ readonly id: string;
140
+ readonly direction: 'input' | 'output';
141
+ readonly type: 'visual-frame' | 'mask' | 'depth' | 'motion-vectors';
142
+ readonly binding: 'host' | 'instance';
143
+ readonly required: boolean;
144
+ }
145
+ export interface MaterialResourceSlotDefinition {
146
+ readonly id: string;
147
+ readonly required: boolean;
148
+ readonly fallbackResourceId?: string;
149
+ }
150
+ export type MaterialImplementationDescriptor = {
151
+ readonly type: 'graph';
152
+ } | {
153
+ readonly type: 'shader';
154
+ readonly backend: 'webgpu' | 'webgl2';
155
+ } | {
156
+ readonly type: 'wasm';
157
+ };
158
+ export interface RuntimeMaterialDefinition {
159
+ readonly parameters: readonly MaterialParameterDefinition[];
160
+ readonly ports: readonly MaterialPortDefinition[];
161
+ readonly resourceSlots: readonly MaterialResourceSlotDefinition[];
162
+ readonly implementations: readonly MaterialImplementationDescriptor[];
163
+ }
164
+ export interface MaterialInstanceValue {
165
+ readonly parameters: Readonly<Record<string, JsonValue>>;
166
+ readonly resourceBindings?: Readonly<Record<string, JsonValue>>;
167
+ readonly inputBindings?: Readonly<Record<string, JsonValue>>;
168
+ }
169
+ export interface MaterialRuntimeSelectionOptions {
170
+ readonly backend: 'webgpu' | 'webgl2' | 'cpu';
171
+ readonly trust: 'declarative' | 'trusted-code';
172
+ readonly trustedCodeAuthorized?: boolean;
173
+ }
174
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEzE,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,MAAM,GAAG,cAAc,CAAC;AAElE,MAAM,MAAM,YAAY,GACpB;IAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;CAAE,GAC7B;IAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC3B;IAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAElC,MAAM,WAAW,iBAAkB,SAAQ,UAAU;IACnD,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,aAAc,SAAQ,UAAU;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC7E;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAC7D,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAC9D,QAAQ,CAAC,uBAAuB,EAAE,MAAM,CAAC;IACzC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC;CACzD;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,MAAM,EACX;QAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;QAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;KAAE,GACnD;QAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;QAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;CACtD;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,QAAQ,EAAE,SAAS,sBAAsB,EAAE,CAAC;IACrD,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,qBAAqB,CAAC;IACxC,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC;IAC9C,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,kBAAkB,EAAE,CAAC;CACjD;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EACX;QAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GACpD;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CACxD;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,SAAS,uBAAuB,EAAE,CAAC;IACpD,QAAQ,CAAC,QAAQ,EAAE,SAAS,sBAAsB,EAAE,CAAC;IACrD,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,QAAQ,EAAE,SAAS,sBAAsB,EAAE,CAAC;IACrD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC;CAC/C;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,eAAe,CAAC;IAC5D,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,uBAAuB,EAAE,MAAM,CAAC;CAC1C;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,MAAM,EAAE,SAAS,qBAAqB,EAAE,CAAC;IAClD,QAAQ,CAAC,wBAAwB,EAAE,MAAM,CAAC;CAC3C;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;CACpC;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,uBAAuB,EAAE,MAAM,CAAC;IACzC,QAAQ,CAAC,eAAe,EAAE,SAAS,sBAAsB,EAAE,CAAC;IAC5D,QAAQ,CAAC,WAAW,EAAE,SAAS,UAAU,EAAE,CAAC;CAC7C;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC;IACjE,iFAAiF;IACjF,QAAQ,CAAC,oBAAoB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;IACpE,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC;IACjE,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAC/D,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC;CAChD;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;IACnE,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAClE,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;CAC9B;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,SAAS,EAAE,OAAO,GAAG,QAAQ,CAAC;IACvC,QAAQ,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,GAAG,OAAO,GAAG,gBAAgB,CAAC;IACpE,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IACtC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CACtC;AAED,MAAM,MAAM,gCAAgC,GACxC;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAC1B;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,QAAQ,CAAA;CAAE,GAClE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9B,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,UAAU,EAAE,SAAS,2BAA2B,EAAE,CAAC;IAC5D,QAAQ,CAAC,KAAK,EAAE,SAAS,sBAAsB,EAAE,CAAC;IAClD,QAAQ,CAAC,aAAa,EAAE,SAAS,8BAA8B,EAAE,CAAC;IAClE,QAAQ,CAAC,eAAe,EAAE,SAAS,gCAAgC,EAAE,CAAC;CACvE;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;IACzD,QAAQ,CAAC,gBAAgB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;IAChE,QAAQ,CAAC,aAAa,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;CAC9D;AAED,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,QAAQ,GAAG,KAAK,CAAC;IAC9C,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,cAAc,CAAC;IAC/C,QAAQ,CAAC,qBAAqB,CAAC,EAAE,OAAO,CAAC;CAC1C"}
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import type { CompileMaterialOptions, MaterialGraph, WebGl2MaterialProgram } from './types.js';
2
+ export declare function compileMaterialGraphToWebGl2(graph: MaterialGraph, options: CompileMaterialOptions): WebGl2MaterialProgram;
3
+ //# sourceMappingURL=webgl2.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webgl2.d.ts","sourceRoot":"","sources":["../src/webgl2.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,sBAAsB,EAEtB,aAAa,EAEb,qBAAqB,EAEtB,MAAM,YAAY,CAAC;AAySpB,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,sBAAsB,GAC9B,qBAAqB,CA+FvB"}
package/dist/webgl2.js ADDED
@@ -0,0 +1,366 @@
1
+ import { buildMaterialExecutionPlan, compileMaterialGraph } from './compiler.js';
2
+ import { compileMaterialGraphToWebGpu } from './webgpu.js';
3
+ function identifier(value) {
4
+ return value.replaceAll(/[^a-zA-Z0-9_]/gu, '_');
5
+ }
6
+ function literal(value) {
7
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
8
+ throw new TypeError('WebGL2 material expressions only support finite numeric literals');
9
+ }
10
+ return Number.isInteger(value) ? `${value.toString()}.0` : value.toString();
11
+ }
12
+ function graphHash(graph) {
13
+ const serialized = JSON.stringify(graph);
14
+ let hash = 2_166_136_261;
15
+ for (let index = 0; index < serialized.length; index += 1) {
16
+ hash ^= serialized.charCodeAt(index);
17
+ hash = Math.imul(hash, 16_777_619);
18
+ }
19
+ return (hash >>> 0).toString(16).padStart(8, '0');
20
+ }
21
+ function uniformFor(binding, uniforms) {
22
+ if ('parameter' in binding) {
23
+ const name = `u_parameter_${identifier(binding.parameter)}`;
24
+ uniforms.set(name, {
25
+ name,
26
+ type: 'float',
27
+ source: { kind: 'parameter', id: binding.parameter },
28
+ });
29
+ return name;
30
+ }
31
+ if ('system' in binding) {
32
+ const name = `u_system_${identifier(binding.system)}`;
33
+ uniforms.set(name, { name, type: 'float', source: { kind: 'system', id: binding.system } });
34
+ return name;
35
+ }
36
+ return undefined;
37
+ }
38
+ function bindingExpression(binding, nodeExpressions, inputPorts, uniforms) {
39
+ if ('value' in binding)
40
+ return literal(binding.value);
41
+ const uniform = uniformFor(binding, uniforms);
42
+ if (uniform !== undefined)
43
+ return uniform;
44
+ if ('inputPort' in binding) {
45
+ inputPorts.add(binding.inputPort);
46
+ return `texture(u_input_${identifier(binding.inputPort)}, v_uv)`;
47
+ }
48
+ if ('node' in binding) {
49
+ const expression = nodeExpressions.get(binding.node);
50
+ if (expression === undefined)
51
+ throw new TypeError(`Node ${binding.node} has no expression`);
52
+ return expression;
53
+ }
54
+ throw new TypeError('Resource bindings are not executable in the Phase 0 WebGL2 backend');
55
+ }
56
+ function nodeExpression(type, input, binding, options) {
57
+ switch (type) {
58
+ case 'time.transition-curve': {
59
+ const curveBinding = binding('curve');
60
+ const curve = 'value' in curveBinding
61
+ ? curveBinding.value
62
+ : 'parameter' in curveBinding
63
+ ? options.specializationValues?.[curveBinding.parameter]
64
+ : undefined;
65
+ if (curve === 'linear')
66
+ return `clamp(${input('progress')}, 0.0, 1.0)`;
67
+ if (curve === 'smooth') {
68
+ return `smoothstep(0.0, 1.0, clamp(${input('progress')}, 0.0, 1.0))`;
69
+ }
70
+ throw new TypeError('time.transition-curve requires a linear or smooth specialization value');
71
+ }
72
+ case 'composite.mix':
73
+ return `mix(${input('a')}, ${input('b')}, clamp(${input('amount')}, 0.0, 1.0))`;
74
+ case 'color.temperature':
75
+ return `aelion_temperature(${input('source')}, ${input('amount')})`;
76
+ case 'color.lift-black':
77
+ return `aelion_lift_black(${input('source')}, ${input('amount')})`;
78
+ case 'color.scale-rgb':
79
+ return `aelion_scale_rgb(${input('source')}, ${input('scale')})`;
80
+ case 'composite.screen':
81
+ return `aelion_screen(${input('base')}, ${input('overlay')})`;
82
+ case 'math.add':
83
+ return `(${input('a')} + ${input('b')})`;
84
+ case 'math.subtract':
85
+ return `(${input('a')} - ${input('b')})`;
86
+ case 'math.multiply':
87
+ return `(${input('a')} * ${input('b')})`;
88
+ case 'math.divide':
89
+ return `(${input('a')} / ${input('b')})`;
90
+ case 'math.clamp':
91
+ return `clamp(${input('value')}, ${input('min')}, ${input('max')})`;
92
+ case 'math.smoothstep':
93
+ return `smoothstep(${input('edge0')}, ${input('edge1')}, ${input('x')})`;
94
+ case 'color.exposure':
95
+ return `aelion_scale_rgb(${input('source')}, exp2(${input('stops')}))`;
96
+ case 'color.contrast':
97
+ return `aelion_contrast(${input('source')}, ${input('amount')})`;
98
+ case 'color.saturation':
99
+ return `aelion_saturation(${input('source')}, ${input('amount')})`;
100
+ case 'color.invert':
101
+ return `aelion_invert(${input('source')})`;
102
+ case 'composite.multiply':
103
+ return `aelion_multiply(${input('base')}, ${input('overlay')})`;
104
+ case 'composite.add':
105
+ return `aelion_add(${input('base')}, ${input('overlay')})`;
106
+ default:
107
+ throw new TypeError(`Node ${type} is not supported by the Phase 0 WebGL2 backend`);
108
+ }
109
+ }
110
+ const HELPERS = `
111
+ vec4 aelion_temperature(vec4 color, float amount) {
112
+ vec3 scale = vec3(1.0 + amount, 1.0 + amount * 0.15, 1.0 - amount * 0.55);
113
+ return vec4(max(color.rgb * scale, vec3(0.0)), color.a);
114
+ }
115
+ vec4 aelion_lift_black(vec4 color, float amount) {
116
+ return vec4(mix(color.rgb, vec3(1.0), clamp(amount, 0.0, 1.0)), color.a);
117
+ }
118
+ vec4 aelion_scale_rgb(vec4 color, float scale) {
119
+ return vec4(max(color.rgb * scale, vec3(0.0)), color.a);
120
+ }
121
+ vec4 aelion_screen(vec4 base, vec4 overlay) {
122
+ return vec4(1.0 - (1.0 - base.rgb) * (1.0 - overlay.rgb), base.a + overlay.a - base.a * overlay.a);
123
+ }
124
+ vec4 aelion_contrast(vec4 color, float amount) {
125
+ return vec4((color.rgb - vec3(0.5)) * amount + vec3(0.5), color.a);
126
+ }
127
+ vec4 aelion_saturation(vec4 color, float amount) {
128
+ float luma = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722));
129
+ return vec4(mix(vec3(luma), color.rgb, amount), color.a);
130
+ }
131
+ vec4 aelion_invert(vec4 color) { return vec4(color.a - color.rgb, color.a); }
132
+ vec4 aelion_multiply(vec4 base, vec4 overlay) { return vec4(base.rgb * overlay.rgb, base.a * overlay.a); }
133
+ vec4 aelion_add(vec4 base, vec4 overlay) { return min(base + overlay, vec4(1.0)); }
134
+ `;
135
+ function parameterUniform(id) {
136
+ return {
137
+ name: `u_parameter_${identifier(id)}`,
138
+ type: 'float',
139
+ source: { kind: 'parameter', id },
140
+ };
141
+ }
142
+ function numericParameter(binding, context) {
143
+ if (!('parameter' in binding)) {
144
+ throw new TypeError(`${context} must bind a numeric Material parameter`);
145
+ }
146
+ return binding.parameter;
147
+ }
148
+ function inputPort(binding, context) {
149
+ if (!('inputPort' in binding))
150
+ throw new TypeError(`${context} must bind a host input port`);
151
+ return binding.inputPort;
152
+ }
153
+ function nodeReference(binding, expected, context) {
154
+ if (!('node' in binding) || binding.node !== expected) {
155
+ throw new TypeError(`${context} must consume node ${expected}`);
156
+ }
157
+ }
158
+ function softGlowPasses(graph) {
159
+ const highlight = graph.nodes.find(node => node.type === 'color.extract-highlights');
160
+ const blur = graph.nodes.find(node => node.type === 'blur.gaussian');
161
+ const scale = graph.nodes.find(node => node.type === 'color.scale-rgb');
162
+ const composite = graph.nodes.find(node => node.type === 'composite.screen');
163
+ const hasMultiPassNode = graph.nodes.some(node => node.type === 'blur.gaussian');
164
+ if (!hasMultiPassNode)
165
+ return undefined;
166
+ if (highlight === undefined ||
167
+ blur === undefined ||
168
+ scale === undefined ||
169
+ composite === undefined) {
170
+ throw new TypeError('This multi-pass graph is not supported by the Phase 0 WebGL2 planner');
171
+ }
172
+ const sourceBinding = highlight.inputs.source;
173
+ const thresholdBinding = highlight.inputs.threshold;
174
+ const blurSource = blur.inputs.source;
175
+ const radiusBinding = blur.inputs.radiusPx;
176
+ const scaleSource = scale.inputs.source;
177
+ const intensityBinding = scale.inputs.scale;
178
+ const compositeBase = composite.inputs.base;
179
+ const compositeOverlay = composite.inputs.overlay;
180
+ if (sourceBinding === undefined ||
181
+ thresholdBinding === undefined ||
182
+ blurSource === undefined ||
183
+ radiusBinding === undefined ||
184
+ scaleSource === undefined ||
185
+ intensityBinding === undefined ||
186
+ compositeBase === undefined ||
187
+ compositeOverlay === undefined) {
188
+ throw new TypeError('Soft Glow graph is missing a required binding');
189
+ }
190
+ const source = inputPort(sourceBinding, 'Highlight source');
191
+ const base = inputPort(compositeBase, 'Composite base');
192
+ if (source !== base)
193
+ throw new TypeError('Soft Glow source and composite base must match');
194
+ nodeReference(blurSource, highlight.id, 'Blur source');
195
+ nodeReference(scaleSource, blur.id, 'Glow scale source');
196
+ nodeReference(compositeOverlay, scale.id, 'Composite overlay');
197
+ const threshold = numericParameter(thresholdBinding, 'Highlight threshold');
198
+ const radius = numericParameter(radiusBinding, 'Blur radius');
199
+ const intensity = numericParameter(intensityBinding, 'Glow intensity');
200
+ const thresholdUniform = parameterUniform(threshold);
201
+ const radiusUniform = parameterUniform(radius);
202
+ const intensityUniform = parameterUniform(intensity);
203
+ const preamble = `#version 300 es
204
+ precision highp float;
205
+ in vec2 v_uv;
206
+ out vec4 out_color;`;
207
+ const blurBody = (axis) => `${preamble}
208
+ uniform sampler2D u_input_source;
209
+ uniform float ${radiusUniform.name};
210
+ void main() {
211
+ vec2 texel = 1.0 / vec2(textureSize(u_input_source, 0));
212
+ float radius = clamp(${radiusUniform.name}, 0.0, 64.0);
213
+ vec2 stepUv = texel * vec2(${axis === 'x' ? 'radius / 4.0, 0.0' : '0.0, radius / 4.0'});
214
+ vec4 color = texture(u_input_source, v_uv) * 0.227027;
215
+ color += texture(u_input_source, v_uv + stepUv) * 0.1945946;
216
+ color += texture(u_input_source, v_uv - stepUv) * 0.1945946;
217
+ color += texture(u_input_source, v_uv + stepUv * 2.0) * 0.1216216;
218
+ color += texture(u_input_source, v_uv - stepUv * 2.0) * 0.1216216;
219
+ color += texture(u_input_source, v_uv + stepUv * 3.0) * 0.054054;
220
+ color += texture(u_input_source, v_uv - stepUv * 3.0) * 0.054054;
221
+ color += texture(u_input_source, v_uv + stepUv * 4.0) * 0.016216;
222
+ color += texture(u_input_source, v_uv - stepUv * 4.0) * 0.016216;
223
+ out_color = color;
224
+ }`;
225
+ return [
226
+ {
227
+ id: `${highlight.id}:draw`,
228
+ inputs: [{ sampler: 'source', source: { kind: 'external', port: source } }],
229
+ uniforms: [thresholdUniform],
230
+ fragmentShader: `${preamble}
231
+ uniform sampler2D u_input_source;
232
+ uniform float ${thresholdUniform.name};
233
+ void main() {
234
+ vec4 color = texture(u_input_source, v_uv);
235
+ float luma = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722));
236
+ float threshold = clamp(${thresholdUniform.name}, 0.0, 1.0);
237
+ float gate = smoothstep(max(0.0, threshold - 0.05), min(1.0, threshold + 0.05), luma);
238
+ out_color = vec4(color.rgb * gate, color.a * gate);
239
+ }`,
240
+ },
241
+ {
242
+ id: `${blur.id}:horizontal`,
243
+ inputs: [{ sampler: 'source', source: { kind: 'pass', passId: `${highlight.id}:draw` } }],
244
+ uniforms: [radiusUniform],
245
+ fragmentShader: blurBody('x'),
246
+ },
247
+ {
248
+ id: `${blur.id}:vertical`,
249
+ inputs: [{ sampler: 'source', source: { kind: 'pass', passId: `${blur.id}:horizontal` } }],
250
+ uniforms: [radiusUniform],
251
+ fragmentShader: blurBody('y'),
252
+ },
253
+ {
254
+ id: `${composite.id}:draw`,
255
+ inputs: [
256
+ { sampler: 'base', source: { kind: 'external', port: base } },
257
+ { sampler: 'overlay', source: { kind: 'pass', passId: `${blur.id}:vertical` } },
258
+ ],
259
+ uniforms: [intensityUniform],
260
+ fragmentShader: `${preamble}
261
+ uniform sampler2D u_input_base;
262
+ uniform sampler2D u_input_overlay;
263
+ uniform float ${intensityUniform.name};
264
+ void main() {
265
+ vec4 baseColor = texture(u_input_base, v_uv);
266
+ vec4 glow = texture(u_input_overlay, v_uv) * max(0.0, ${intensityUniform.name});
267
+ out_color = vec4(
268
+ 1.0 - (1.0 - baseColor.rgb) * (1.0 - glow.rgb),
269
+ baseColor.a + glow.a - baseColor.a * glow.a
270
+ );
271
+ }`,
272
+ },
273
+ ];
274
+ }
275
+ export function compileMaterialGraphToWebGl2(graph, options) {
276
+ const analysis = compileMaterialGraph(graph, options);
277
+ const errors = analysis.diagnostics.filter(value => value.severity === 'error');
278
+ if (errors.length > 0) {
279
+ throw new TypeError(`Material graph is not executable: ${errors.map(value => value.code).join(', ')}`);
280
+ }
281
+ const multiPasses = softGlowPasses(graph);
282
+ if (multiPasses !== undefined) {
283
+ const inputPorts = [
284
+ ...new Set(multiPasses.flatMap(pass => pass.inputs.flatMap(input => input.source.kind === 'external' ? [input.source.port] : []))),
285
+ ];
286
+ const uniforms = [
287
+ ...new Map(multiPasses.flatMap(pass => pass.uniforms).map(value => [value.name, value])).values(),
288
+ ];
289
+ const compiledHash = graphHash(graph);
290
+ return {
291
+ backend: 'webgl2',
292
+ nodeSet: graph.nodeSet,
293
+ graphHash: compiledHash,
294
+ inputPorts,
295
+ uniforms,
296
+ fragmentShader: multiPasses.at(-1)?.fragmentShader ?? '',
297
+ executionPlan: buildMaterialExecutionPlan(graph, analysis),
298
+ passes: multiPasses,
299
+ };
300
+ }
301
+ const nodes = new Map(graph.nodes.map(node => [node.id, node]));
302
+ const expressions = new Map();
303
+ const inputPorts = new Set();
304
+ const uniforms = new Map();
305
+ for (const id of analysis.order) {
306
+ const node = nodes.get(id);
307
+ if (node === undefined)
308
+ continue;
309
+ const input = (name) => {
310
+ const binding = node.inputs[name];
311
+ if (binding === undefined)
312
+ throw new TypeError(`Node ${node.id} is missing ${name}`);
313
+ return bindingExpression(binding, expressions, inputPorts, uniforms);
314
+ };
315
+ const binding = (name) => {
316
+ const value = node.inputs[name];
317
+ if (value === undefined)
318
+ throw new TypeError(`Node ${node.id} is missing ${name}`);
319
+ return value;
320
+ };
321
+ expressions.set(id, nodeExpression(node.type, input, binding, options));
322
+ }
323
+ const output = graph.outputs.result ?? Object.values(graph.outputs)[0];
324
+ if (output === undefined)
325
+ throw new TypeError('Material graph has no output');
326
+ const resultExpression = expressions.get(output.node);
327
+ if (resultExpression === undefined)
328
+ throw new TypeError('Material graph output is unavailable');
329
+ const samplerDeclarations = [...inputPorts]
330
+ .map(port => `uniform sampler2D u_input_${identifier(port)};`)
331
+ .join('\n');
332
+ const uniformDeclarations = [...uniforms.values()]
333
+ .map(value => `uniform float ${value.name};`)
334
+ .join('\n');
335
+ const compiledHash = graphHash({
336
+ ...graph,
337
+ ...(options.specializationValues === undefined
338
+ ? {}
339
+ : { specializationValues: options.specializationValues }),
340
+ });
341
+ let webgpu;
342
+ try {
343
+ webgpu = compileMaterialGraphToWebGpu(graph, options);
344
+ }
345
+ catch {
346
+ webgpu = undefined;
347
+ }
348
+ return {
349
+ backend: 'webgl2',
350
+ nodeSet: graph.nodeSet,
351
+ graphHash: compiledHash,
352
+ inputPorts: [...inputPorts],
353
+ uniforms: [...uniforms.values()],
354
+ executionPlan: buildMaterialExecutionPlan(graph, analysis),
355
+ fragmentShader: `#version 300 es
356
+ precision highp float;
357
+ ${samplerDeclarations}
358
+ ${uniformDeclarations}
359
+ in vec2 v_uv;
360
+ out vec4 out_color;
361
+ ${HELPERS}
362
+ void main() { out_color = ${resultExpression}; }
363
+ `,
364
+ ...(webgpu === undefined ? {} : { webgpu }),
365
+ };
366
+ }
@@ -0,0 +1,3 @@
1
+ import type { CompileMaterialOptions, MaterialGraph, WebGpuMaterialProgram } from './types.js';
2
+ export declare function compileMaterialGraphToWebGpu(graph: MaterialGraph, options: CompileMaterialOptions): WebGpuMaterialProgram;
3
+ //# sourceMappingURL=webgpu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webgpu.d.ts","sourceRoot":"","sources":["../src/webgpu.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,sBAAsB,EAEtB,aAAa,EAEb,qBAAqB,EACtB,MAAM,YAAY,CAAC;AAkJpB,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,sBAAsB,GAC9B,qBAAqB,CA0EvB"}