Use a shader graph as a node in other shaders (#303)
A calculation needed in several shaders (a greyscale, a noise, an outline) had to be built again in every one of them. A shader function keeps it as an asset of its own, called like a behavior is since #16. - New asset type `shaderfunction` ("Shader function"), edited in the Shader tab. The panel beside the graph shows no picture, the readout counts its inputs and outputs. - Two new nodes: `Function input` and `Function output`. Their declared ports, numbers or colours only, are the ports of every call. One of each per function, like Start and End. - The call is node `ShaderCall`, named after the function. The palette lists every function, it can be dragged in from the Assets panel, and a double-click opens it. - The compiler sets the function in where it is called (`inlineFunctions`). It renames the copied nodes `<call>/<node>` and turns an unconnected port into a constant. Passes, Time, the Edge cut and the corner walk then see a plain graph. - A problem inside a function is reported at the call, in the function's words. The function's own tab checks it on its own (`checkShaderFunction`). - `ShaderLibrary` keys a shader on its graph and on every function it calls, however deep, so a change reaches every caller. - The port commands mirror a new, renamed or removed port into every call, as for a behavior's Start and End. `NodeDef.portKinds` holds the two ends to Number and Color. - As agreed in the issue: no `Property`, `Color property`, Output or Vertex output in a function, and `Time` is the caller's clock. - Own decision: a `Pass` may stand in a function. It becomes a pass of the calling shader, so a wide blur can be a function. - A function calling itself, also through another, says so at the node and is not set in. The palette does not offer such a call. - The Shader tab showed only the compiler's problems. A call nothing reaches was never compiled and looked healthy, so a call now also shows its own problem there, as in a behavior. Tests: - Gate: 228 files, 3510 tests, CHECK EXIT 0 (+13: 11 in tests/core/shader-functions.test.ts, 2 for the new icon) - e2e 28: 100 checks, all passed (+3 in 28l-viii). Called and in place both #606060/#a2a2a2, halved in the function #303030/#515151. - Counter-check: a fingerprint without the called functions fails 1 unit test, and in the browser the change check (#606060 stays). - The first e2e runs found two faults, both fixed: a double-click only selected the function, and an unreached call of itself showed no problem. - e2e total 1728 is computed (1725 + 3). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2e55071fa5
commit
a526e1b40f
28 files changed
+1091
-83
No files matched your search
+19
-2
@@ -277,6 +277,15 @@ export interface ShaderAsset extends AssetBase {
|
||||
graph: Graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* A part of a shader graph used in others (#303): its `Function input` and `Function output` nodes
|
||||
* are the ports of every node calling it, and the compiler sets the graph in at each call.
|
||||
*/
|
||||
export interface ShaderFunctionAsset extends AssetBase {
|
||||
type: 'shaderfunction';
|
||||
graph: Graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* `file` names the typeface in `data/fonts/` the way a texture names its image: content-addressed
|
||||
* `{sha256}.{ext}`, empty when the upload failed. A text object wears it through its `font`
|
||||
@@ -545,6 +554,7 @@ export type Asset =
|
||||
| TilemapAsset
|
||||
| StateMachineAsset
|
||||
| ShaderAsset
|
||||
| ShaderFunctionAsset
|
||||
| FontAsset
|
||||
| SoundAsset
|
||||
| ParticlesAsset;
|
||||
@@ -557,9 +567,16 @@ export function isVariableOwner(asset: Asset | undefined): asset is VariableOwne
|
||||
}
|
||||
|
||||
/** The assets edited as a node graph – what the graph canvas and the graph commands work on. */
|
||||
export type GraphAsset = BehaviorAsset | StateMachineAsset | ShaderAsset | ParticlesAsset;
|
||||
export type GraphAsset =
|
||||
BehaviorAsset | StateMachineAsset | ShaderAsset | ShaderFunctionAsset | ParticlesAsset;
|
||||
|
||||
const GRAPH_TYPES: ReadonlySet<AssetType> = new Set(['behavior', 'statemachine', 'shader', 'particles']);
|
||||
const GRAPH_TYPES: ReadonlySet<AssetType> = new Set([
|
||||
'behavior',
|
||||
'statemachine',
|
||||
'shader',
|
||||
'shaderfunction',
|
||||
'particles',
|
||||
]);
|
||||
|
||||
export function isGraphAsset(asset: Asset): asset is GraphAsset {
|
||||
return GRAPH_TYPES.has(asset.type);
|
||||
|
||||
@@ -21,6 +21,7 @@ export const ASSET_TYPES = [
|
||||
'tilemap',
|
||||
'statemachine',
|
||||
'shader',
|
||||
'shaderfunction',
|
||||
'font',
|
||||
'sound',
|
||||
'particles',
|
||||
@@ -51,6 +52,7 @@ export const ASSET_TYPE_LABELS: Record<AssetType, string> = {
|
||||
tilemap: 'Tilemap',
|
||||
statemachine: 'State machine',
|
||||
shader: 'Shader',
|
||||
shaderfunction: 'Shader function',
|
||||
font: 'Font',
|
||||
sound: 'Sound',
|
||||
particles: 'Particles',
|
||||
@@ -64,6 +66,7 @@ export const ASSET_TYPE_LABELS: Record<AssetType, string> = {
|
||||
* what a tilemap draws with, so it is placed no more directly than a texture is – what is placed
|
||||
* is the tilemap. A curve is the way a key or a `Lerp` blends, picked by id like a state machine.
|
||||
* A struct is a shape that variables and fields take on (#253), nothing that exists on its own.
|
||||
* A shader function is part of other shaders (#303), worn by nothing itself.
|
||||
*/
|
||||
export type PlaceableAssetType = Exclude<
|
||||
AssetType,
|
||||
@@ -77,6 +80,7 @@ export type PlaceableAssetType = Exclude<
|
||||
| 'tile'
|
||||
| 'statemachine'
|
||||
| 'shader'
|
||||
| 'shaderfunction'
|
||||
| 'font'
|
||||
| 'sound'
|
||||
| 'variables'
|
||||
@@ -94,6 +98,7 @@ const NOT_PLACEABLE: ReadonlySet<AssetType> = new Set([
|
||||
'tile',
|
||||
'statemachine',
|
||||
'shader',
|
||||
'shaderfunction',
|
||||
'font',
|
||||
'sound',
|
||||
'variables',
|
||||
|
||||
@@ -9,6 +9,14 @@ import { fieldTypings, typeFields, typingOf } from './structTypes';
|
||||
import { findAsset, findAssetOf, type Project } from './Project';
|
||||
import { resolvePicture } from './texture';
|
||||
|
||||
/** The project's shader functions by id, as a call and the compiler see them (#303). */
|
||||
export function shaderFunctionsOf(project: Project): GraphLookup['shaderFunction'] {
|
||||
return (assetId) => {
|
||||
const asset = findAssetOf(project, assetId, 'shaderfunction');
|
||||
return asset ? { name: asset.name, graph: asset.graph } : null;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* What a graph may see of the project around it. A behavior's `defaults` are its variable values;
|
||||
* every call starts from a copy of them, except for the variables it keeps (#229).
|
||||
@@ -25,6 +33,7 @@ export function graphLookup(project: Project): GraphLookup {
|
||||
kept: asset.keptVariables ?? [],
|
||||
};
|
||||
},
|
||||
shaderFunction: shaderFunctionsOf(project),
|
||||
machine: (assetId) => {
|
||||
const asset = findAssetOf(project, assetId, 'statemachine');
|
||||
if (!asset) return null;
|
||||
|
||||
Reference in new issue
Block a user