Describe shader functions (#303)

Shaders gets the section Shader functions, three node rows and the code notes. Concepts names the new asset type, Development the e2e total 1728.
admin committed 2026-10-03 17:10:09 +02:00
1 parent 986aad5bd7
commit 9823f414cd
3 files changed
+59 -4

No files matched your search

+2 -2
@@ -1,12 +1,12 @@
| Term | Meaning | | Term | Meaning |
|---|---| |---|---|
| **Account** | Who you are signed in as. The editor asks for a user name and a password before it loads anything, and every project belongs to one account — see [Accounts](Accounts). The **Account** menu is at the far right of the toolbar and holds the name, *Change password* and *Logout*; signing in and out changes what the canvas draws, it never reloads the page. | | **Account** | Who you are signed in as. The editor asks for a user name and a password before it loads anything, and every project belongs to one account — see [Accounts](Accounts). The **Account** menu is at the far right of the toolbar and holds the name, *Change password* and *Logout*; signing in and out changes what the canvas draws, it never reloads the page. |
| **Asset** | Named thing with a type (`scene`, `camera`, `clip`, `sprite`, `text`, `behavior`, `collider`, `container`, `texture`, `animation`, `statemachine`, `shader`, `font`, `sound`, `particles`, `variables`) and typed properties. Assets have **no position**. | | **Asset** | Named thing with a type (`scene`, `camera`, `clip`, `sprite`, `text`, `behavior`, `collider`, `container`, `texture`, `animation`, `statemachine`, `shader`, `shaderfunction`, `font`, `sound`, `particles`, `variables`) and typed properties. Assets have **no position**. |
| **Texture** | An uploaded image. Scenes, textures, animations, state machines, shaders, fonts, sounds and Variables assets are the eight types that cannot be placed: a texture has no position, it is *worn* by a sprite through the ordinary property `texture`. See [Textures](Textures). | | **Texture** | An uploaded image. Scenes, textures, animations, state machines, shaders, fonts, sounds and Variables assets are the eight types that cannot be placed: a texture has no position, it is *worn* by a sprite through the ordinary property `texture`. See [Textures](Textures). |
| **Font** | An uploaded typeface, worn by a text through the ordinary property `font`; no font means the editor's own. Uploaded and picked exactly like a texture, and stored the same way. See [Fonts](Fonts). | | **Font** | An uploaded typeface, worn by a text through the ordinary property `font`; no font means the editor's own. Uploaded and picked exactly like a texture, and stored the same way. See [Fonts](Fonts). |
| **Sound** | An uploaded audio file. Uploaded, picked and stored exactly like a typeface — but nothing *wears* it: it is neither on screen nor in the scene, and a graph plays it with `Play sound`. Its own rows say how loud it is and whether it loops; the node scales the one and can overrule the other either way. See [Sounds](Sounds). | | **Sound** | An uploaded audio file. Uploaded, picked and stored exactly like a typeface — but nothing *wears* it: it is neither on screen nor in the scene, and a graph plays it with `Play sound`. Its own rows say how loud it is and whether it loops; the node scales the one and can overrule the other either way. See [Sounds](Sounds). |
| **State machine** | A named set of **states**, edited as nodes in its own tab. A behavior puts it into a state with `Set state`, moves it along a named transition with `State transition` and branches on it with `State branch`. Its **Scope** says where the current state lives: **Local** gives every behavior instance one of its own, **Global** keeps a single one for the whole run. See [State machines](State-machines). | | **State machine** | A named set of **states**, edited as nodes in its own tab. A behavior puts it into a state with `Set state`, moves it along a named transition with `State transition` and branches on it with `State branch`. Its **Scope** says where the current state lives: **Local** gives every behavior instance one of its own, **Global** keeps a single one for the whole run. See [State machines](State-machines). |
| **Shader** | A node graph that becomes the GLSL a sprite is drawn with, edited in its own tab. A sprite, a text or a particle source wears it through `shader`; the numbers the graph declares are rows below that dropdown. See [Shaders](Shaders). | | **Shader** | A node graph that becomes the GLSL a sprite is drawn with, edited in its own tab. A sprite, a text or a particle source wears it through `shader`; the numbers the graph declares are rows below that dropdown. A **shader function** is a part of such a graph kept on its own and used as a node in other shaders. See [Shaders](Shaders). |
| **Particles** | A node graph that says what *one* particle is as a function of its age and of a random number drawn once for it — no forces, no simulation. Unlike the other graph assets it **is** placeable: the object made from it is a source, whose rows say how many go out and for how long they live. Because a particle is a formula, the same second always draws the same picture and the tab's slider can jump to any of them exactly. See [Particles](Particles). | | **Particles** | A node graph that says what *one* particle is as a function of its age and of a random number drawn once for it — no forces, no simulation. Unlike the other graph assets it **is** placeable: the object made from it is a source, whose rows say how many go out and for how long they live. Because a particle is a formula, the same second always draws the same picture and the tab's slider can jump to any of them exactly. See [Particles](Particles). |
| **Text** | An asset that draws a line of text (`text`, `font`, `fontSize`, `color`, `align`) — titles, HUD readouts, button labels. Its content is an ordinary property, so a `Properties` node of type Text with `text` switched to Set changes it while playing. | | **Text** | An asset that draws a line of text (`text`, `font`, `fontSize`, `color`, `align`) — titles, HUD readouts, button labels. Its content is an ordinary property, so a `Properties` node of type Text with `text` switched to Set changes it while playing. |
| **Enabled** | Every placeable asset has the switch `enabled` (default on). A disabled object is not drawn, does not collide and its behavior does not run — **and neither does anything parented below it**. Switching one instance off is a normal override, which makes a container a *screen*: switch it on to show it, off to hide it. From a graph, `Properties` with `enabled` switched to Set does it, and `Is enabled` asks (including the parents); a behavior hears the switch as `On enable` / `On disable`. | | **Enabled** | Every placeable asset has the switch `enabled` (default on). A disabled object is not drawn, does not collide and its behavior does not run — **and neither does anything parented below it**. Switching one instance off is a normal override, which makes a container a *screen*: switch it on to show it, off to hide it. From a graph, `Properties` with `enabled` switched to Set does it, and `Is enabled` asks (including the parents); a behavior hears the switch as `On enable` / `On disable`. |
+1 -1
@@ -18,7 +18,7 @@ php tests/php/accounts.test.php # the API's accoun
# has and the web server's container has not (SKIP there) # has and the web server's container has not (SKIP there)
php tests/php/snapshots.test.php # the snapshot store (#231), the same way php tests/php/snapshots.test.php # the snapshot store (#231), the same way
php tests/php/releases.test.php # the release store (#266), the same way php tests/php/releases.test.php # the release store (#266), the same way
node tools/seed-e2e.mjs && node tools/e2e.mjs # 1725 editor checks, screenshots in /tmp/gm-e2e-*.png node tools/seed-e2e.mjs && node tools/e2e.mjs # 1728 editor checks, screenshots in /tmp/gm-e2e-*.png
GM_STEPS=20 node tools/e2e.mjs # or 1-12, or 6,20 — a subset while working on one step GM_STEPS=20 node tools/e2e.mjs # or 1-12, or 6,20 — a subset while working on one step
./e2e.sh start [GM_STEPS] # the same run detached, see below ./e2e.sh start [GM_STEPS] # the same run detached, see below
./e2e.sh status # live progress, or the last run's result ./e2e.sh status # live progress, or the last run's result
+56 -1
@@ -96,6 +96,9 @@ plugged into a colour port, straight from a `Number` or a `Luminance` as well.
| **Pow** | in `value` (any), `exponent` (number) · out `result` | `value` to the power of `exponent`: 2 squares it, 0.5 is the square root; an easing curve on `u`, a gamma on a colour. Below 0 the value counts as 0, because GLSL leaves `pow` undefined there (0 to a power of 0 or less is undefined too). Per channel for colours. | | **Pow** | in `value` (any), `exponent` (number) · out `result` | `value` to the power of `exponent`: 2 squares it, 0.5 is the square root; an easing curve on `u`, a gamma on a colour. Below 0 the value counts as 0, because GLSL leaves `pow` undefined there (0 to a power of 0 or less is undefined too). Per channel for colours. |
| **Sqrt** | in `value` (any) · out `result` | The square root: 0.25 becomes 0.5, 4 becomes 2. The same as `Pow` with exponent 0.5, in one node. Below 0 the value counts as 0, because GLSL leaves `sqrt` undefined there. Per channel for colours. | | **Sqrt** | in `value` (any) · out `result` | The square root: 0.25 becomes 0.5, 4 becomes 2. The same as `Pow` with exponent 0.5, in one node. Below 0 the value counts as 0, because GLSL leaves `sqrt` undefined there. Per channel for colours. |
| **Clamp** | in `value` (any), `min`, `max` (number) · out `result` | Holds the value inside `min`..`max`, per channel for colours. The Output clamps the colour it is given anyway; this one holds a value *inside* the graph, before it is added to or multiplied. | | **Clamp** | in `value` (any), `min`, `max` (number) · out `result` | Holds the value inside `min`..`max`, per channel for colours. The Output clamps the colour it is given anyway; this one holds a value *inside* the graph, before it is added to or multiplied. |
| **Function input** | out: what you declare | Only in a shader function: the values a shader calling the function hands in. Every input declared in the inspector (`+ Input`, a number or a colour) is an output here and an input on every call. One per function. See *Shader functions*. |
| **Function output** | in: what you declare | Only in a shader function: what it hands back. Every output declared here is an output on every call; unconnected, it is the value typed on it. One per function. |
| *a shader function* | its declared inputs · its declared outputs | Listed in the palette under the function's name: the function's graph, set in here. A double-click opens it. |
An input with nothing to type on it (the Output's two, Luminance's colour) that is left unconnected An input with nothing to type on it (the Output's two, Luminance's colour) that is left unconnected
reads the texel: its colour for a colour port, its alpha for the Output's `alpha`. That is why the reads the texel: its colour for a colour port, its alpha for the Output's `alpha`. That is why the
@@ -294,6 +297,47 @@ Pass and blurred down, is the same 5x5 spot of 1/25 each that a `Blur` *Both* dr
231/60/60, red once halved in each; two Passes in a row, the first the very program of an earlier 231/60/60, red once halved in each; two Passes in a row, the first the very program of an earlier
graph's only Pass, still show the picture and not white. graph's only Pass, still show the picture and not white.
## Shader functions
A **shader function** (#303) is a part of a shader graph kept as an asset of its own, so the same
calculation (a greyscale, a noise, a vignette, an outline of eight samples) is built once and used in
any number of shaders. `+` in the Assets panel → **Shader function** makes one; it opens in the
Shader tab like a shader. It has no Output, no wearer and no preview of its own: the panel beside
the graph says *A function shows in the shaders calling it*, and the readout counts its inputs and
outputs.
- **Its ports** are declared on its two ends: **Function input** says what a caller hands in,
**Function output** what comes back. Select one and add ports in the inspector, each a *Number* or
a *Color*; nothing else can be declared. Every port is at once a port of every node calling the
function, in every shader: a new input grows those nodes, a renamed one keeps their edges, a removed
one takes its edges along, and an undo takes all of it back.
- **Calling it:** every shader function is in the Shader tab's palette under its own name, and can be
dragged in from the Assets panel. The node carries the function's name; an input left unconnected is
the value typed on it. A double-click on the node opens the function.
- **Set in, not called.** The compiler copies the function's graph into the shader where the node
stands, as if its nodes had been placed there, so it costs exactly what the same nodes cost in place.
Calling a function twice copies it twice. The nodes that read the picture (`Texture color`,
`Sample texture`, `Blur`, ...) read the picture of whatever wears the calling shader, and `Time` is the
same clock. A Pass inside a function is a pass of the calling shader (a wide blur can be a function).
- **What it may hold:** everything a shader holds except the Output, the Vertex output, `Property` and
`Color property`. Values come in through the inputs; the calling shader hangs its own properties on
them, so it stays clear whose row a property is. Those nodes are not in a function's palette, and one
pasted or left in says *A function cannot hold ...*. The two ends are not in a shader's palette.
- **Changes reach every caller.** Every shader calling a function compiles again when the function
changes, however deep the call sits.
- **Problems** inside a function show on its own nodes in its tab, and at every call, in the
function's words: *In Grayscale: "level" needs a number, not a color*. A function calling itself,
directly or through another, says *A function cannot call itself* and is not set in; the palette
does not offer such a call in the first place.
A function is part of the shader graph only: a behavior cannot call it, since a behavior runs on the
processor and a shader on the graphics card.
Measured in the checks (step 28): a function "Grayscale" (a colour in, `Luminance` out as a colour),
called between `Texture color` and the Output, draws the same pixels as the same nodes in place, byte
for byte; a `Multiply` by 0.5 put into the function halves the sprite's grey without the shader being
touched; a double-click on the call opens the function, and a call of itself inside it is marked.
## Moving the corners ## Moving the corners
Everything above decides what colour a pixel gets. A **Vertex output** node decides where the Everything above decides what colour a pixel gets. A **Vertex output** node decides where the
@@ -571,8 +615,19 @@ transform from world units to that picture. The Game tab and the game link then
off the stage and let the picture stand in for it; the Scene tab leaves it drawn, since it shows off the stage and let the picture stand in for it; the Scene tab leaves it drawn, since it shows
more than the frame, and lays the picture over it below the symbols (`shadingCamera` and more than the frame, and lays the picture over it below the symbols (`shadingCamera` and
`keepsWorldAroundShader` in `WorldViewport`). `keepsWorldAroundShader` in `WorldViewport`).
A shader function (`src/core/shader/functions.ts`) never reaches the walk as a node:
`inlineFunctions` first replaces every call by a copy of the function's graph, its node ids prefixed
with the call's (`<call>/<node>`), wires what fed the call's inputs to what the Function input fed,
and what read its outputs to what fed the Function output; an unconnected port becomes a `Number` or
`Color` node with the typed value. Everything after that (passes, Time, the Edge cut, the corner
walk) sees a plain graph. A problem on a copied node is moved back onto the call
(`problemsAtCalls`). The function's own tab walks it from its Function output with any value at its
input (`checkShaderFunction`). The call's ports come from the function through `GraphLookup`
(`shaderFunction`), and the port commands mirror a change at a function's end into every call, as
they do for a behavior's Start and End.
`src/viewport/ShaderLibrary.ts` caches the compiled graph per asset, keyed by a fingerprint of what `src/viewport/ShaderLibrary.ts` caches the compiled graph per asset, keyed by a fingerprint of what
the fragment depends on — node ids, types, params, declared ports, and which port feeds which — so the fragment depends on — node ids, types, params, declared ports, and which port feeds which, and
the same of every function it calls (`functionsFingerprint`) — so
moving, collapsing or folding compiles nothing again; a changed param or link does, once moving, collapsing or folding compiles nothing again; a changed param or link does, once
(`__gm.shaderCompiles()` counts it for the checks). A sprite's picture is a `Mesh` (`__gm.shaderCompiles()` counts it for the checks). A sprite's picture is a `Mesh`
(a unit quad sized by its scale, `symbols.ts`), a sibling of the sprite's `Graphics` node under the (a unit quad sized by its scale, `symbols.ts`), a sibling of the sprite's `Graphics` node under the