Soften a shader's picture in one pass with a Blur node (#300)
A blur already worked with Sample texture, but only by wiring every sample by hand: 25 nodes for a 5x5. Averaging their colours also gave a dark seam wherever a neighbour was transparent. The Blur node writes the loop itself and averages the right way. - Blur takes radius (pixels of the picture, 0 to 8, fractions allowed) and u/v like Sample texture, and gives color and alpha. - Kind is Box (default) or Gaussian, Direction is Both, Horizontal or Vertical. Both are arguments of one prelude helper, blur(). - The samples are averaged premultiplied and only then made straight, so a transparent neighbour lowers the opacity and adds no black. - The outermost ring counts by the part of the radius that reaches it, so a radius from a Property changes smoothly and compiles nothing again. - A node may now declare prepare, a vec4 the compiler works out once into a local. Color and alpha read that one sum instead of blurring twice. - A reached Blur counts as a shifted sample, so the picture comes with the Output's Edge. It cannot feed the Vertex output. - Both perf tools take GM_PERF_BLUR=<radius>. Wider blurs and bloom need several passes. That is #301. Measured on stein2 (HD 630, one run per value, fps), Box blur: - 48x48 sprites at 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. - 256x256 field shaders at 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. Tests: - Gate: 224 files, 3467 tests, CHECK EXIT 0 (+7). - e2e 28: ALL PASSED, 94 checks (+5 in 28l-vi). Pixels match the arithmetic to 1: the last red texel at radius 0.5/1/4 reads 187/173/154 (expected 188/173/154). - Counter-checks: Blur not counted as shifted fails 1 unit test. Blur output left premultiplied fails the seam and spot checks. - e2e total 1712 is computed (1707 + 5). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d6d38a6422
commit
df61b08bd1
8 files changed
+398
-16
No files matched your search
@@ -11,6 +11,7 @@
|
||||
*
|
||||
* Usage: node tools/seed-e2e.mjs && node tools/perf-field-shaders.mjs
|
||||
* GM_PERF_COUNTS=... source counts (default 1,10,50), GM_PERF_FRAME=... frame edge in units
|
||||
* GM_PERF_BLUR=... puts a Blur (#300) with that radius between picture and Output
|
||||
*
|
||||
* It is a measuring tool, not a test: frame times depend on the machine, so CI does not run it.
|
||||
* Read the rate, not the median – see `perf-shaders.mjs` for why, and for the 60 Hz cap.
|
||||
@@ -23,6 +24,20 @@ const { projectId, baseUrl } = cliArgs('perf-field-shaders.mjs', PROJECTS.fixtur
|
||||
|
||||
const COUNTS = (process.env.GM_PERF_COUNTS ?? '1,10,50').split(',').map(Number);
|
||||
const FRAME = Number(process.env.GM_PERF_FRAME ?? 256);
|
||||
const BLUR = Number(process.env.GM_PERF_BLUR ?? 0);
|
||||
const FIELD_GRAPH =
|
||||
BLUR > 0
|
||||
? {
|
||||
nodes: [
|
||||
{ id: 'pf-blur', type: 'ShaderBlur', x: 0, y: 0, params: { radius: BLUR } },
|
||||
{ id: 'pf-out', type: 'ShaderOutput', x: 300, y: 0, params: {} },
|
||||
],
|
||||
edges: [
|
||||
{ id: 'pf-c', from: { node: 'pf-blur', port: 'color' }, to: { node: 'pf-out', port: 'color' } },
|
||||
{ id: 'pf-a', from: { node: 'pf-blur', port: 'alpha' }, to: { node: 'pf-out', port: 'alpha' } },
|
||||
],
|
||||
}
|
||||
: { nodes: [{ id: 'pf-out', type: 'ShaderOutput', x: 0, y: 0, params: {} }], edges: [] };
|
||||
const WARMUP_MS = 1500;
|
||||
const SAMPLE_MS = 4000;
|
||||
|
||||
@@ -40,7 +55,7 @@ await runChecks({ projectId, baseUrl, port: 9348, shotPrefix: 'gm-perf-field' },
|
||||
const p = __gm.state.project;
|
||||
p.assets = p.assets.filter((a) => a.id !== 'perf-source' && a.id !== 'perf-field');
|
||||
p.assets.push({ id: 'perf-field', type: 'shader', name: 'Perf field', props: {},
|
||||
graph: { nodes: [{ id: 'pf-out', type: 'ShaderOutput', x: 0, y: 0, params: {} }], edges: [] } });
|
||||
graph: ${JSON.stringify(FIELD_GRAPH)} });
|
||||
p.assets.push({ id: 'perf-source', type: 'particles', name: 'Perf source',
|
||||
props: { rate: 50, lifetime: 1, size: 24, color: 0x4080ff, fieldShader: '',
|
||||
fieldWidth: ${FRAME}, fieldHeight: ${FRAME} },
|
||||
@@ -131,7 +146,9 @@ await runChecks({ projectId, baseUrl, port: 9348, shotPrefix: 'gm-perf-field' },
|
||||
await shot('heaviest');
|
||||
await stop();
|
||||
|
||||
console.log(`\nframe ${FRAME}x${FRAME} units, pass-through field shader, ${SAMPLE_MS} ms per point`);
|
||||
console.log(
|
||||
`\nframe ${FRAME}x${FRAME} units, ${BLUR > 0 ? `Blur radius ${BLUR}` : 'pass-through'} field shader, ${SAMPLE_MS} ms per point`,
|
||||
);
|
||||
console.log('| sources | no field shader | field shader | picture |');
|
||||
console.log('| --- | --- | --- | --- |');
|
||||
for (const r of rows)
|
||||
|
||||
Reference in new issue
Block a user