Describe the Blur node and what it costs (#300)
- Shaders: catalogue row, section Softening the picture with the glow recipe, Blur in the list of nodes a corner cannot read, the measured tables under What it costs, and a note in In the code. - Shaders: drop "no colour-typed property", which #298 made untrue. - Development: e2e total 1712.
1 parent
315ee9215c
commit
e28129747e
2 files changed
+62
-3
No files matched your search
+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 # 1707 editor checks, screenshots in /tmp/gm-e2e-*.png
|
node tools/seed-e2e.mjs && node tools/e2e.mjs # 1712 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
|
||||||
|
|||||||
+61
-2
@@ -78,6 +78,7 @@ plugged into a colour port, straight from a `Number` or a `Luminance` as well.
|
|||||||
| **Texture alpha** | out `value` | The opacity of the sprite's own pixel: 0 transparent, 1 opaque. |
|
| **Texture alpha** | out `value` | The opacity of the sprite's own pixel: 0 transparent, 1 opaque. |
|
||||||
| **UV** | out `u`, `v` | Where this pixel is on the sprite's picture, 0 to 1: `u` left to right, `v` top to bottom. |
|
| **UV** | out `u`, `v` | Where this pixel is on the sprite's picture, 0 to 1: `u` left to right, `v` top to bottom. |
|
||||||
| **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. |
|
||||||
|
| **Blur** | in `radius` (number, 2), `u`, `v` (number) · out `color`, `alpha` · *Kind*, *Direction* | The picture softened around a spot, tinted like Texture color. `radius` is in pixels of the picture, 0 to 8, fractions allowed. *Kind* Box (default) weighs every pixel alike, Gaussian the middle more; *Direction* Both, Horizontal or Vertical. Unconnected, `u` and `v` are this pixel's own. See *Softening the picture*. |
|
||||||
| **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: *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*). |
|
| **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*). |
|
||||||
@@ -195,6 +196,40 @@ 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
|
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.
|
a ring of 4 border pixels and keeps its 8-pixel border.
|
||||||
|
|
||||||
|
## Softening the picture
|
||||||
|
|
||||||
|
`Blur` (#300) averages the pixels around a spot in one pass: with *Direction* Both a square of
|
||||||
|
(2r+1)*(2r+1) pixels, with Horizontal or Vertical one row or column of 2r+1. Its `color` and `alpha`
|
||||||
|
go where a `Sample texture`'s would; for the plain blurred sprite, both into the Output.
|
||||||
|
|
||||||
|
- **No dark seam.** The average is taken over the premultiplied pixels and only then turned back
|
||||||
|
into a colour, so a transparent neighbour lowers the opacity and adds no black. Averaging the
|
||||||
|
`color` of several `Sample texture` nodes by hand does add it.
|
||||||
|
- **Fractions.** The outermost ring counts by the part of the radius that reaches it: 0.5 weighs
|
||||||
|
the neighbours half, 1 fully. A radius from a `Property` can therefore be animated smoothly, and
|
||||||
|
changing it compiles nothing again.
|
||||||
|
- **Radius 0** is the picture unchanged. Above 8 the radius stays at 8: that is 289 samples per
|
||||||
|
pixel. Wider blurs and bloom need several passes (#301).
|
||||||
|
- **Past the border** the picture continues as the Output's *Edge* says. For a soft edge that
|
||||||
|
stands free around the sprite, set *Transparent* and give it *Padding* at least as wide as the
|
||||||
|
radius.
|
||||||
|
- **The pixel** is the wearer's picture's: a pixel of the frame for a sprite, of the rastered text
|
||||||
|
for a text (which depends on the zoom with *Linear*), and of the field picture, that is a screen
|
||||||
|
pixel, for a field shader.
|
||||||
|
- **Kind:** *Box* weighs every pixel alike; *Gaussian* weighs the middle more (sigma = radius / 2)
|
||||||
|
and looks softer at the same radius and the same cost.
|
||||||
|
|
||||||
|
A glow lays the blurred shape behind the picture. Colour: `Mix` with `a` the glow colour, `b`
|
||||||
|
`Texture color` and `t` `Texture alpha`. Opacity: `Mix` with `a` the Blur's `alpha`, `b` 1 and `t`
|
||||||
|
`Texture alpha`. Both into the Output, with Edge *Transparent* and Padding for the glow to spread
|
||||||
|
into.
|
||||||
|
|
||||||
|
Measured in the checks (step 28), on a 32-pixel picture red on the left and green on the right,
|
||||||
|
both *Nearest*: radius 0 draws the picture byte for byte; the last red pixel, softened
|
||||||
|
horizontally through a Property, goes to 3/4, 2/3 and 5/9 red at 0.5, 1 and 4 with no compile in
|
||||||
|
between; Vertical keeps it red, Gaussian keeps more red than Box; with Edge *Transparent* the first
|
||||||
|
pixel stays red at 5/9 opacity; one white pixel blurred by 1 is a 3x3 spot of 1/9 each.
|
||||||
|
|
||||||
## 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
|
||||||
@@ -217,7 +252,7 @@ there are no corners in between, and the whole picture leans instead. For a wave
|
|||||||
|
|
||||||
What may feed it is what a corner can know: `UV` (the corner's own place on the picture), `Time`,
|
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`,
|
`Property`, `Number` and the calculating nodes. A corner has no pixel, so `Texture color`,
|
||||||
`Texture alpha`, `Sample texture`, `Texture size`, `Color` and `Color property` cannot feed it; the Vertex output
|
`Texture alpha`, `Sample texture`, `Blur`, `Texture size`, `Color` and `Color property` cannot feed it; the Vertex output
|
||||||
then says "Not available per corner: …" and the shader does not compile. The same node may feed
|
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.
|
both outputs: one `Property` can drive the bend and the colour.
|
||||||
|
|
||||||
@@ -297,7 +332,7 @@ texture itself, so pixel art stays sharp: measured in play at a fractional camer
|
|||||||
screen pixels per world unit), a *Nearest* texture keeps its hard border pixel for pixel with a
|
screen pixels per world unit), a *Nearest* texture keeps its hard border pixel for pixel with a
|
||||||
neutral shader on, and brightness 0.5 darkens every one of those pixels — the shader draws there,
|
neutral shader on, and brightness 0.5 darkens every one of those pixels — the shader draws there,
|
||||||
and it does not resample. The node set is still
|
and it does not resample. The node set is still
|
||||||
small — no channel split, no colour-typed property, no second picture; each is one node in
|
small — no channel split, 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. Without a shader the question never comes up — the quad's own
|
frame is a texture of its own. Without a shader the question never comes up — the quad's own
|
||||||
@@ -335,6 +370,26 @@ Up to 10 sources nothing shows; at 50 the field shaders cost about 1.6 frames a
|
|||||||
cap hides what a pass costs inside a frame that is not full, and a graph that samples the picture
|
cap hides what a pass costs inside a frame that is not full, and a graph that samples the picture
|
||||||
many times (a blur) adds its own price per pixel on top of the pass.
|
many times (a blur) adds its own price per pixel on top of the pass.
|
||||||
|
|
||||||
|
A `Blur` (#300) pays per pixel: (2r+1)*(2r+1) samples with *Direction* Both. One run per row with
|
||||||
|
`GM_PERF_BLUR` in both tools on the same HD 630, a shader of nothing but a Box Blur into the
|
||||||
|
Output, in frames per second. Without a shader the same scenes held 59.0 to 60.1.
|
||||||
|
|
||||||
|
| sprites of 48x48 | 100 | 400 | 800 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| radius 2 | 59.8 | 58.4 | 55.6 |
|
||||||
|
| radius 4 | 53.1 | 51.4 | 36.3 |
|
||||||
|
| radius 8 | 46.6 | 24.4 | 14.4 |
|
||||||
|
|
||||||
|
| field shaders, 256x256 pixels each | 1 | 10 | 50 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| radius 2 | 60.2 | 56.0 | 49.0 |
|
||||||
|
| radius 4 | 60.1 | 49.0 | 27.1 |
|
||||||
|
| radius 8 | 52.4 | 34.9 | 11.4 |
|
||||||
|
|
||||||
|
Radius 2 is cheap. From 4 on the samples set the price: a single field of 256x256 at radius 8 is
|
||||||
|
289 samples for each of its 65536 pixels and already costs 8 frames a second. Two passes (#301)
|
||||||
|
would bring that to 34 samples per pixel.
|
||||||
|
|
||||||
## In the code
|
## In the code
|
||||||
|
|
||||||
`src/core/shader/nodes.ts` is the catalogue, `src/core/shader/compile.ts` turns a graph into the
|
`src/core/shader/nodes.ts` is the catalogue, `src/core/shader/compile.ts` turns a graph into the
|
||||||
@@ -349,6 +404,10 @@ the two. `src/viewport/shaders.ts` wraps the result in a Pixi
|
|||||||
A Vertex output is a second walk of the same graph (`ShaderVertex` in `compile.ts`): its lines go
|
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
|
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`.
|
the grid is `gridQuad` in `src/viewport/quadPadding.ts`.
|
||||||
|
`Blur` writes no loop of its own: the loop is `blur()` in the prelude (`BLUR_FN`, only there when
|
||||||
|
a Blur is reached), and Kind and Direction are its arguments. A node with expensive work declares
|
||||||
|
it as `prepare` (a vec4 the compiler puts into one local), so `color` and `alpha` read one result
|
||||||
|
instead of blurring twice.
|
||||||
`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 — 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
|
||||||
|
|||||||
Reference in new issue
Block a user