@framefields/node-blur 2.0.2
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.
- package/.turbo/turbo-build.log +19 -0
- package/CHANGELOG.md +23 -0
- package/LICENCE +202 -0
- package/dist/index.d.mts +75 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +3 -0
- package/dist/renderer.d.mts +7 -0
- package/dist/renderer.d.mts.map +1 -0
- package/dist/renderer.mjs +3 -0
- package/dist/renderers-DAmsgkhH.mjs +1156 -0
- package/dist/renderers-DAmsgkhH.mjs.map +1 -0
- package/package.json +48 -0
- package/src/index.ts +2 -0
- package/src/renderers/index.ts +6 -0
- package/src/renderers/webgpu-renderer.ts +1238 -0
- package/src/shared/config.ts +107 -0
- package/src/shared/index.ts +1 -0
- package/tsconfig.json +12 -0
- package/tsdown.config.ts +13 -0
|
@@ -0,0 +1,1156 @@
|
|
|
1
|
+
import { ImageResultSchema, VideoResultSchema, configBuilder } from "@framefields/node-sdk";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { defineRenderer } from "@framefields/node-sdk/renderer";
|
|
4
|
+
import { signalRegistry } from "@framefields/webgpu-renderers";
|
|
5
|
+
|
|
6
|
+
//#region src/shared/config.ts
|
|
7
|
+
const MAX_BLUR = 100;
|
|
8
|
+
const blurConfig = configBuilder().field("blurType", z.enum([
|
|
9
|
+
"Gaussian",
|
|
10
|
+
"Box",
|
|
11
|
+
"Median",
|
|
12
|
+
"Motion",
|
|
13
|
+
"Bilateral",
|
|
14
|
+
"Edge-preserving",
|
|
15
|
+
"Radial",
|
|
16
|
+
"Zoom"
|
|
17
|
+
]).default("Gaussian"), {
|
|
18
|
+
bindable: false,
|
|
19
|
+
label: "Blur Type"
|
|
20
|
+
}).field("strength", z.number().min(0).max(MAX_BLUR).default(5), {
|
|
21
|
+
bindable: true,
|
|
22
|
+
dataTypes: ["Number", "Signal"],
|
|
23
|
+
label: "Strength Signal",
|
|
24
|
+
description: "The intensity of the blur. 0 = no blur. Can be modulated by a static number or a dynamic signal."
|
|
25
|
+
}).field("angle", z.number().int().min(0).max(360).default(0), {
|
|
26
|
+
bindable: false,
|
|
27
|
+
signal: true,
|
|
28
|
+
label: "Angle",
|
|
29
|
+
description: "Direction of a Motion blur, in degrees."
|
|
30
|
+
}).field("sigmaColor", z.number().min(.01).max(1).default(.1), {
|
|
31
|
+
bindable: true,
|
|
32
|
+
dataTypes: ["Number", "Signal"],
|
|
33
|
+
label: "Color Sigma Signal",
|
|
34
|
+
description: "Sigma value for color space in Bilateral / Edge-preserving blurs. Can be modulated by a static number or a dynamic signal."
|
|
35
|
+
}).field("centerX", z.number().min(0).max(1).default(.5), {
|
|
36
|
+
bindable: true,
|
|
37
|
+
dataTypes: ["Number", "Signal"],
|
|
38
|
+
label: "Center X Signal",
|
|
39
|
+
description: "The X coordinate of the blur center (0.0 to 1.0). Can be modulated by a static number or a dynamic signal."
|
|
40
|
+
}).field("centerY", z.number().min(0).max(1).default(.5), {
|
|
41
|
+
bindable: true,
|
|
42
|
+
dataTypes: ["Number", "Signal"],
|
|
43
|
+
label: "Center Y Signal",
|
|
44
|
+
description: "The Y coordinate of the blur center (0.0 to 1.0). Can be modulated by a static number or a dynamic signal."
|
|
45
|
+
}).field("partialBlur", z.boolean().default(false), {
|
|
46
|
+
bindable: false,
|
|
47
|
+
signal: true,
|
|
48
|
+
label: "Partial Blur",
|
|
49
|
+
description: "Blurs only a specific circular area around center point."
|
|
50
|
+
}).field("radius", z.number().min(.01).max(1).default(.3), {
|
|
51
|
+
bindable: true,
|
|
52
|
+
dataTypes: ["Number", "Signal"],
|
|
53
|
+
label: "Partial Radius Signal",
|
|
54
|
+
description: "Radius of the partial blur region (0.01 to 1.0). Can be modulated by a static number or a dynamic signal."
|
|
55
|
+
}).field("radiusY", z.number().min(0).max(1).default(0), {
|
|
56
|
+
bindable: true,
|
|
57
|
+
dataTypes: ["Number", "Signal"],
|
|
58
|
+
label: "Partial Radius Y Signal",
|
|
59
|
+
description: "Vertical radius of the partial blur region (0 to 1.0). 0 reuses the horizontal radius."
|
|
60
|
+
}).field("shape", z.enum([
|
|
61
|
+
"ellipse",
|
|
62
|
+
"circle",
|
|
63
|
+
"rect"
|
|
64
|
+
]).default("circle"), {
|
|
65
|
+
bindable: false,
|
|
66
|
+
label: "Partial Shape",
|
|
67
|
+
description: "Shape of the partial blur region."
|
|
68
|
+
}).build();
|
|
69
|
+
const BlurNodeConfigSchema = blurConfig.schema;
|
|
70
|
+
const BlurResultSchema = z.union([ImageResultSchema, VideoResultSchema]);
|
|
71
|
+
const BLUR_OUTPUT_TYPE_MAP = {
|
|
72
|
+
Video: "Video",
|
|
73
|
+
Lottie: "Video",
|
|
74
|
+
GIF: "GIF",
|
|
75
|
+
Image: "Image",
|
|
76
|
+
SVG: "Image"
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
//#endregion
|
|
80
|
+
//#region src/renderers/webgpu-renderer.ts
|
|
81
|
+
const WGSL_BLUR_UNIFORMS = `
|
|
82
|
+
struct BlurUniforms {
|
|
83
|
+
dirX : f32,
|
|
84
|
+
dirY : f32,
|
|
85
|
+
strength : f32,
|
|
86
|
+
angle : f32,
|
|
87
|
+
|
|
88
|
+
sigmaColor : f32,
|
|
89
|
+
centerX : f32,
|
|
90
|
+
centerY : f32,
|
|
91
|
+
radius : f32,
|
|
92
|
+
|
|
93
|
+
partialBlur : f32,
|
|
94
|
+
hasStrengthSig : f32,
|
|
95
|
+
hasSigmaColorSig: f32,
|
|
96
|
+
hasCenterXSig : f32,
|
|
97
|
+
|
|
98
|
+
hasCenterYSig : f32,
|
|
99
|
+
hasRadiusSig : f32,
|
|
100
|
+
radiusY : f32,
|
|
101
|
+
shape : f32,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
@group(0) @binding(0) var<uniform> u : BlurUniforms;
|
|
105
|
+
@group(1) @binding(0) var tex : texture_2d<f32>;
|
|
106
|
+
@group(1) @binding(1) var samp : sampler;
|
|
107
|
+
@group(1) @binding(2) var origTex : texture_2d<f32>;
|
|
108
|
+
|
|
109
|
+
@group(2) @binding(0) var strengthSigTex : texture_2d<f32>;
|
|
110
|
+
@group(2) @binding(1) var sigmaColorSigTex : texture_2d<f32>;
|
|
111
|
+
@group(2) @binding(2) var centerXSigTex : texture_2d<f32>;
|
|
112
|
+
@group(2) @binding(3) var centerYSigTex : texture_2d<f32>;
|
|
113
|
+
@group(2) @binding(4) var radiusSigTex : texture_2d<f32>;
|
|
114
|
+
@group(2) @binding(5) var signalSamp : sampler;
|
|
115
|
+
|
|
116
|
+
fn applyPartialBlur(blurred: vec4<f32>, uv: vec2<f32>, dimensions: vec2<f32>) -> vec4<f32> {
|
|
117
|
+
if (u.partialBlur <= 0.5) {
|
|
118
|
+
return blurred;
|
|
119
|
+
}
|
|
120
|
+
let orig = textureSampleLevel(origTex, samp, uv, 0.0);
|
|
121
|
+
var centerX = u.centerX;
|
|
122
|
+
if (u.hasCenterXSig > 0.5) {
|
|
123
|
+
centerX = textureSampleLevel(centerXSigTex, signalSamp, vec2<f32>(0.5, 0.5), 0.0).r;
|
|
124
|
+
}
|
|
125
|
+
var centerY = u.centerY;
|
|
126
|
+
if (u.hasCenterYSig > 0.5) {
|
|
127
|
+
centerY = textureSampleLevel(centerYSigTex, signalSamp, vec2<f32>(0.5, 0.5), 0.0).r;
|
|
128
|
+
}
|
|
129
|
+
var radiusX = u.radius;
|
|
130
|
+
if (u.hasRadiusSig > 0.5) {
|
|
131
|
+
radiusX = textureSampleLevel(radiusSigTex, signalSamp, vec2<f32>(0.5, 0.5), 0.0).r;
|
|
132
|
+
}
|
|
133
|
+
var radiusY = u.radiusY;
|
|
134
|
+
|
|
135
|
+
var mask = 0.0;
|
|
136
|
+
if (radiusY <= 0.0) {
|
|
137
|
+
let aspect = dimensions.x / dimensions.y;
|
|
138
|
+
let diff = (uv - vec2<f32>(centerX, centerY)) * vec2<f32>(aspect, 1.0);
|
|
139
|
+
let dist = length(diff);
|
|
140
|
+
let feather = 0.05;
|
|
141
|
+
mask = 1.0 - smoothstep(radiusX - feather, radiusX + feather, dist);
|
|
142
|
+
} else {
|
|
143
|
+
let diff = abs(uv - vec2<f32>(centerX, centerY));
|
|
144
|
+
if (u.shape > 0.5) {
|
|
145
|
+
let feather = 0.02;
|
|
146
|
+
let edgeX = 1.0 - smoothstep(radiusX - feather, radiusX + feather, diff.x);
|
|
147
|
+
let edgeY = 1.0 - smoothstep(radiusY - feather, radiusY + feather, diff.y);
|
|
148
|
+
mask = edgeX * edgeY;
|
|
149
|
+
} else {
|
|
150
|
+
let normDist = length(vec2<f32>(diff.x / max(1e-4, radiusX), diff.y / max(1e-4, radiusY)));
|
|
151
|
+
let feather = 0.08;
|
|
152
|
+
mask = 1.0 - smoothstep(1.0 - feather, 1.0 + feather, normDist);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return mix(orig, blurred, mask);
|
|
156
|
+
}
|
|
157
|
+
`;
|
|
158
|
+
const GAUSSIAN_BLUR_SHADER = `
|
|
159
|
+
${WGSL_BLUR_UNIFORMS}
|
|
160
|
+
|
|
161
|
+
struct VSOut {
|
|
162
|
+
@builtin(position) pos : vec4<f32>,
|
|
163
|
+
@location(0) uv : vec2<f32>,
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
@vertex fn vs(@builtin(vertex_index) vi: u32) -> VSOut {
|
|
167
|
+
var pos = array<vec2<f32>, 4>(
|
|
168
|
+
vec2<f32>(-1.0, 1.0),
|
|
169
|
+
vec2<f32>(1.0, 1.0),
|
|
170
|
+
vec2<f32>(-1.0, -1.0),
|
|
171
|
+
vec2<f32>(1.0, -1.0)
|
|
172
|
+
);
|
|
173
|
+
var uv = array<vec2<f32>, 4>(
|
|
174
|
+
vec2<f32>(0.0, 0.0),
|
|
175
|
+
vec2<f32>(1.0, 0.0),
|
|
176
|
+
vec2<f32>(0.0, 1.0),
|
|
177
|
+
vec2<f32>(1.0, 1.0)
|
|
178
|
+
);
|
|
179
|
+
return VSOut(vec4<f32>(pos[vi], 0.0, 1.0), uv[vi]);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
@fragment fn fs(in : VSOut) -> @location(0) vec4<f32> {
|
|
183
|
+
let c = textureSampleLevel(tex, samp, in.uv, 0.0);
|
|
184
|
+
var spatial_sigma = u.strength;
|
|
185
|
+
if (u.hasStrengthSig > 0.5) {
|
|
186
|
+
spatial_sigma = textureSampleLevel(strengthSigTex, signalSamp, vec2<f32>(0.5, 0.5), 0.0).r;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
let dimensions = vec2<f32>(textureDimensions(tex));
|
|
190
|
+
if (spatial_sigma <= 0.0) {
|
|
191
|
+
return applyPartialBlur(c, in.uv, dimensions);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
let texelSize = 1.0 / dimensions;
|
|
195
|
+
let stepScale = max(1.0, spatial_sigma / 20.0);
|
|
196
|
+
let effectiveSigma = spatial_sigma / stepScale;
|
|
197
|
+
let radius = min(i32(ceil(3.0 * effectiveSigma)), 64);
|
|
198
|
+
|
|
199
|
+
let direction = vec2<f32>(u.dirX, u.dirY);
|
|
200
|
+
|
|
201
|
+
var color = vec4<f32>(0.0);
|
|
202
|
+
var totalWeight = 0.0;
|
|
203
|
+
|
|
204
|
+
for (var i = -radius; i <= radius; i++) {
|
|
205
|
+
let offset = vec2<f32>(f32(i) * stepScale) * direction * texelSize;
|
|
206
|
+
let x = f32(i) * stepScale;
|
|
207
|
+
let weight = exp(-0.5 * (x / spatial_sigma) * (x / spatial_sigma));
|
|
208
|
+
color += textureSampleLevel(tex, samp, in.uv + offset, 0.0) * weight;
|
|
209
|
+
totalWeight += weight;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
let blurred = color / totalWeight;
|
|
213
|
+
return applyPartialBlur(blurred, in.uv, dimensions);
|
|
214
|
+
}
|
|
215
|
+
`;
|
|
216
|
+
const BOX_BLUR_SHADER = `
|
|
217
|
+
${WGSL_BLUR_UNIFORMS}
|
|
218
|
+
|
|
219
|
+
struct VSOut {
|
|
220
|
+
@builtin(position) pos : vec4<f32>,
|
|
221
|
+
@location(0) uv : vec2<f32>,
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
@vertex fn vs(@builtin(vertex_index) vi: u32) -> VSOut {
|
|
225
|
+
var pos = array<vec2<f32>, 4>(
|
|
226
|
+
vec2<f32>(-1.0, 1.0),
|
|
227
|
+
vec2<f32>(1.0, 1.0),
|
|
228
|
+
vec2<f32>(-1.0, -1.0),
|
|
229
|
+
vec2<f32>(1.0, -1.0)
|
|
230
|
+
);
|
|
231
|
+
var uv = array<vec2<f32>, 4>(
|
|
232
|
+
vec2<f32>(0.0, 0.0),
|
|
233
|
+
vec2<f32>(1.0, 0.0),
|
|
234
|
+
vec2<f32>(0.0, 1.0),
|
|
235
|
+
vec2<f32>(1.0, 1.0)
|
|
236
|
+
);
|
|
237
|
+
return VSOut(vec4<f32>(pos[vi], 0.0, 1.0), uv[vi]);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
@fragment fn fs(in : VSOut) -> @location(0) vec4<f32> {
|
|
241
|
+
let c = textureSampleLevel(tex, samp, in.uv, 0.0);
|
|
242
|
+
var strength = u.strength;
|
|
243
|
+
if (u.hasStrengthSig > 0.5) {
|
|
244
|
+
strength = textureSampleLevel(strengthSigTex, signalSamp, vec2<f32>(0.5, 0.5), 0.0).r;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
let dimensions = vec2<f32>(textureDimensions(tex));
|
|
248
|
+
let radius = i32(round(strength));
|
|
249
|
+
if (radius <= 0) {
|
|
250
|
+
return applyPartialBlur(c, in.uv, dimensions);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
let texelSize = 1.0 / dimensions;
|
|
254
|
+
let direction = vec2<f32>(u.dirX, u.dirY);
|
|
255
|
+
|
|
256
|
+
var color = vec4<f32>(0.0);
|
|
257
|
+
var count = 0.0;
|
|
258
|
+
|
|
259
|
+
for (var i = -radius; i <= radius; i++) {
|
|
260
|
+
let offset = vec2<f32>(f32(i)) * direction * texelSize;
|
|
261
|
+
color += textureSampleLevel(tex, samp, in.uv + offset, 0.0);
|
|
262
|
+
count += 1.0;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
let blurred = color / count;
|
|
266
|
+
return applyPartialBlur(blurred, in.uv, dimensions);
|
|
267
|
+
}
|
|
268
|
+
`;
|
|
269
|
+
const MEDIAN_BLUR_SHADER = `
|
|
270
|
+
${WGSL_BLUR_UNIFORMS}
|
|
271
|
+
|
|
272
|
+
struct VSOut {
|
|
273
|
+
@builtin(position) pos : vec4<f32>,
|
|
274
|
+
@location(0) uv : vec2<f32>,
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
struct Val {
|
|
278
|
+
luma : f32,
|
|
279
|
+
color : vec4<f32>,
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
@vertex fn vs(@builtin(vertex_index) vi: u32) -> VSOut {
|
|
283
|
+
var pos = array<vec2<f32>, 4>(
|
|
284
|
+
vec2<f32>(-1.0, 1.0),
|
|
285
|
+
vec2<f32>(1.0, 1.0),
|
|
286
|
+
vec2<f32>(-1.0, -1.0),
|
|
287
|
+
vec2<f32>(1.0, -1.0)
|
|
288
|
+
);
|
|
289
|
+
var uv = array<vec2<f32>, 4>(
|
|
290
|
+
vec2<f32>(0.0, 0.0),
|
|
291
|
+
vec2<f32>(1.0, 0.0),
|
|
292
|
+
vec2<f32>(0.0, 1.0),
|
|
293
|
+
vec2<f32>(1.0, 1.0)
|
|
294
|
+
);
|
|
295
|
+
return VSOut(vec4<f32>(pos[vi], 0.0, 1.0), uv[vi]);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
@fragment fn fs(in : VSOut) -> @location(0) vec4<f32> {
|
|
299
|
+
let centerCol = textureSampleLevel(tex, samp, in.uv, 0.0);
|
|
300
|
+
var strength = u.strength;
|
|
301
|
+
if (u.hasStrengthSig > 0.5) {
|
|
302
|
+
strength = textureSampleLevel(strengthSigTex, signalSamp, vec2<f32>(0.5, 0.5), 0.0).r;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
let dimensions = vec2<f32>(textureDimensions(tex));
|
|
306
|
+
let radius = min(i32(round(strength)), 16);
|
|
307
|
+
if (radius <= 0) {
|
|
308
|
+
return applyPartialBlur(centerCol, in.uv, dimensions);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
let count = radius * 2 + 1;
|
|
312
|
+
let texelSize = 1.0 / dimensions;
|
|
313
|
+
let direction = vec2<f32>(u.dirX, u.dirY);
|
|
314
|
+
|
|
315
|
+
var values : array<Val, 33>;
|
|
316
|
+
|
|
317
|
+
for (var i = 0; i < 33; i++) {
|
|
318
|
+
if (i >= count) { break; }
|
|
319
|
+
let offset = vec2<f32>(f32(i - radius)) * direction * texelSize;
|
|
320
|
+
let col = textureSampleLevel(tex, samp, in.uv + offset, 0.0);
|
|
321
|
+
values[i] = Val(dot(col.rgb, vec3<f32>(0.299, 0.587, 0.114)), col);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Selection sort
|
|
325
|
+
for (var i = 0; i < 33; i++) {
|
|
326
|
+
if (i >= count - 1) { break; }
|
|
327
|
+
var minIdx = i;
|
|
328
|
+
for (var j = 0; j < 33; j++) {
|
|
329
|
+
if (j <= i) { continue; }
|
|
330
|
+
if (j >= count) { break; }
|
|
331
|
+
if (values[j].luma < values[minIdx].luma) {
|
|
332
|
+
minIdx = j;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (minIdx != i) {
|
|
336
|
+
let temp = values[i];
|
|
337
|
+
values[i] = values[minIdx];
|
|
338
|
+
values[minIdx] = temp;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
let medianIdx = count / 2;
|
|
343
|
+
return applyPartialBlur(values[medianIdx].color, in.uv, dimensions);
|
|
344
|
+
}
|
|
345
|
+
`;
|
|
346
|
+
const MOTION_BLUR_SHADER = `
|
|
347
|
+
${WGSL_BLUR_UNIFORMS}
|
|
348
|
+
|
|
349
|
+
struct VSOut {
|
|
350
|
+
@builtin(position) pos : vec4<f32>,
|
|
351
|
+
@location(0) uv : vec2<f32>,
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
@vertex fn vs(@builtin(vertex_index) vi: u32) -> VSOut {
|
|
355
|
+
var pos = array<vec2<f32>, 4>(
|
|
356
|
+
vec2<f32>(-1.0, 1.0),
|
|
357
|
+
vec2<f32>(1.0, 1.0),
|
|
358
|
+
vec2<f32>(-1.0, -1.0),
|
|
359
|
+
vec2<f32>(1.0, -1.0)
|
|
360
|
+
);
|
|
361
|
+
var uv = array<vec2<f32>, 4>(
|
|
362
|
+
vec2<f32>(0.0, 0.0),
|
|
363
|
+
vec2<f32>(1.0, 0.0),
|
|
364
|
+
vec2<f32>(0.0, 1.0),
|
|
365
|
+
vec2<f32>(1.0, 1.0)
|
|
366
|
+
);
|
|
367
|
+
return VSOut(vec4<f32>(pos[vi], 0.0, 1.0), uv[vi]);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
@fragment fn fs(in : VSOut) -> @location(0) vec4<f32> {
|
|
371
|
+
let c = textureSampleLevel(tex, samp, in.uv, 0.0);
|
|
372
|
+
var strength = u.strength;
|
|
373
|
+
if (u.hasStrengthSig > 0.5) {
|
|
374
|
+
strength = textureSampleLevel(strengthSigTex, signalSamp, vec2<f32>(0.5, 0.5), 0.0).r;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
let dimensions = vec2<f32>(textureDimensions(tex));
|
|
378
|
+
if (strength <= 0.0) {
|
|
379
|
+
return applyPartialBlur(c, in.uv, dimensions);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
let PI = 3.14159265359;
|
|
383
|
+
let angleRad = u.angle * PI / 180.0;
|
|
384
|
+
let dir = vec2<f32>(cos(angleRad), sin(angleRad));
|
|
385
|
+
|
|
386
|
+
let texelSize = 1.0 / dimensions;
|
|
387
|
+
let step = dir * texelSize;
|
|
388
|
+
|
|
389
|
+
let numSamples = i32(clamp(strength, 1.0, 64.0));
|
|
390
|
+
var color = vec4<f32>(0.0);
|
|
391
|
+
|
|
392
|
+
for (var i = 0; i < 64; i++) {
|
|
393
|
+
if (i >= numSamples) { break; }
|
|
394
|
+
let offset = f32(i - numSamples / 2) * step;
|
|
395
|
+
color += textureSampleLevel(tex, samp, in.uv + offset, 0.0);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
let blurred = color / f32(numSamples);
|
|
399
|
+
return applyPartialBlur(blurred, in.uv, dimensions);
|
|
400
|
+
}
|
|
401
|
+
`;
|
|
402
|
+
const BILATERAL_BLUR_SHADER = `
|
|
403
|
+
${WGSL_BLUR_UNIFORMS}
|
|
404
|
+
|
|
405
|
+
struct VSOut {
|
|
406
|
+
@builtin(position) pos : vec4<f32>,
|
|
407
|
+
@location(0) uv : vec2<f32>,
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
@vertex fn vs(@builtin(vertex_index) vi: u32) -> VSOut {
|
|
411
|
+
var pos = array<vec2<f32>, 4>(
|
|
412
|
+
vec2<f32>(-1.0, 1.0),
|
|
413
|
+
vec2<f32>(1.0, 1.0),
|
|
414
|
+
vec2<f32>(-1.0, -1.0),
|
|
415
|
+
vec2<f32>(1.0, -1.0)
|
|
416
|
+
);
|
|
417
|
+
var uv = array<vec2<f32>, 4>(
|
|
418
|
+
vec2<f32>(0.0, 0.0),
|
|
419
|
+
vec2<f32>(1.0, 0.0),
|
|
420
|
+
vec2<f32>(0.0, 1.0),
|
|
421
|
+
vec2<f32>(1.0, 1.0)
|
|
422
|
+
);
|
|
423
|
+
return VSOut(vec4<f32>(pos[vi], 0.0, 1.0), uv[vi]);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
@fragment fn fs(in : VSOut) -> @location(0) vec4<f32> {
|
|
427
|
+
let centerCol = textureSampleLevel(tex, samp, in.uv, 0.0);
|
|
428
|
+
var spatial_sigma = u.strength;
|
|
429
|
+
if (u.hasStrengthSig > 0.5) {
|
|
430
|
+
spatial_sigma = textureSampleLevel(strengthSigTex, signalSamp, vec2<f32>(0.5, 0.5), 0.0).r;
|
|
431
|
+
}
|
|
432
|
+
var sigmaColor = u.sigmaColor;
|
|
433
|
+
if (u.hasSigmaColorSig > 0.5) {
|
|
434
|
+
sigmaColor = textureSampleLevel(sigmaColorSigTex, signalSamp, vec2<f32>(0.5, 0.5), 0.0).r;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
let dimensions = vec2<f32>(textureDimensions(tex));
|
|
438
|
+
if (spatial_sigma <= 0.0) {
|
|
439
|
+
return applyPartialBlur(centerCol, in.uv, dimensions);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
let texelSize = 1.0 / dimensions;
|
|
443
|
+
let radius = min(i32(ceil(3.0 * spatial_sigma)), 32);
|
|
444
|
+
let direction = vec2<f32>(u.dirX, u.dirY);
|
|
445
|
+
|
|
446
|
+
var color = vec4<f32>(0.0);
|
|
447
|
+
var totalWeight = 0.0;
|
|
448
|
+
|
|
449
|
+
for (var i = -32; i <= 32; i++) {
|
|
450
|
+
if (i < -radius || i > radius) { continue; }
|
|
451
|
+
let offset = vec2<f32>(f32(i)) * direction * texelSize;
|
|
452
|
+
let sampleCol = textureSampleLevel(tex, samp, in.uv + offset, 0.0);
|
|
453
|
+
|
|
454
|
+
let spatialDistSq = f32(i * i);
|
|
455
|
+
let spatialWeight = exp(-0.5 * spatialDistSq / (spatial_sigma * spatial_sigma));
|
|
456
|
+
|
|
457
|
+
let diff = sampleCol - centerCol;
|
|
458
|
+
let colorDistSq = dot(diff, diff);
|
|
459
|
+
let rangeWeight = exp(-0.5 * colorDistSq / (sigmaColor * sigmaColor));
|
|
460
|
+
|
|
461
|
+
let weight = spatialWeight * rangeWeight;
|
|
462
|
+
color += sampleCol * weight;
|
|
463
|
+
totalWeight += weight;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
var blurred = centerCol;
|
|
467
|
+
if (totalWeight > 0.0) {
|
|
468
|
+
blurred = color / totalWeight;
|
|
469
|
+
}
|
|
470
|
+
return applyPartialBlur(blurred, in.uv, dimensions);
|
|
471
|
+
}
|
|
472
|
+
`;
|
|
473
|
+
const EDGE_PRESERVING_SHADER = `
|
|
474
|
+
${WGSL_BLUR_UNIFORMS}
|
|
475
|
+
|
|
476
|
+
struct VSOut {
|
|
477
|
+
@builtin(position) pos : vec4<f32>,
|
|
478
|
+
@location(0) uv : vec2<f32>,
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
@vertex fn vs(@builtin(vertex_index) vi: u32) -> VSOut {
|
|
482
|
+
var pos = array<vec2<f32>, 4>(
|
|
483
|
+
vec2<f32>(-1.0, 1.0),
|
|
484
|
+
vec2<f32>(1.0, 1.0),
|
|
485
|
+
vec2<f32>(-1.0, -1.0),
|
|
486
|
+
vec2<f32>(1.0, -1.0)
|
|
487
|
+
);
|
|
488
|
+
var uv = array<vec2<f32>, 4>(
|
|
489
|
+
vec2<f32>(0.0, 0.0),
|
|
490
|
+
vec2<f32>(1.0, 0.0),
|
|
491
|
+
vec2<f32>(0.0, 1.0),
|
|
492
|
+
vec2<f32>(1.0, 1.0)
|
|
493
|
+
);
|
|
494
|
+
return VSOut(vec4<f32>(pos[vi], 0.0, 1.0), uv[vi]);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
@fragment fn fs(in : VSOut) -> @location(0) vec4<f32> {
|
|
498
|
+
let centerCol = textureSampleLevel(tex, samp, in.uv, 0.0);
|
|
499
|
+
var strength = u.strength;
|
|
500
|
+
if (u.hasStrengthSig > 0.5) {
|
|
501
|
+
strength = textureSampleLevel(strengthSigTex, signalSamp, vec2<f32>(0.5, 0.5), 0.0).r;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Map strength (1-10) to radius (1-6)
|
|
505
|
+
let radius = i32(clamp(round(strength * 5.0 / 10.0) + 1.0, 1.0, 6.0));
|
|
506
|
+
|
|
507
|
+
let dimensions = vec2<f32>(textureDimensions(tex));
|
|
508
|
+
let texelSize = 1.0 / dimensions;
|
|
509
|
+
|
|
510
|
+
var m0 = vec3<f32>(0.0); var m1 = vec3<f32>(0.0); var m2 = vec3<f32>(0.0); var m3 = vec3<f32>(0.0);
|
|
511
|
+
var s0 = vec3<f32>(0.0); var s1 = vec3<f32>(0.0); var s2 = vec3<f32>(0.0); var s3 = vec3<f32>(0.0);
|
|
512
|
+
|
|
513
|
+
let n = f32((radius + 1) * (radius + 1));
|
|
514
|
+
|
|
515
|
+
for (var j = -6; j <= 6; j++) {
|
|
516
|
+
if (j < -radius || j > radius) { continue; }
|
|
517
|
+
for (var i = -6; i <= 6; i++) {
|
|
518
|
+
if (i < -radius || i > radius) { continue; }
|
|
519
|
+
|
|
520
|
+
let c = textureSampleLevel(tex, samp, in.uv + vec2<f32>(f32(i), f32(j)) * texelSize, 0.0).rgb;
|
|
521
|
+
|
|
522
|
+
if (i <= 0 && j <= 0) {
|
|
523
|
+
m0 += c;
|
|
524
|
+
s0 += c * c;
|
|
525
|
+
}
|
|
526
|
+
if (i >= 0 && j <= 0) {
|
|
527
|
+
m1 += c;
|
|
528
|
+
s1 += c * c;
|
|
529
|
+
}
|
|
530
|
+
if (i <= 0 && j >= 0) {
|
|
531
|
+
m2 += c;
|
|
532
|
+
s2 += c * c;
|
|
533
|
+
}
|
|
534
|
+
if (i >= 0 && j >= 0) {
|
|
535
|
+
m3 += c;
|
|
536
|
+
s3 += c * c;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
m0 = m0 / n; s0 = abs(s0 / n - m0 * m0);
|
|
542
|
+
m1 = m1 / n; s1 = abs(s1 / n - m1 * m1);
|
|
543
|
+
m2 = m2 / n; s2 = abs(s2 / n - m2 * m2);
|
|
544
|
+
m3 = m3 / n; s3 = abs(s3 / n - m3 * m3);
|
|
545
|
+
|
|
546
|
+
let v0 = s0.r + s0.g + s0.b;
|
|
547
|
+
let v1 = s1.r + s1.g + s1.b;
|
|
548
|
+
let v2 = s2.r + s2.g + s2.b;
|
|
549
|
+
let v3 = s3.r + s3.g + s3.b;
|
|
550
|
+
|
|
551
|
+
var minVar = v0;
|
|
552
|
+
var finalCol = m0;
|
|
553
|
+
|
|
554
|
+
if (v1 < minVar) {
|
|
555
|
+
minVar = v1;
|
|
556
|
+
finalCol = m1;
|
|
557
|
+
}
|
|
558
|
+
if (v2 < minVar) {
|
|
559
|
+
minVar = v2;
|
|
560
|
+
finalCol = m2;
|
|
561
|
+
}
|
|
562
|
+
if (v3 < minVar) {
|
|
563
|
+
minVar = v3;
|
|
564
|
+
finalCol = m3;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
let blurred = vec4<f32>(finalCol, centerCol.a);
|
|
568
|
+
return applyPartialBlur(blurred, in.uv, dimensions);
|
|
569
|
+
}
|
|
570
|
+
`;
|
|
571
|
+
const RADIAL_BLUR_SHADER = `
|
|
572
|
+
${WGSL_BLUR_UNIFORMS}
|
|
573
|
+
|
|
574
|
+
struct VSOut {
|
|
575
|
+
@builtin(position) pos : vec4<f32>,
|
|
576
|
+
@location(0) uv : vec2<f32>,
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
@vertex fn vs(@builtin(vertex_index) vi: u32) -> VSOut {
|
|
580
|
+
var pos = array<vec2<f32>, 4>(
|
|
581
|
+
vec2<f32>(-1.0, 1.0),
|
|
582
|
+
vec2<f32>(1.0, 1.0),
|
|
583
|
+
vec2<f32>(-1.0, -1.0),
|
|
584
|
+
vec2<f32>(1.0, -1.0)
|
|
585
|
+
);
|
|
586
|
+
var uv = array<vec2<f32>, 4>(
|
|
587
|
+
vec2<f32>(0.0, 0.0),
|
|
588
|
+
vec2<f32>(1.0, 0.0),
|
|
589
|
+
vec2<f32>(0.0, 1.0),
|
|
590
|
+
vec2<f32>(1.0, 1.0)
|
|
591
|
+
);
|
|
592
|
+
return VSOut(vec4<f32>(pos[vi], 0.0, 1.0), uv[vi]);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
@fragment fn fs(in : VSOut) -> @location(0) vec4<f32> {
|
|
596
|
+
let centerCol = textureSampleLevel(tex, samp, in.uv, 0.0);
|
|
597
|
+
var strength = u.strength;
|
|
598
|
+
if (u.hasStrengthSig > 0.5) {
|
|
599
|
+
strength = textureSampleLevel(strengthSigTex, signalSamp, vec2<f32>(0.5, 0.5), 0.0).r;
|
|
600
|
+
}
|
|
601
|
+
var centerX = u.centerX;
|
|
602
|
+
if (u.hasCenterXSig > 0.5) {
|
|
603
|
+
centerX = textureSampleLevel(centerXSigTex, signalSamp, vec2<f32>(0.5, 0.5), 0.0).r;
|
|
604
|
+
}
|
|
605
|
+
var centerY = u.centerY;
|
|
606
|
+
if (u.hasCenterYSig > 0.5) {
|
|
607
|
+
centerY = textureSampleLevel(centerYSigTex, signalSamp, vec2<f32>(0.5, 0.5), 0.0).r;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
let dimensions = vec2<f32>(textureDimensions(tex));
|
|
611
|
+
if (strength <= 0.0) {
|
|
612
|
+
return applyPartialBlur(centerCol, in.uv, dimensions);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
let center = vec2<f32>(centerX, centerY);
|
|
616
|
+
let toPixel = in.uv - center;
|
|
617
|
+
let dist = length(toPixel);
|
|
618
|
+
if (dist <= 0.0) {
|
|
619
|
+
return applyPartialBlur(centerCol, in.uv, dimensions);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
let dir = vec2<f32>(-toPixel.y, toPixel.x);
|
|
623
|
+
let blurAmount = (strength / 100.0) * 0.1 * dist;
|
|
624
|
+
|
|
625
|
+
let numSamples = 24;
|
|
626
|
+
var color = vec4<f32>(0.0);
|
|
627
|
+
let step = dir * (blurAmount / f32(numSamples));
|
|
628
|
+
|
|
629
|
+
for (var i = 0; i < 24; i++) {
|
|
630
|
+
let offset = step * f32(i - 12);
|
|
631
|
+
color += textureSampleLevel(tex, samp, in.uv + offset, 0.0);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
let blurred = color / 24.0;
|
|
635
|
+
return applyPartialBlur(blurred, in.uv, dimensions);
|
|
636
|
+
}
|
|
637
|
+
`;
|
|
638
|
+
const ZOOM_BLUR_SHADER = `
|
|
639
|
+
${WGSL_BLUR_UNIFORMS}
|
|
640
|
+
|
|
641
|
+
struct VSOut {
|
|
642
|
+
@builtin(position) pos : vec4<f32>,
|
|
643
|
+
@location(0) uv : vec2<f32>,
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
@vertex fn vs(@builtin(vertex_index) vi: u32) -> VSOut {
|
|
647
|
+
var pos = array<vec2<f32>, 4>(
|
|
648
|
+
vec2<f32>(-1.0, 1.0),
|
|
649
|
+
vec2<f32>(1.0, 1.0),
|
|
650
|
+
vec2<f32>(-1.0, -1.0),
|
|
651
|
+
vec2<f32>(1.0, -1.0)
|
|
652
|
+
);
|
|
653
|
+
var uv = array<vec2<f32>, 4>(
|
|
654
|
+
vec2<f32>(0.0, 0.0),
|
|
655
|
+
vec2<f32>(1.0, 0.0),
|
|
656
|
+
vec2<f32>(0.0, 1.0),
|
|
657
|
+
vec2<f32>(1.0, 1.0)
|
|
658
|
+
);
|
|
659
|
+
return VSOut(vec4<f32>(pos[vi], 0.0, 1.0), uv[vi]);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
@fragment fn fs(in : VSOut) -> @location(0) vec4<f32> {
|
|
663
|
+
let centerCol = textureSampleLevel(tex, samp, in.uv, 0.0);
|
|
664
|
+
var strength = u.strength;
|
|
665
|
+
if (u.hasStrengthSig > 0.5) {
|
|
666
|
+
strength = textureSampleLevel(strengthSigTex, signalSamp, vec2<f32>(0.5, 0.5), 0.0).r;
|
|
667
|
+
}
|
|
668
|
+
var centerX = u.centerX;
|
|
669
|
+
if (u.hasCenterXSig > 0.5) {
|
|
670
|
+
centerX = textureSampleLevel(centerXSigTex, signalSamp, vec2<f32>(0.5, 0.5), 0.0).r;
|
|
671
|
+
}
|
|
672
|
+
var centerY = u.centerY;
|
|
673
|
+
if (u.hasCenterYSig > 0.5) {
|
|
674
|
+
centerY = textureSampleLevel(centerYSigTex, signalSamp, vec2<f32>(0.5, 0.5), 0.0).r;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
let dimensions = vec2<f32>(textureDimensions(tex));
|
|
678
|
+
if (strength <= 0.0) {
|
|
679
|
+
return applyPartialBlur(centerCol, in.uv, dimensions);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
let center = vec2<f32>(centerX, centerY);
|
|
683
|
+
let toPixel = in.uv - center;
|
|
684
|
+
let blurAmount = (strength / 100.0) * 0.15;
|
|
685
|
+
|
|
686
|
+
let numSamples = 24;
|
|
687
|
+
var color = vec4<f32>(0.0);
|
|
688
|
+
let step = toPixel * (blurAmount / f32(numSamples));
|
|
689
|
+
|
|
690
|
+
for (var i = 0; i < 24; i++) {
|
|
691
|
+
let offset = step * f32(i - 12);
|
|
692
|
+
color += textureSampleLevel(tex, samp, in.uv + offset, 0.0);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
let blurred = color / 24.0;
|
|
696
|
+
return applyPartialBlur(blurred, in.uv, dimensions);
|
|
697
|
+
}
|
|
698
|
+
`;
|
|
699
|
+
const deviceResourceCache = /* @__PURE__ */ new WeakMap();
|
|
700
|
+
function getDeviceLayouts(device) {
|
|
701
|
+
let res = deviceResourceCache.get(device);
|
|
702
|
+
if (res) return res;
|
|
703
|
+
res = {
|
|
704
|
+
blurUniformLayout: device.createBindGroupLayout({ entries: [{
|
|
705
|
+
binding: 0,
|
|
706
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
707
|
+
buffer: { type: "uniform" }
|
|
708
|
+
}] }),
|
|
709
|
+
singleTextureLayout: device.createBindGroupLayout({ entries: [
|
|
710
|
+
{
|
|
711
|
+
binding: 0,
|
|
712
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
713
|
+
texture: { sampleType: "float" }
|
|
714
|
+
},
|
|
715
|
+
{
|
|
716
|
+
binding: 1,
|
|
717
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
718
|
+
sampler: { type: "filtering" }
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
binding: 2,
|
|
722
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
723
|
+
texture: { sampleType: "float" }
|
|
724
|
+
}
|
|
725
|
+
] }),
|
|
726
|
+
signalTextureLayout: device.createBindGroupLayout({ entries: [
|
|
727
|
+
{
|
|
728
|
+
binding: 0,
|
|
729
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
730
|
+
texture: { sampleType: "float" }
|
|
731
|
+
},
|
|
732
|
+
{
|
|
733
|
+
binding: 1,
|
|
734
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
735
|
+
texture: { sampleType: "float" }
|
|
736
|
+
},
|
|
737
|
+
{
|
|
738
|
+
binding: 2,
|
|
739
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
740
|
+
texture: { sampleType: "float" }
|
|
741
|
+
},
|
|
742
|
+
{
|
|
743
|
+
binding: 3,
|
|
744
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
745
|
+
texture: { sampleType: "float" }
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
binding: 4,
|
|
749
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
750
|
+
texture: { sampleType: "float" }
|
|
751
|
+
},
|
|
752
|
+
{
|
|
753
|
+
binding: 5,
|
|
754
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
755
|
+
sampler: { type: "filtering" }
|
|
756
|
+
}
|
|
757
|
+
] }),
|
|
758
|
+
pipelineCache: /* @__PURE__ */ new Map()
|
|
759
|
+
};
|
|
760
|
+
deviceResourceCache.set(device, res);
|
|
761
|
+
return res;
|
|
762
|
+
}
|
|
763
|
+
function getBlurResources(device, format, blurType) {
|
|
764
|
+
const layouts = getDeviceLayouts(device);
|
|
765
|
+
const cacheKey = `blur_${blurType}_${format}`;
|
|
766
|
+
let pipeline = layouts.pipelineCache.get(cacheKey);
|
|
767
|
+
if (!pipeline) {
|
|
768
|
+
let code = GAUSSIAN_BLUR_SHADER;
|
|
769
|
+
if (blurType === "Box") code = BOX_BLUR_SHADER;
|
|
770
|
+
else if (blurType === "Median") code = MEDIAN_BLUR_SHADER;
|
|
771
|
+
else if (blurType === "Motion") code = MOTION_BLUR_SHADER;
|
|
772
|
+
else if (blurType === "Bilateral") code = BILATERAL_BLUR_SHADER;
|
|
773
|
+
else if (blurType === "Edge-preserving") code = EDGE_PRESERVING_SHADER;
|
|
774
|
+
else if (blurType === "Radial") code = RADIAL_BLUR_SHADER;
|
|
775
|
+
else if (blurType === "Zoom") code = ZOOM_BLUR_SHADER;
|
|
776
|
+
const blurModule = device.createShaderModule({
|
|
777
|
+
label: `blur_${blurType}_${format}.wgsl`,
|
|
778
|
+
code
|
|
779
|
+
});
|
|
780
|
+
pipeline = device.createRenderPipeline({
|
|
781
|
+
label: `BlurPipeline_${blurType}_${format}`,
|
|
782
|
+
layout: device.createPipelineLayout({ bindGroupLayouts: [
|
|
783
|
+
layouts.blurUniformLayout,
|
|
784
|
+
layouts.singleTextureLayout,
|
|
785
|
+
layouts.signalTextureLayout
|
|
786
|
+
] }),
|
|
787
|
+
vertex: {
|
|
788
|
+
module: blurModule,
|
|
789
|
+
entryPoint: "vs"
|
|
790
|
+
},
|
|
791
|
+
fragment: {
|
|
792
|
+
module: blurModule,
|
|
793
|
+
entryPoint: "fs",
|
|
794
|
+
targets: [{ format }]
|
|
795
|
+
},
|
|
796
|
+
primitive: { topology: "triangle-strip" }
|
|
797
|
+
});
|
|
798
|
+
layouts.pipelineCache.set(cacheKey, pipeline);
|
|
799
|
+
}
|
|
800
|
+
return {
|
|
801
|
+
blurPipeline: pipeline,
|
|
802
|
+
blurUniformLayout: layouts.blurUniformLayout,
|
|
803
|
+
singleTextureLayout: layouts.singleTextureLayout,
|
|
804
|
+
signalTextureLayout: layouts.signalTextureLayout
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
const BlurWebGPURenderer = async (args) => {
|
|
808
|
+
const { ctx, encoder, pass, targetView, targetTexture, targetWidth, targetHeight, props, drawChild } = args;
|
|
809
|
+
const frame = props.frame ?? 0;
|
|
810
|
+
const fps = props.fps || 30;
|
|
811
|
+
const op = props.virtualMedia?.operation;
|
|
812
|
+
if (op?.op !== "Blur" || !op) return;
|
|
813
|
+
const childMedia = props.virtualMedia.children?.[0];
|
|
814
|
+
if (!childMedia) return;
|
|
815
|
+
pass.end();
|
|
816
|
+
const blurData = new Float32Array(16);
|
|
817
|
+
const width = targetWidth;
|
|
818
|
+
const height = targetHeight;
|
|
819
|
+
const dpiScale = width / (props.containerWidth ?? ctx.surface.width / (typeof window !== "undefined" ? window.devicePixelRatio : 1));
|
|
820
|
+
const nodeId = op.inputs ? Object.keys(op.inputs)[0] : "blur_node";
|
|
821
|
+
const resolveBindable = (configKey, defaultValue, minVal, maxVal, scaleByDpi = false) => {
|
|
822
|
+
const handleId = op[`${configKey}HandleId`];
|
|
823
|
+
const input = handleId ? op.inputs?.[handleId] : null;
|
|
824
|
+
const hasSignal = !!(input?.connectionValid && (input.outputItem?.type === "Signal" || input.outputItem?.type === "Numeric"));
|
|
825
|
+
const sd = hasSignal && input?.outputItem?.data ? input.outputItem.data : null;
|
|
826
|
+
let val = defaultValue;
|
|
827
|
+
let hasStaticSig = false;
|
|
828
|
+
let gpuTextureView = void 0;
|
|
829
|
+
if (!hasSignal) if (input?.connectionValid && input.outputItem?.type === "Number") {
|
|
830
|
+
const rawVal = Number(input.outputItem.data ?? defaultValue);
|
|
831
|
+
val = Math.max(minVal, Math.min(maxVal, rawVal));
|
|
832
|
+
if (scaleByDpi) val *= dpiScale;
|
|
833
|
+
} else {
|
|
834
|
+
let rawVal = op[configKey];
|
|
835
|
+
if (rawVal !== void 0 && rawVal !== null) {
|
|
836
|
+
if (typeof rawVal === "object") {
|
|
837
|
+
if ("get" in rawVal && typeof rawVal.get === "function") rawVal = rawVal.get({
|
|
838
|
+
frame,
|
|
839
|
+
fps
|
|
840
|
+
});
|
|
841
|
+
else if ("value" in rawVal) {
|
|
842
|
+
const sig = rawVal;
|
|
843
|
+
if (sig.gpuBinding?.textureView) {
|
|
844
|
+
gpuTextureView = sig.gpuBinding.textureView;
|
|
845
|
+
hasStaticSig = true;
|
|
846
|
+
}
|
|
847
|
+
rawVal = sig.value;
|
|
848
|
+
}
|
|
849
|
+
} else if (typeof rawVal === "function") rawVal = rawVal(frame, fps);
|
|
850
|
+
}
|
|
851
|
+
const numVal = Number(rawVal ?? defaultValue);
|
|
852
|
+
val = Math.max(minVal, Math.min(maxVal, isNaN(numVal) ? defaultValue : numVal));
|
|
853
|
+
if (scaleByDpi) val *= dpiScale;
|
|
854
|
+
}
|
|
855
|
+
else if (sd) {
|
|
856
|
+
const rawVal = Number(sd.offset ?? 0);
|
|
857
|
+
val = Math.max(minVal, Math.min(maxVal, rawVal));
|
|
858
|
+
if (scaleByDpi) val *= dpiScale;
|
|
859
|
+
hasStaticSig = true;
|
|
860
|
+
}
|
|
861
|
+
return {
|
|
862
|
+
val,
|
|
863
|
+
hasStaticSig,
|
|
864
|
+
sd,
|
|
865
|
+
gpuTextureView
|
|
866
|
+
};
|
|
867
|
+
};
|
|
868
|
+
const strengthRes = resolveBindable("strength", 5, 0, MAX_BLUR, true);
|
|
869
|
+
const sigmaColorRes = resolveBindable("sigmaColor", .1, .01, 1, false);
|
|
870
|
+
const centerXRes = resolveBindable("centerX", .5, 0, 1, false);
|
|
871
|
+
const centerYRes = resolveBindable("centerY", .5, 0, 1, false);
|
|
872
|
+
const radiusRes = resolveBindable("radius", .3, .001, 1, false);
|
|
873
|
+
const radiusYRes = resolveBindable("radiusY", 0, 0, 1, false);
|
|
874
|
+
const tmpTex = ctx.renderer.getTemporaryTexture(width, height, [...props.excludeTextures || [], targetTexture]);
|
|
875
|
+
const tmpView = tmpTex.createView();
|
|
876
|
+
ctx.renderer.beginFrame(encoder, tmpView, {
|
|
877
|
+
r: 0,
|
|
878
|
+
g: 0,
|
|
879
|
+
b: 0,
|
|
880
|
+
a: 0
|
|
881
|
+
}, width, height, "clear").end();
|
|
882
|
+
ctx.renderer.pushScissor({
|
|
883
|
+
x: 0,
|
|
884
|
+
y: 0,
|
|
885
|
+
width,
|
|
886
|
+
height
|
|
887
|
+
});
|
|
888
|
+
ctx.renderer.pushIdentity();
|
|
889
|
+
await drawChild(childMedia, {
|
|
890
|
+
...props,
|
|
891
|
+
virtualMedia: childMedia
|
|
892
|
+
}, tmpView, tmpTex, width, height);
|
|
893
|
+
ctx.renderer.popTransform();
|
|
894
|
+
ctx.renderer.popScissor();
|
|
895
|
+
const blurType = op.blurType ?? "Gaussian";
|
|
896
|
+
const isSeparable = blurType === "Gaussian" || blurType === "Box" || blurType === "Median" || blurType === "Bilateral";
|
|
897
|
+
const { blurPipeline: pipeline, blurUniformLayout: uLayout, singleTextureLayout: tLayout, signalTextureLayout: sigLayout } = getBlurResources(ctx.device, ctx.renderer.format, blurType);
|
|
898
|
+
const sampler = ctx.renderer.samplerCache.getSampler(ctx.device);
|
|
899
|
+
const elapsedSeconds = props.elapsedMs !== void 0 ? props.elapsedMs / 1e3 : frame / fps;
|
|
900
|
+
const durationSeconds = props.virtualMedia?.metadata?.durationMs ? props.virtualMedia.metadata.durationMs / 1e3 : props.durationMs !== void 0 ? props.durationMs / 1e3 : 0;
|
|
901
|
+
for (const bindableRes of [
|
|
902
|
+
strengthRes,
|
|
903
|
+
sigmaColorRes,
|
|
904
|
+
centerXRes,
|
|
905
|
+
centerYRes,
|
|
906
|
+
radiusRes
|
|
907
|
+
]) if (bindableRes.hasStaticSig && bindableRes.sd) await signalRegistry.ensureAudioSourcesExtracted(ctx.device, bindableRes.sd, fps, props.renderId);
|
|
908
|
+
const getSignalView = (res, suffix) => {
|
|
909
|
+
if (res.gpuTextureView) return res.gpuTextureView;
|
|
910
|
+
if (res.hasStaticSig && res.sd) return signalRegistry.getOrCreate2DTextureView(ctx.device, encoder, res.sd.nodeId ?? `${nodeId}_${suffix}`, elapsedSeconds, durationSeconds, res.sd, width, height, props.renderId, frame, fps);
|
|
911
|
+
return signalRegistry.getDummy1x1TextureView(ctx.device);
|
|
912
|
+
};
|
|
913
|
+
const strengthView = getSignalView(strengthRes, "strength_sig");
|
|
914
|
+
const sigmaColorView = getSignalView(sigmaColorRes, "sigmacolor_sig");
|
|
915
|
+
const centerXView = getSignalView(centerXRes, "centerx_sig");
|
|
916
|
+
const centerYView = getSignalView(centerYRes, "centery_sig");
|
|
917
|
+
const radiusView = getSignalView(radiusRes, "radius_sig");
|
|
918
|
+
const createBlurBindGroup1 = (inputTex, origTex) => ctx.device.createBindGroup({
|
|
919
|
+
layout: tLayout,
|
|
920
|
+
entries: [
|
|
921
|
+
{
|
|
922
|
+
binding: 0,
|
|
923
|
+
resource: inputTex.createView()
|
|
924
|
+
},
|
|
925
|
+
{
|
|
926
|
+
binding: 1,
|
|
927
|
+
resource: sampler
|
|
928
|
+
},
|
|
929
|
+
{
|
|
930
|
+
binding: 2,
|
|
931
|
+
resource: origTex.createView()
|
|
932
|
+
}
|
|
933
|
+
]
|
|
934
|
+
});
|
|
935
|
+
const isPartial = Boolean(op.partialBlur && op.partialBlur !== "false" && op.partialBlur !== 0);
|
|
936
|
+
blurData[2] = strengthRes.val;
|
|
937
|
+
blurData[3] = Number(op.angle ?? 0);
|
|
938
|
+
blurData[4] = sigmaColorRes.val;
|
|
939
|
+
blurData[5] = centerXRes.val;
|
|
940
|
+
blurData[6] = centerYRes.val;
|
|
941
|
+
blurData[7] = radiusRes.val;
|
|
942
|
+
blurData[8] = isPartial ? 1 : 0;
|
|
943
|
+
blurData[9] = strengthRes.hasStaticSig ? 1 : 0;
|
|
944
|
+
blurData[10] = sigmaColorRes.hasStaticSig ? 1 : 0;
|
|
945
|
+
blurData[11] = centerXRes.hasStaticSig ? 1 : 0;
|
|
946
|
+
blurData[12] = centerYRes.hasStaticSig ? 1 : 0;
|
|
947
|
+
blurData[13] = radiusRes.hasStaticSig ? 1 : 0;
|
|
948
|
+
blurData[14] = radiusYRes.val;
|
|
949
|
+
blurData[15] = op.shape === "rect" ? 1 : 0;
|
|
950
|
+
let outTex2;
|
|
951
|
+
if (isSeparable) {
|
|
952
|
+
const outTex1 = ctx.renderer.getTemporaryTexture(width, height, [
|
|
953
|
+
tmpTex,
|
|
954
|
+
targetTexture,
|
|
955
|
+
...props.excludeTextures || []
|
|
956
|
+
]);
|
|
957
|
+
outTex2 = ctx.renderer.getTemporaryTexture(width, height, [
|
|
958
|
+
tmpTex,
|
|
959
|
+
outTex1,
|
|
960
|
+
targetTexture,
|
|
961
|
+
...props.excludeTextures || []
|
|
962
|
+
]);
|
|
963
|
+
blurData[0] = 1;
|
|
964
|
+
blurData[1] = 0;
|
|
965
|
+
blurData[8] = 0;
|
|
966
|
+
const hBuffer = ctx.renderer.getTemporaryBuffer(blurData);
|
|
967
|
+
const hPass = encoder.beginRenderPass({ colorAttachments: [{
|
|
968
|
+
view: outTex1.createView(),
|
|
969
|
+
loadOp: "clear",
|
|
970
|
+
storeOp: "store",
|
|
971
|
+
clearValue: {
|
|
972
|
+
r: 0,
|
|
973
|
+
g: 0,
|
|
974
|
+
b: 0,
|
|
975
|
+
a: 0
|
|
976
|
+
}
|
|
977
|
+
}] });
|
|
978
|
+
hPass.setPipeline(pipeline);
|
|
979
|
+
hPass.setBindGroup(0, ctx.device.createBindGroup({
|
|
980
|
+
layout: uLayout,
|
|
981
|
+
entries: [{
|
|
982
|
+
binding: 0,
|
|
983
|
+
resource: { buffer: hBuffer }
|
|
984
|
+
}]
|
|
985
|
+
}));
|
|
986
|
+
hPass.setBindGroup(1, createBlurBindGroup1(tmpTex, tmpTex));
|
|
987
|
+
hPass.setBindGroup(2, ctx.device.createBindGroup({
|
|
988
|
+
layout: sigLayout,
|
|
989
|
+
entries: [
|
|
990
|
+
{
|
|
991
|
+
binding: 0,
|
|
992
|
+
resource: strengthView
|
|
993
|
+
},
|
|
994
|
+
{
|
|
995
|
+
binding: 1,
|
|
996
|
+
resource: sigmaColorView
|
|
997
|
+
},
|
|
998
|
+
{
|
|
999
|
+
binding: 2,
|
|
1000
|
+
resource: centerXView
|
|
1001
|
+
},
|
|
1002
|
+
{
|
|
1003
|
+
binding: 3,
|
|
1004
|
+
resource: centerYView
|
|
1005
|
+
},
|
|
1006
|
+
{
|
|
1007
|
+
binding: 4,
|
|
1008
|
+
resource: radiusView
|
|
1009
|
+
},
|
|
1010
|
+
{
|
|
1011
|
+
binding: 5,
|
|
1012
|
+
resource: sampler
|
|
1013
|
+
}
|
|
1014
|
+
]
|
|
1015
|
+
}));
|
|
1016
|
+
hPass.draw(4);
|
|
1017
|
+
hPass.end();
|
|
1018
|
+
blurData[0] = 0;
|
|
1019
|
+
blurData[1] = 1;
|
|
1020
|
+
blurData[8] = isPartial ? 1 : 0;
|
|
1021
|
+
const vBuffer = ctx.renderer.getTemporaryBuffer(blurData);
|
|
1022
|
+
const vPass = encoder.beginRenderPass({ colorAttachments: [{
|
|
1023
|
+
view: outTex2.createView(),
|
|
1024
|
+
loadOp: "clear",
|
|
1025
|
+
storeOp: "store",
|
|
1026
|
+
clearValue: {
|
|
1027
|
+
r: 0,
|
|
1028
|
+
g: 0,
|
|
1029
|
+
b: 0,
|
|
1030
|
+
a: 0
|
|
1031
|
+
}
|
|
1032
|
+
}] });
|
|
1033
|
+
vPass.setPipeline(pipeline);
|
|
1034
|
+
vPass.setBindGroup(0, ctx.device.createBindGroup({
|
|
1035
|
+
layout: uLayout,
|
|
1036
|
+
entries: [{
|
|
1037
|
+
binding: 0,
|
|
1038
|
+
resource: { buffer: vBuffer }
|
|
1039
|
+
}]
|
|
1040
|
+
}));
|
|
1041
|
+
vPass.setBindGroup(1, createBlurBindGroup1(outTex1, tmpTex));
|
|
1042
|
+
vPass.setBindGroup(2, ctx.device.createBindGroup({
|
|
1043
|
+
layout: sigLayout,
|
|
1044
|
+
entries: [
|
|
1045
|
+
{
|
|
1046
|
+
binding: 0,
|
|
1047
|
+
resource: strengthView
|
|
1048
|
+
},
|
|
1049
|
+
{
|
|
1050
|
+
binding: 1,
|
|
1051
|
+
resource: sigmaColorView
|
|
1052
|
+
},
|
|
1053
|
+
{
|
|
1054
|
+
binding: 2,
|
|
1055
|
+
resource: centerXView
|
|
1056
|
+
},
|
|
1057
|
+
{
|
|
1058
|
+
binding: 3,
|
|
1059
|
+
resource: centerYView
|
|
1060
|
+
},
|
|
1061
|
+
{
|
|
1062
|
+
binding: 4,
|
|
1063
|
+
resource: radiusView
|
|
1064
|
+
},
|
|
1065
|
+
{
|
|
1066
|
+
binding: 5,
|
|
1067
|
+
resource: sampler
|
|
1068
|
+
}
|
|
1069
|
+
]
|
|
1070
|
+
}));
|
|
1071
|
+
vPass.draw(4);
|
|
1072
|
+
vPass.end();
|
|
1073
|
+
} else {
|
|
1074
|
+
outTex2 = ctx.renderer.getTemporaryTexture(width, height, [
|
|
1075
|
+
tmpTex,
|
|
1076
|
+
targetTexture,
|
|
1077
|
+
...props.excludeTextures || []
|
|
1078
|
+
]);
|
|
1079
|
+
blurData[0] = 0;
|
|
1080
|
+
blurData[1] = 0;
|
|
1081
|
+
blurData[8] = isPartial ? 1 : 0;
|
|
1082
|
+
const singleBuffer = ctx.renderer.getTemporaryBuffer(blurData);
|
|
1083
|
+
const singlePass = encoder.beginRenderPass({ colorAttachments: [{
|
|
1084
|
+
view: outTex2.createView(),
|
|
1085
|
+
loadOp: "clear",
|
|
1086
|
+
storeOp: "store",
|
|
1087
|
+
clearValue: {
|
|
1088
|
+
r: 0,
|
|
1089
|
+
g: 0,
|
|
1090
|
+
b: 0,
|
|
1091
|
+
a: 0
|
|
1092
|
+
}
|
|
1093
|
+
}] });
|
|
1094
|
+
singlePass.setPipeline(pipeline);
|
|
1095
|
+
singlePass.setBindGroup(0, ctx.device.createBindGroup({
|
|
1096
|
+
layout: uLayout,
|
|
1097
|
+
entries: [{
|
|
1098
|
+
binding: 0,
|
|
1099
|
+
resource: { buffer: singleBuffer }
|
|
1100
|
+
}]
|
|
1101
|
+
}));
|
|
1102
|
+
singlePass.setBindGroup(1, createBlurBindGroup1(tmpTex, tmpTex));
|
|
1103
|
+
singlePass.setBindGroup(2, ctx.device.createBindGroup({
|
|
1104
|
+
layout: sigLayout,
|
|
1105
|
+
entries: [
|
|
1106
|
+
{
|
|
1107
|
+
binding: 0,
|
|
1108
|
+
resource: strengthView
|
|
1109
|
+
},
|
|
1110
|
+
{
|
|
1111
|
+
binding: 1,
|
|
1112
|
+
resource: sigmaColorView
|
|
1113
|
+
},
|
|
1114
|
+
{
|
|
1115
|
+
binding: 2,
|
|
1116
|
+
resource: centerXView
|
|
1117
|
+
},
|
|
1118
|
+
{
|
|
1119
|
+
binding: 3,
|
|
1120
|
+
resource: centerYView
|
|
1121
|
+
},
|
|
1122
|
+
{
|
|
1123
|
+
binding: 4,
|
|
1124
|
+
resource: radiusView
|
|
1125
|
+
},
|
|
1126
|
+
{
|
|
1127
|
+
binding: 5,
|
|
1128
|
+
resource: sampler
|
|
1129
|
+
}
|
|
1130
|
+
]
|
|
1131
|
+
}));
|
|
1132
|
+
singlePass.draw(4);
|
|
1133
|
+
singlePass.end();
|
|
1134
|
+
}
|
|
1135
|
+
const finalPass = ctx.renderer.beginFrame(encoder, targetView, {
|
|
1136
|
+
r: 0,
|
|
1137
|
+
g: 0,
|
|
1138
|
+
b: 0,
|
|
1139
|
+
a: 0
|
|
1140
|
+
}, targetWidth, targetHeight, "load");
|
|
1141
|
+
ctx.renderer.drawTexture(finalPass, outTex2, {
|
|
1142
|
+
x: 0,
|
|
1143
|
+
y: 0,
|
|
1144
|
+
width: targetWidth,
|
|
1145
|
+
height: targetHeight
|
|
1146
|
+
}, { opacity: op.opacity ?? 1 });
|
|
1147
|
+
args.pass = finalPass;
|
|
1148
|
+
};
|
|
1149
|
+
|
|
1150
|
+
//#endregion
|
|
1151
|
+
//#region src/renderers/index.ts
|
|
1152
|
+
var renderers_default = defineRenderer({ WebGPURenderer: BlurWebGPURenderer });
|
|
1153
|
+
|
|
1154
|
+
//#endregion
|
|
1155
|
+
export { MAX_BLUR as a, BlurResultSchema as i, BLUR_OUTPUT_TYPE_MAP as n, blurConfig as o, BlurNodeConfigSchema as r, renderers_default as t };
|
|
1156
|
+
//# sourceMappingURL=renderers-DAmsgkhH.mjs.map
|