@nodetool-ai/gpu 0.8.3 → 0.8.4-a24.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1 +1 @@
|
|
|
1
|
-
2026-10-
|
|
1
|
+
2026-10-16T07:29:05.468Z
|
|
@@ -1,36 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `filters.grain@1` — film grain
|
|
2
|
+
* `filters.grain@1` — film grain modelled on exposed stock.
|
|
3
3
|
*
|
|
4
|
-
* **
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* reason `filters.vignette@1` may scale), and it is also closer to how exposed
|
|
9
|
-
* stock behaves — grain bites in the midtones and leaves black black, because
|
|
10
|
-
* there is nothing there to modulate.
|
|
4
|
+
* **Bell-shaped, clumped, two octaves.** Each lattice point draws roughly
|
|
5
|
+
* Gaussian noise (three hashes summed), and smooth value noise interpolates
|
|
6
|
+
* between points, so `size` grows soft clumps rather than square blocks. A
|
|
7
|
+
* second octave at twice the frequency adds the fine layer real grain has.
|
|
11
8
|
*
|
|
12
|
-
* **
|
|
9
|
+
* **Follows exposure.** The noise is added to the straight colour and weighted
|
|
10
|
+
* by luma: zero in pure black, strongest through shadows and midtones, thinner
|
|
11
|
+
* in the highlights. The result is clamped to 0..1 and premultiplied again, so
|
|
12
|
+
* `rgb <= a` holds by construction on a translucent layer.
|
|
13
|
+
*
|
|
14
|
+
* **Reproducible.** The pattern is a hash of the lattice and `seed`, with no
|
|
13
15
|
* clock and no RNG state, so two renders of the same frame are byte-identical
|
|
14
16
|
* and a cached render can be handed back. A host that wants the pattern to roll
|
|
15
|
-
* passes the frame's own time as the seed
|
|
17
|
+
* passes the frame's own time as the seed; the shader folds it into a small
|
|
18
|
+
* exact integer so large frame times keep their precision.
|
|
16
19
|
*/
|
|
17
20
|
import * as d from "typegpu/data";
|
|
18
21
|
export declare const GrainParams: d.WgslStruct<{
|
|
19
|
-
/**
|
|
22
|
+
/** Grain strength, 0..1. 1 is a 0.15 deviation in midtones. 0 is a no-op. */
|
|
20
23
|
amount: d.F32;
|
|
21
|
-
/** Grain
|
|
24
|
+
/** Grain clump size in source pixels. 1 is per-pixel; larger is softer, coarser stock. */
|
|
22
25
|
size: d.F32;
|
|
23
|
-
/** 0 = one
|
|
26
|
+
/** 0 = one noise value for the pixel (monochrome), 1 = per-channel colour noise. */
|
|
24
27
|
colorAmount: d.F32;
|
|
25
28
|
/** Rolls the pattern. Same seed, same grain. */
|
|
26
29
|
seed: d.F32;
|
|
27
30
|
}>;
|
|
28
31
|
export declare const filtersGrainV1: import("../../../../module.js").ShaderModule<d.WgslStruct<{
|
|
29
|
-
/**
|
|
32
|
+
/** Grain strength, 0..1. 1 is a 0.15 deviation in midtones. 0 is a no-op. */
|
|
30
33
|
amount: d.F32;
|
|
31
|
-
/** Grain
|
|
34
|
+
/** Grain clump size in source pixels. 1 is per-pixel; larger is softer, coarser stock. */
|
|
32
35
|
size: d.F32;
|
|
33
|
-
/** 0 = one
|
|
36
|
+
/** 0 = one noise value for the pixel (monochrome), 1 = per-channel colour noise. */
|
|
34
37
|
colorAmount: d.F32;
|
|
35
38
|
/** Rolls the pattern. Same seed, same grain. */
|
|
36
39
|
seed: d.F32;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../../../src/shaders/filters/grain/v1/module.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../../../src/shaders/filters/grain/v1/module.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,CAAC,MAAM,cAAc,CAAC;AAGlC,eAAO,MAAM,WAAW;IACtB,6EAA6E;;IAE7E,0FAA0F;;IAE1F,oFAAoF;;IAEpF,gDAAgD;;EAEhD,CAAC;AAQH,eAAO,MAAM,cAAc;IAhBzB,6EAA6E;;IAE7E,0FAA0F;;IAE1F,oFAAoF;;IAEpF,gDAAgD;;GA0IhD,CAAC"}
|
|
@@ -1,28 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `filters.grain@1` — film grain
|
|
2
|
+
* `filters.grain@1` — film grain modelled on exposed stock.
|
|
3
3
|
*
|
|
4
|
-
* **
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* reason `filters.vignette@1` may scale), and it is also closer to how exposed
|
|
9
|
-
* stock behaves — grain bites in the midtones and leaves black black, because
|
|
10
|
-
* there is nothing there to modulate.
|
|
4
|
+
* **Bell-shaped, clumped, two octaves.** Each lattice point draws roughly
|
|
5
|
+
* Gaussian noise (three hashes summed), and smooth value noise interpolates
|
|
6
|
+
* between points, so `size` grows soft clumps rather than square blocks. A
|
|
7
|
+
* second octave at twice the frequency adds the fine layer real grain has.
|
|
11
8
|
*
|
|
12
|
-
* **
|
|
9
|
+
* **Follows exposure.** The noise is added to the straight colour and weighted
|
|
10
|
+
* by luma: zero in pure black, strongest through shadows and midtones, thinner
|
|
11
|
+
* in the highlights. The result is clamped to 0..1 and premultiplied again, so
|
|
12
|
+
* `rgb <= a` holds by construction on a translucent layer.
|
|
13
|
+
*
|
|
14
|
+
* **Reproducible.** The pattern is a hash of the lattice and `seed`, with no
|
|
13
15
|
* clock and no RNG state, so two renders of the same frame are byte-identical
|
|
14
16
|
* and a cached render can be handed back. A host that wants the pattern to roll
|
|
15
|
-
* passes the frame's own time as the seed
|
|
17
|
+
* passes the frame's own time as the seed; the shader folds it into a small
|
|
18
|
+
* exact integer so large frame times keep their precision.
|
|
16
19
|
*/
|
|
17
20
|
import tgpu from "typegpu";
|
|
18
21
|
import * as d from "typegpu/data";
|
|
19
22
|
import { defineModule } from "../../../../module.js";
|
|
20
23
|
export const GrainParams = d.struct({
|
|
21
|
-
/**
|
|
24
|
+
/** Grain strength, 0..1. 1 is a 0.15 deviation in midtones. 0 is a no-op. */
|
|
22
25
|
amount: d.f32,
|
|
23
|
-
/** Grain
|
|
26
|
+
/** Grain clump size in source pixels. 1 is per-pixel; larger is softer, coarser stock. */
|
|
24
27
|
size: d.f32,
|
|
25
|
-
/** 0 = one
|
|
28
|
+
/** 0 = one noise value for the pixel (monochrome), 1 = per-channel colour noise. */
|
|
26
29
|
colorAmount: d.f32,
|
|
27
30
|
/** Rolls the pattern. Same seed, same grain. */
|
|
28
31
|
seed: d.f32
|
|
@@ -58,9 +61,45 @@ fn hash31(p: vec3f) -> f32 {
|
|
|
58
61
|
return fract((p3.x + p3.y) * p3.z);
|
|
59
62
|
}
|
|
60
63
|
|
|
61
|
-
/**
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Approximately Gaussian noise with unit deviation at one lattice point: the
|
|
66
|
+
* sum of three uniform hashes (Irwin-Hall), centred and rescaled. Real grain is
|
|
67
|
+
* the sum of many silver crystals, so its density is bell-shaped, not flat.
|
|
68
|
+
*/
|
|
69
|
+
fn gaussAt(cell: vec2f, seed: f32) -> f32 {
|
|
70
|
+
let a = hash31(vec3f(cell.x, cell.y, seed));
|
|
71
|
+
let b = hash31(vec3f(cell.x + 57.0, cell.y + 57.0, seed));
|
|
72
|
+
let c = hash31(vec3f(cell.x + 113.0, cell.y + 113.0, seed));
|
|
73
|
+
return (a + b + c - 1.5) * 2.0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Smooth value noise over the grain lattice. Interpolating between lattice
|
|
78
|
+
* points turns a large size into soft clumps instead of hard square blocks. At
|
|
79
|
+
* integer coordinates the weights are exactly zero, so size 1 returns the
|
|
80
|
+
* lattice value itself: fine, per-pixel grain.
|
|
81
|
+
*/
|
|
82
|
+
fn valueNoise(p: vec2f, seed: f32) -> f32 {
|
|
83
|
+
let i = floor(p);
|
|
84
|
+
let f = p - i;
|
|
85
|
+
let u = f * f * (vec2f(3.0) - 2.0 * f);
|
|
86
|
+
let n00 = gaussAt(i, seed);
|
|
87
|
+
let n10 = gaussAt(i + vec2f(1.0, 0.0), seed);
|
|
88
|
+
let n01 = gaussAt(i + vec2f(0.0, 1.0), seed);
|
|
89
|
+
let n11 = gaussAt(i + vec2f(1.0, 1.0), seed);
|
|
90
|
+
return mix(mix(n00, n10, u.x), mix(n01, n11, u.x), u.y);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Two octaves: the clump at the chosen size and a finer layer at twice the
|
|
95
|
+
* frequency. The doubling and the fixed offset keep every floored coordinate an
|
|
96
|
+
* exact float, so the CPU mirror can reproduce the lattice bit for bit.
|
|
97
|
+
*/
|
|
98
|
+
fn grainAt(p: vec2f, seed: f32, channel: f32) -> f32 {
|
|
99
|
+
let s = seed + channel * 19.19;
|
|
100
|
+
let coarse = valueNoise(p, s);
|
|
101
|
+
let fine = valueNoise(p * 2.0 + vec2f(17.0, 29.0), s + 7.0);
|
|
102
|
+
return (coarse + 0.5 * fine) * 0.8944;
|
|
64
103
|
}
|
|
65
104
|
|
|
66
105
|
@compute @workgroup_size(16, 16, 1)
|
|
@@ -70,24 +109,42 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
|
70
109
|
let coords = vec2<i32>(i32(gid.x), i32(gid.y));
|
|
71
110
|
let v = layout.$.params;
|
|
72
111
|
let color = textureLoad(layout.$.source, coords, 0);
|
|
112
|
+
if (color.a <= 0.0) {
|
|
113
|
+
textureStore(layout.$.outputTexture, coords, color);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
73
116
|
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
|
|
117
|
+
// Fold the seed into a small exact integer, then turn it into a whole-pixel
|
|
118
|
+
// shift of the lattice. Frame-time seeds run into the hundreds of thousands,
|
|
119
|
+
// where f32 hashing loses the bits that make consecutive frames differ.
|
|
120
|
+
let folded = v.seed - 8192.0 * floor(v.seed / 8192.0);
|
|
121
|
+
let shift = floor(vec2f(
|
|
122
|
+
hash31(vec3f(folded, 1.7, 9.2)),
|
|
123
|
+
hash31(vec3f(folded, 4.1, 3.3))
|
|
124
|
+
) * 512.0);
|
|
125
|
+
let p = vec2f(f32(gid.x), f32(gid.y)) / max(v.size, 1.0) + shift;
|
|
77
126
|
|
|
78
|
-
let mono = grainAt(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
127
|
+
let mono = grainAt(p, folded, 0.0);
|
|
128
|
+
var n = vec3f(mono);
|
|
129
|
+
if (v.colorAmount > 0.0) {
|
|
130
|
+
let perChannel = vec3f(
|
|
131
|
+
grainAt(p, folded, 1.0),
|
|
132
|
+
grainAt(p, folded, 2.0),
|
|
133
|
+
grainAt(p, folded, 3.0)
|
|
134
|
+
);
|
|
135
|
+
n = mix(n, perChannel, clamp(v.colorAmount, 0.0, 1.0));
|
|
136
|
+
}
|
|
85
137
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
138
|
+
// Work on the straight colour: grain follows exposure, not coverage.
|
|
139
|
+
let straight = color.rgb / color.a;
|
|
140
|
+
let luma = dot(straight, vec3f(0.2126, 0.7152, 0.0722));
|
|
141
|
+
// Film response: nothing to expose in pure black, strongest through the
|
|
142
|
+
// shadows and midtones, thinning out toward the highlights.
|
|
143
|
+
let response = clamp(luma * 6.0, 0.0, 1.0) * (1.0 - 0.65 * luma * luma);
|
|
144
|
+
let sigma = clamp(v.amount, 0.0, 1.0) * 0.15 * response;
|
|
145
|
+
let grained = clamp(straight + n * sigma, vec3f(0.0), vec3f(1.0));
|
|
146
|
+
// Re-premultiplying a value in 0..1 keeps rgb <= a by construction.
|
|
147
|
+
textureStore(layout.$.outputTexture, coords, vec4<f32>(grained * color.a, color.a));
|
|
91
148
|
}
|
|
92
149
|
`,
|
|
93
150
|
io: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.js","sourceRoot":"","sources":["../../../../../src/shaders/filters/grain/v1/module.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"module.js","sourceRoot":"","sources":["../../../../../src/shaders/filters/grain/v1/module.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,IAAI,MAAM,SAAS,CAAC;AAC3B,OAAO,KAAK,CAAC,MAAM,cAAc,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErD,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,6EAA6E;IAC7E,MAAM,EAAE,CAAC,CAAC,GAAG;IACb,0FAA0F;IAC1F,IAAI,EAAE,CAAC,CAAC,GAAG;IACX,oFAAoF;IACpF,WAAW,EAAE,CAAC,CAAC,GAAG;IAClB,gDAAgD;IAChD,IAAI,EAAE,CAAC,CAAC,GAAG;CACZ,CAAC,CAAC;AAEH,MAAM,MAAM,GAAG,IAAI,CAAC,eAAe,CAAC;IAClC,MAAM,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE;IAC5B,aAAa,EAAE,EAAE,cAAc,EAAE,YAAY,EAAE;IAC/C,MAAM,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE;CACjC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,cAAc,GAAG,YAAY,CAAC;IACzC,EAAE,EAAE,eAAe;IACnB,OAAO,EAAE,CAAC;IACV,OAAO,EAAE,UAAU;IACnB,QAAQ,EAAE,SAAS;IACnB,0EAA0E;IAC1E,iDAAiD;IACjD,SAAS,EAAE,eAAe;IAC1B,IAAI,EAAE,SAAS;IACf,MAAM,EAAE,WAAW;IACnB,aAAa,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE;IAC9D,OAAO,EAAE;QACP,MAAM,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE;QACvD,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE;QACnD,WAAW,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE;QAC5D,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE;KACxB;IACD,MAAM;IACN,aAAa,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;IAC1B,IAAI,EAAE,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4FlB;IACC,EAAE,EAAE;QACF,MAAM,EAAE;YACN,MAAM,EAAE;gBACN,UAAU,EAAE,MAAM;gBAClB,KAAK,EAAE,eAAe;gBACtB,YAAY,EAAE,CAAC,YAAY,CAAC;aAC7B;SACF;QACD,MAAM,EAAE;YACN,UAAU,EAAE,MAAM;YAClB,KAAK,EAAE,eAAe;YACtB,MAAM,EAAE,YAAY;YACpB,UAAU,EAAE,gBAAgB;SAC7B;QACD,GAAG,EAAE,gBAAgB;KACtB;CACF,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nodetool-ai/gpu",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4-a24.1",
|
|
4
4
|
"description": "Canonical blend-mode catalog and shared WGSL blend functions for NodeTool compositors (sketch, timeline, Compositor node)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -15,10 +15,8 @@
|
|
|
15
15
|
"lint": "node ../../scripts/run-tsc.mjs --noEmit"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@nodetool-ai/protocol": "0.8.
|
|
19
|
-
"typegpu": "^0.11.6"
|
|
20
|
-
},
|
|
21
|
-
"optionalDependencies": {
|
|
18
|
+
"@nodetool-ai/protocol": "0.8.4-a24.1",
|
|
19
|
+
"typegpu": "^0.11.6",
|
|
22
20
|
"webgpu": "^0.4.0"
|
|
23
21
|
},
|
|
24
22
|
"devDependencies": {
|