A source wears a shader: what it costs, and what the fragment can see (#88)
Particles: the Shader row, why it is free where a sprite's is not, that the fragment sees a particle's picture and not its life, and which row wins when a name arrives from both graphs. Shaders: the same from the shader's side. Architecture: where a shader is cheap depends on what already breaks the batch, and that rows from two places need an owner. Development: 833 -> 837 checks.
1 parent
7c3ee88c95
commit
0af2886351
4 files changed
+37
-2
No files matched your search
+10
@@ -126,6 +126,16 @@ by adding it there would have taken its particles out of the game. The two quest
|
||||
functions — `hasSceneIcon` for the drawing, `isSymbolType` for the decoration — and the icon itself
|
||||
is a decoration of `SceneViewport`, which is editor-only for free because `GameViewport` never syncs
|
||||
any.
|
||||
**Where a shader is cheap depends on what already breaks the batch.** A sprite wearing one leaves
|
||||
Pixi's batched draw and costs a call of its own; a particle field is a single call for its whole
|
||||
field either way, because the pipe breaks the batch for it regardless — so the same feature that is
|
||||
expensive per sprite is free per field. The fragment the compiler emits needed no change for it,
|
||||
only a second vertex stage: Pixi's particle vertex is GLSL ES 1.00 and ours is ES 3.00, so it is
|
||||
translated rather than invented, and what it hands on is named what the fragment already reads.
|
||||
**Where rows come from two places, one of them has to be the owner.** A source is the only asset
|
||||
whose rows are drawn from its own graph *and* a worn shader's, so a name declared twice is a row
|
||||
with two meanings — #63 again. The source's own row wins and the uniform keeps its default: the
|
||||
shader cannot be blamed for a collision that only exists at this one wearer.
|
||||
Comments state contracts
|
||||
and reasons, not what the code does: a `/** */` on every export whose name does not say it all,
|
||||
`//` only for a decision or gotcha.
|
||||
+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
|
||||
npm run build # writes dist/ and regenerates index.html
|
||||
./build.sh # the same under `set -o pipefail`, for callers that pipe the output
|
||||
node tools/seed-e2e.mjs && node tools/e2e.mjs # 833 editor checks, screenshots in /tmp/gm-e2e-*.png
|
||||
node tools/seed-e2e.mjs && node tools/e2e.mjs # 837 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
|
||||
@@ -165,4 +165,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
|
||||
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 2.0, and the same 833 checks as here.
|
||||
WebGL 2.0, and the same 837 checks as here.
|
||||
+19
@@ -86,11 +86,30 @@ mirror), so hanging one on a moving ship turns the whole spray with it. Its own
|
||||
| **Texture** | none | The picture each particle is drawn with. Without one it is a plain square. |
|
||||
| **Color** | white | The tint, and what an unconnected `color` output reads. |
|
||||
| **Move with source** | off | Off: a particle stays where it was born, so a moving source leaves a trail (rocket smoke). On: it hangs on the source and travels with it (sparks on a carried torch). The difference shows only while the source moves. |
|
||||
| **Shader** | none | A shader asset worn by **every particle** of this source, the way a sprite wears one. Its `Property` rows appear below, like a sprite's. |
|
||||
|
||||
Below them come the rows the asset's graph declares, one per `Property` node — so two fountains of
|
||||
the same asset can spray at different strengths, exactly the way two sprites wearing the same shader
|
||||
can. Overriding one of them on a placed object is an ordinary override, with reset and apply.
|
||||
|
||||
**Wearing a shader.** The *Shader* row puts a shader asset on every particle of the source, and the
|
||||
shader's own rows follow underneath — the same picker a sprite has. It is the cheapest place in the
|
||||
editor to wear one: a shaded sprite gives up its batch and costs a draw call of its own, while a
|
||||
source is already a single draw call for its whole field, so five hundred shaded particles cost
|
||||
exactly what five hundred plain ones did.
|
||||
|
||||
What the shader sees is one particle's **picture**, not its **life**. The fragment gets the quad's
|
||||
texel, its own UV and the tint the graph computed — but not the particle's age, index or random
|
||||
number: the buffer a field is drawn from carries five fixed attributes and takes no sixth. That is
|
||||
less of a loss than it sounds, because colour, alpha, size and rotation over age are what the
|
||||
particle graph itself is for; the shader adds what that graph cannot do — sampling, distorting UVs,
|
||||
reading the texture's own size.
|
||||
|
||||
A source is the one asset drawing rows from **two** graphs, so a name can arrive twice. The source's
|
||||
own row wins and the uniform of that name keeps its declared default: two rows called *Rate* would be
|
||||
one row with two meanings. Nothing is rejected on the shader's side — the same shader on a sprite is
|
||||
perfectly fine, it is only this source that already has the name.
|
||||
|
||||
A **behavior** can start a source over with the `Emit` node (its `source` input names which one): in *Burst*
|
||||
mode that is another burst, in *Continuous* mode the stream begins again at particle 0.
|
||||
|
||||
|
||||
+6
@@ -119,6 +119,12 @@ A placed object may override the shader itself; it then shows *that* shader's ro
|
||||
shader keeps the values of the old one's rows in the bag — they are not visible and do not count as
|
||||
deviations while their shader is not worn.
|
||||
|
||||
**A particle source wears one the same way** (see *Particles*), and there it is free: a field is a
|
||||
single draw call whether or not it wears a shader, so the batch that a shaded sprite gives up was
|
||||
never there to lose. The difference is what the fragment can see — a particle's picture, not its
|
||||
age or index — and that a source, having rows of its own graph as well, keeps a name the shader
|
||||
also declares: the uniform then stays on its default rather than taking the row.
|
||||
|
||||
## What it costs, and where it stops
|
||||
|
||||
A shader runs in the sprite's own draw: the mesh is drawn once, with the graph's fragment in place
|
||||
|
||||
Reference in new issue
Block a user