Add in.dt, slider warnings in --check, and two agent-written effects
Spec fixes come from a test where pi + deepseek-v4.1-flash wrote effects from EFFECTS.md alone: glitch-blocks and ghost-stamps both compiled on the first try. The agent's feedback led to in.dt (frame time), documented limits of in.prev as memory, and a --check warning for ignored slider lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
a20d3310db
commit
169c5f9b63
6 changed files with 111 additions and 9 deletions
14
EFFECTS.md
14
EFFECTS.md
|
|
@ -13,7 +13,7 @@ float4 effect(float2 uv, Inputs in) {
|
|||
```
|
||||
|
||||
- `uv` is the pixel's position: `(0,0)` is top-left, `(1,1)` is bottom-right.
|
||||
- Return `float4(r, g, b, 1.0)` with components in 0..1.
|
||||
- Return `float4(r, g, b, 1.0)` with components in 0..1. Alpha is not shown on screen; return 1.0 unless you use it as memory (see below).
|
||||
- Do not write `#include`, `using namespace`, a vertex function or a fragment function. The app adds those.
|
||||
|
||||
## What `in` gives you
|
||||
|
|
@ -24,10 +24,11 @@ float4 effect(float2 uv, Inputs in) {
|
|||
| `in.camera` | texture | The untouched camera frame. |
|
||||
| `in.last` | texture | The camera frame before this one. `length(camera - last)` at a pixel tells you how much it changed. |
|
||||
| `in.prev` | texture | This effect's own output from the previous frame. Read it to build trails and feedback. |
|
||||
| `in.flow` | texture | `.xy` = which way and how far the picture moved at this spot since the last frame, in `uv` units. Typically tiny (0.001 to 0.02). |
|
||||
| `in.flow` | texture | `.xy` = which way and how far the picture moved at this spot since the last frame, in `uv` units. Typically tiny (0.001 to 0.02): `length(flow) * 50.0` is a rough 0..1 "how fast" value. |
|
||||
| `in.particles` | texture | `.r` = brightness of particles that spawn where something moves and drift along with the motion. |
|
||||
| `in.smp` | sampler | Pass it to every `.sample()` call. |
|
||||
| `in.time` | float | Seconds since the app started. |
|
||||
| `in.dt` | float | Seconds since the previous frame. Effects run once per camera frame, so this is about 1/30. Use it to make fades and timers last the same in real time whatever the frame rate: `value *= exp(-in.dt / seconds)`. |
|
||||
| `in.resolution` | float2 | Output size in pixels. One pixel is `1.0 / in.resolution` in `uv` units. |
|
||||
| `in.p.<name>` | float | Your sliders (see below). |
|
||||
|
||||
|
|
@ -44,7 +45,7 @@ Put these at the top of the file. Spacing matters: one space between the parts,
|
|||
```
|
||||
|
||||
- `// slider glow 0.0 4.0 1.5` adds a slider; read its value as `in.p.glow`. Up to 16 per effect.
|
||||
- `// order 60` sets the position in the chain. Lower runs first; the default is 50. Existing effects use 10 to 90.
|
||||
- `// order 60` sets the position in the chain. Any whole number; only the relative order matters, lower runs first, and the default is 50. Existing effects use 10 to 90. The motion inputs (`flow`, `particles`) are computed from the camera before any effect runs.
|
||||
- `// on` makes the effect enabled when the app starts. Leave it out for new effects.
|
||||
|
||||
Slider names must be plain identifiers and must not be Metal built-ins or keywords (`mix`, `length`, `in`, `float`, `step`, `sample` and so on). `amount`, `speed`, `glow`, `hue` are fine.
|
||||
|
|
@ -61,7 +62,8 @@ Slider names must be plain identifiers and must not be Metal built-ins or keywor
|
|||
|
||||
- Write float numbers with a decimal point: `2.0`, not `2`. Convert loop counters with `float(i)`.
|
||||
- Helper functions go above `effect`, must be marked `static`, and take `Inputs in` as a parameter if they read textures.
|
||||
- There is no global state. To remember something between frames, write it into your output and read it back from `in.prev`. The alpha channel (`.a`) of your output is free for this: it is not shown on screen.
|
||||
- There is no global state. To remember something between frames, write it into your output and read it back from `in.prev`. Its `.rgb` is the picture you showed last frame; its alpha channel (`.a`) is free for your own use because it is not shown on screen. That gives one private number per pixel, stored with 8-bit precision (256 steps between 0 and 1), which is enough for a glow or heat level but not for a timestamp or a full-colour image.
|
||||
- To do something every N seconds, test where `in.time` is inside the current period: `fract(in.time / N) < in.dt / N` is true for exactly one frame per period.
|
||||
|
||||
## Two complete examples
|
||||
|
||||
|
|
@ -112,12 +114,12 @@ swift build -c release
|
|||
.build/release/MotionLab --only <name> --snapshot /tmp/<name>.png
|
||||
```
|
||||
|
||||
`--check` also lists the sliders it found for each effect. If one of yours is missing, its `// slider` line is malformed.
|
||||
`--check` also lists the sliders it found for each effect, and warns when a `// slider` line was ignored because it is malformed or over the limit of 16.
|
||||
|
||||
## Checklist
|
||||
|
||||
- The file is `effects/<name>.metal` and defines `float4 effect(float2 uv, Inputs in)`.
|
||||
- It reads its input picture from `in.image`, not `in.camera`, so it works in a chain.
|
||||
- Every number a user might want to tune is a slider with a sensible range and default.
|
||||
- The few numbers a user would most want to tune are sliders with a sensible range and default. Internal constants can stay in the code.
|
||||
- The first line is a comment saying what the effect does.
|
||||
- `.build/release/MotionLab --check` prints `ok` for it.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue