Shaders: Clamp is the default edge, and what the sheet itself carries (#81)

admin committed 2026-09-13 21:33:47 +02:00
1 parent 8916a38cd6
commit 7ed7bc85b5
2 files changed
+15 -7

No files matched your search

+2 -2
@@ -12,7 +12,7 @@ npm run check # typecheck + lint + format:check + tests, the gate CI runs
# code is lost (tail's is reported), so a red run reads as green # code is lost (tail's is reported), so a red run reads as green
npm run build # writes dist/ and regenerates index.html npm run build # writes dist/ and regenerates index.html
./build.sh # the same under `set -o pipefail`, for callers that pipe the output ./build.sh # the same under `set -o pipefail`, for callers that pipe the output
node tools/seed-e2e.mjs && node tools/e2e.mjs # 784 editor checks, screenshots in /tmp/gm-e2e-*.png node tools/seed-e2e.mjs && node tools/e2e.mjs # 785 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
@@ -151,4 +151,4 @@ widths to find click points. The **render device is not in this repo** — `/dev
and a 1 GB `/dev/shm` come from `container.options` in the runner's own `config.yaml`, which and a 1 GB `/dev/shm` come from `container.options` in the runner's own `config.yaml`, which
applies to every job on that runner. Without them chromium falls back to llvmpipe, which gets applies to every job on that runner. Without them chromium falls back to llvmpipe, which gets
WebGL 1.0 only. Measured in the job container: `ANGLE (Intel, Vulkan 1.3.230, HD Graphics 630)`, WebGL 1.0 only. Measured in the job container: `ANGLE (Intel, Vulkan 1.3.230, HD Graphics 630)`,
WebGL 2.0, and the same 784 checks as here. WebGL 2.0, and the same 785 checks as here.
+13 -5
@@ -31,7 +31,7 @@ fed to a colour port is broadcast to all three channels. A colour cannot be fed
| **Sample texture** | in `u`, `v` (number) · out `color`, `alpha` | The picture at another spot, tinted like Texture color. Unconnected, `u` and `v` are this pixel's own. Past the border the picture continues as the Output's *Edge* says. | | **Sample texture** | in `u`, `v` (number) · out `color`, `alpha` | The picture at another spot, tinted like Texture color. Unconnected, `u` and `v` are this pixel's own. Past the border the picture continues as the Output's *Edge* says. |
| **Texture size** | out `width`, `height` | The picture in pixels: of the frame, when the sprite wears one. | | **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. | | **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: *Mirror* (the default, and what every picture has without a shader), *Repeat* for tiles, *Clamp* to stretch the border pixel. | | **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. |
| **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. | | **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. |
| **Color** | out `color` | A constant colour, picked in the inspector. | | **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). | | **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). |
@@ -81,7 +81,7 @@ behavior can write.
- **Flip:** `Subtract` with 1 typed in as `a` and `UV.u` as `b`, into `Sample texture.u`. - **Flip:** `Subtract` with 1 typed in as `a` and `UV.u` as `b`, into `Sample texture.u`.
- **Scroll:** `Add` `UV.u` and a Property *offset* (or `Time` times a speed), into - **Scroll:** `Add` `UV.u` and a Property *offset* (or `Time` times a speed), into
`Sample texture.u`. Set the Output's Edge to *Repeat*, so the picture comes round again; with `Sample texture.u`. Set the Output's Edge to *Repeat*, so the picture comes round again; with
*Mirror* it turns back on itself, with *Clamp* the border pixel stretches. *Clamp* (the default) the border pixel stretches, with *Mirror* the picture turns back on itself.
- **Wave:** `UV.u` times 20, `Add` `Time` times 4, `Sin`, times 0.02, `Add` `UV.v`, into - **Wave:** `UV.u` times 20, `Add` `Time` times 4, `Sin`, times 0.02, `Add` `UV.v`, into
`Sample texture.v`. `Sample texture.v`.
- **Pixelate:** `UV.u` times `Texture size.width`, `Divide` by the cell size, `Floor`, times the - **Pixelate:** `UV.u` times `Texture size.width`, `Divide` by the cell size, `Floor`, times the
@@ -97,7 +97,9 @@ once, like any other.
Measured in the checks (step 28): a `Step` at 0.5 on `u` is red left of the middle and blue right Measured in the checks (step 28): a `Step` at 0.5 on `u` is red left of the middle and blue right
of it, on `v` red above and blue below; on a picture red on the left and green on the right, of it, on `v` red above and blue below; on a picture red on the left and green on the right,
sampling at `1 - u` swaps the halves; `u + 0.5` with Edge *Repeat* shows green on the left and, sampling at `1 - u` swaps the halves; `u + 0.5` with Edge *Repeat* shows green on the left and,
past the border, red again on the right, with *Mirror* green on both sides; with the clock held at past the border, red again on the right, with *Mirror* green on both sides; `u + 0.8` reaches far
enough to separate the two remaining modes — with nothing set on the Output the right side is green
(*Clamp*), with *Mirror* it is red; with the clock held at
0.2 s and 0.7 s a `Fract` into a `Step` gives red and then blue; released, the pixel of a 0.2 s and 0.7 s a `Fract` into a `Step` gives red and then blue; released, the pixel of a
`Fract(Time)` mix changes between two reads with nothing running. `Fract(Time)` mix changes between two reads with nothing running.
@@ -142,7 +144,9 @@ and it does not resample. Only sprites wear shaders; text objects do not. The no
small — no channel split, no colour-typed property, no second picture; each is one node in small — no channel split, no colour-typed property, no second picture; each is one node in
`src/core/shader/nodes.ts` (a `ShaderNodeDef` with an `emit`) and one row in the table above. A `src/core/shader/nodes.ts` (a `ShaderNodeDef` with an `emit`) and one row in the table above. A
picture sampled past its border shows what Edge says, never the sprite's neighbour on the sheet: a picture sampled past its border shows what Edge says, never the sprite's neighbour on the sheet: a
frame is a texture of its own. What no shader can do is draw outside the sprite's rectangle: the 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*. What no shader can do is draw outside the sprite's rectangle: the
quad has four corners and stays a quad (#79 is the vertex-side step that would change that). quad has four corners and stays a quad (#79 is the vertex-side step that would change that).
## In the code ## In the code
@@ -165,7 +169,11 @@ The fragment starts with `#version 300 es`: that line is what makes Pixi compile
GLSL ES 3.00 (`GlProgram` looks for it in the fragment); without it `texture()` is only a GLSL ES 3.00 (`GlProgram` looks for it in the fragment); without it `texture()` is only a
`#define` and `textureSize` does not exist. The Output's Edge is a second cut of the picture with `#define` and `textureSize` does not exist. The Output's Edge is a second cut of the picture with
that address mode (`TextureLibrary.get(ref, edge)`), because WebGL takes the sampler state from the that address mode (`TextureLibrary.get(ref, edge)`), because WebGL takes the sampler state from the
texture source, not from a style the shader hands over. Time is `ShaderLibrary.now()`, held still texture source, not from a style the shader hands over. That cut is only made where it can show:
a graph whose `Sample texture` reads nothing but its own pixel draws with the plain sheet, whatever
the Edge says, so the common shader costs no second copy of the picture. The sheet itself carries
`mirror-repeat` (`SHEET_EDGE`) and cannot carry `clamp-to-edge`: Pixi rewrites that to `repeat` on
every texture a Graphics fill draws with. Time is `ShaderLibrary.now()`, held still
by `__gm.shaderTime(seconds)` for the checks; a view that drew a Time shader redraws on every by `__gm.shaderTime(seconds)` for the checks; a view that drew a Time shader redraws on every
editor tick (`WorldViewport.animate`). The rows come from editor tick (`WorldViewport.animate`). The rows come from
`shaderParamDefs(graph)` in `src/core/properties/shaders.ts`; `schemaForAsset` and `shaderParamDefs(graph)` in `src/core/properties/shaders.ts`; `schemaForAsset` and