diff --git a/Development.md b/Development.md index 32a9ece..65c7dc4 100644 --- a/Development.md +++ b/Development.md @@ -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) 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 -node tools/seed-e2e.mjs && node tools/e2e.mjs # 1695 editor checks, screenshots in /tmp/gm-e2e-*.png +node tools/seed-e2e.mjs && node tools/e2e.mjs # 1699 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 ./e2e.sh start [GM_STEPS] # the same run detached, see below ./e2e.sh status # live progress, or the last run's result diff --git a/Shaders.md b/Shaders.md index d6be40f..af0bd2d 100644 --- a/Shaders.md +++ b/Shaders.md @@ -80,6 +80,7 @@ plugged into a colour port, straight from a `Number` or a `Luminance` as well. | **Texture size** | out `width`, `height` | The picture in pixels: of the frame, when the sprite wears one. | | **Time** | out `value` | Seconds since the editor started; see *Coordinates and time* below. | | **Output** | in `color`, `alpha` (number) | What the sprite is drawn with: the colour, clamped to 0-1, and the opacity, clamped too. Each input left unconnected stays the texture's. One per graph. Its *Edge* setting says how the picture continues past its border when sampled outside it: *Clamp* (the default) holds the border pixel, *Mirror* turns the picture back on itself, *Repeat* starts it over for tiles, *Transparent* leaves everything outside empty. Its *Padding* is room around the picture to draw into, in pixels of the picture per side (see *Room around the picture*). | +| **Vertex output** | in `x`, `y` (number) | Moves the corners of the grid the picture is drawn on: `x` to the right and `y` down, in pixels of the picture; the picture bends with them. One per graph, and optional: without it the wearer draws on a plain rectangle. Its *Subdivisions* setting is how many cells the grid has each way, 1 to 64, 8 by default; 1 is the plain rectangle (see *Moving the corners*). | | **Property** | out `value` | Declares a number the sprite sets: `Name` is the row below the sprite's Shader dropdown and the uniform in the fragment; `Default`, `Min`, `Max` are the row's range. A name is one word (letters, digits, `_`), unique in the graph, and not one of the rows a sprite has anyway — `enabled`, `scale`, `rotation`, `mirrorX`, `mirrorY`, `width`, `height`, `color`, `texture`, `shader`. Both rows would sit on the same key and the uniform would read the sprite's width instead of the declared default, so the node says *"width" is a sprite property* and nothing compiles. | | **Color** | out `color` | A constant colour, picked in the inspector. | | **Number** | out `value` | A constant number, typed on the node. Handy where the same value feeds more than one input, or where an input has nothing to type on (Luminance, the Output's alpha). | @@ -192,6 +193,48 @@ the border colour fills the margin and the Output carries the hint; a text's fil units past its rectangle and no further; particles grow about their centres; a sliced sprite gets a ring of 4 border pixels and keeps its 8-pixel border. +## Moving the corners + +Everything above decides what colour a pixel gets. A **Vertex output** node decides where the +picture is drawn: what feeds its `x` and `y` runs once for every corner of a grid laid over the +wearer and moves that corner, in pixels of the picture, `x` to the right and `y` down. The picture +is pinned to the corners, so it bends with them: a wave, a flag, a wobble. Between two corners the +move is a straight blend of theirs. + +Whether the wearer is a plain rectangle or a grid is said in one place, the node's *Subdivisions*: +the number of cells each way. 8 is an 8 by 8 grid with 81 corners, and 1 is the rectangle with its +four. There is no switch beside it. A shader without a Vertex output node draws on the one +rectangle all sprites share and costs nothing extra. The setting is the shader's, not the +sprite's: how fine the grid has to be depends on the effect, so every wearer of the shader gets +the same grid, whatever its size. + +A number feeding `x` moves the whole picture. `UV.v` through a `Step` at 0.5, times 16, into `x` +moves only the lower half of a 32-pixel picture half its width to the right; with *Subdivisions* 1 +there are no corners in between, and the whole picture leans instead. For a wave, feed `y` with +`Sin` of `UV.u` times a frequency plus `Time`, times the height in pixels. + +What may feed it is what a corner can know: `UV` (the corner's own place on the picture), `Time`, +`Property`, `Number` and the calculating nodes. A corner has no pixel, so `Texture color`, +`Texture alpha`, `Sample texture`, `Texture size` and `Color` cannot feed it; the Vertex output +then says "Not available per corner: …" and the shader does not compile. The same node may feed +both outputs: one `Property` can drive the bend and the colour. + +Who it applies to: + +- **Sprite and text:** the grid, with the pixel counted as for Padding (a pixel of the frame, a + unit of the text). +- **Nine-sliced sprite and particles:** not at all. A sliced sprite keeps its nine parts, a + particle its four corners, and both draw as if the node were not there. + +With Padding the grid covers the margin as well. Like the margin, the bend is drawn only: clicking, +the selection frame, the size rows and colliders keep to the rectangle, and a wearer is drawn even +when its rectangle is off screen, because its corners may reach in. + +Measured in the checks (steps 28 and 30): a Vertex output fed 16 draws a 32-pixel picture half a +sprite to the right; the cut on `v` moves only the lower half with 8 subdivisions and leans the +upper half along with 1; `Texture alpha` into `x` is refused at the node; a text's filled quad +stands 6 units to the right. + ## Wearing a shader The sprite's `Shader` row is a picker over the project's shader assets (`None` is no shader: the @@ -251,8 +294,22 @@ picture sampled past its border shows what Edge says, never the sprite's neighbo frame is a texture of its own. Without a shader the question never comes up — the quad's own coordinates run 0 to 1 and never leave the picture — which is why the default is the mode that matches what you see there: *Clamp*. Outside the sprite's rectangle a shader draws only as far as -its Padding reaches, and what it draws with is still a rectangle: the quad has four corners and -stays a quad (#79 is the vertex-side step that would change that). +its Padding reaches and its Vertex output moves the corners. + +A grid has a price of its own, because every corner is computed and every wearer carries its own +copy of the grid. One run per column with `tools/perf-shaders.mjs` (`GM_PERF_SUBDIVISIONS`) on the +same HD 630, the same scene, a Vertex output with nothing feeding it, in frames per second: + +| sprites | 200 | 400 | 800 | 1600 | +| --- | --- | --- | --- | --- | +| no shader | 60 | 60 | 60 | 41 | +| shader, no Vertex output | 60 | 60 | 58 | 36 | +| Subdivisions 8 | 59 | 58 | 46 | 27 | +| Subdivisions 32 | 54 | 57 | 41 | 23 | + +Up to 400 wearers a grid hardly shows; at 800 it costs a quarter of the frame rate. The 54 at 200 +with 32 subdivisions is below the 57 at 400, so single values here are good to a few frames, not +to one. ## In the code @@ -264,7 +321,10 @@ particle compiler). The type of a `result` has one source: `liftedType` in the c one and the same value* in [Behaviors](Behaviors)) is the same rule for the port the editor draws. A test runs every node with an open result through every mix of numbers and colours and compares the two. `src/viewport/shaders.ts` wraps the result in a Pixi -`Shader` for the sprite's mesh (`GraphShader`, with a vertex stage of its own) and +`Shader` for the sprite's mesh (`GraphShader`, with a vertex stage of its own). +A Vertex output is a second walk of the same graph (`ShaderVertex` in `compile.ts`): its lines go +into the sprite's vertex stage (`cornerVertex` in `shaders.ts`), which reads the same uniforms, and +the grid is `gridQuad` in `src/viewport/quadPadding.ts`. `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 moving, collapsing or folding compiles nothing again; a changed param or link does, once