@framefields/node-mask-math 0.0.0-stage → 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.
@@ -0,0 +1,784 @@
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_RADIUS = 200;
8
+ const MaskOperationEnum = z.enum([
9
+ "Union",
10
+ "Intersect",
11
+ "Subtract",
12
+ "Difference",
13
+ "Invert",
14
+ "Dilate",
15
+ "Erode",
16
+ "Choke",
17
+ "Feather"
18
+ ]);
19
+ const MaskChannelEnum = z.enum([
20
+ "Alpha",
21
+ "Luminance",
22
+ "Red",
23
+ "Green",
24
+ "Blue"
25
+ ]);
26
+ const MaskOutputFormatEnum = z.enum([
27
+ "WhiteWithAlpha",
28
+ "GrayscaleRGB",
29
+ "AlphaOnly",
30
+ "PassthroughRGB"
31
+ ]);
32
+ const maskMathConfig = configBuilder().field("operation", MaskOperationEnum.default("Union"), {
33
+ label: "Operation",
34
+ description: "Morphological or Boolean set operation applied to input alpha masks."
35
+ }).field("radius", z.number().min(0).max(MAX_RADIUS).default(0), {
36
+ bindable: true,
37
+ dataTypes: ["Number", "Signal"],
38
+ label: "Radius / Width (px)",
39
+ description: "Kernel radius in pixels for Dilate, Erode, Choke, or Feather operations."
40
+ }).field("threshold", z.number().multipleOf(.01).min(0).max(1).default(.5), {
41
+ bindable: true,
42
+ dataTypes: ["Number", "Signal"],
43
+ label: "Threshold",
44
+ description: "Cutoff threshold level (0.0–1.0) for binarization step or non-linear choke."
45
+ }).field("clampMin", z.number().multipleOf(.01).min(0).max(1).default(0), {
46
+ bindable: true,
47
+ dataTypes: ["Number", "Signal"],
48
+ label: "Clamp Min",
49
+ description: "Minimum alpha matte cutoff limit (0.0–1.0)."
50
+ }).field("clampMax", z.number().multipleOf(.01).min(0).max(1).default(1), {
51
+ bindable: true,
52
+ dataTypes: ["Number", "Signal"],
53
+ label: "Clamp Max",
54
+ description: "Maximum alpha matte cutoff limit (0.0–1.0)."
55
+ }).field("channelA", MaskChannelEnum.default("Alpha"), {
56
+ label: "Mask A Channel",
57
+ description: "Source channel to extract mask from Mask A (Alpha, Luminance, Red, Green, Blue)."
58
+ }).field("channelB", MaskChannelEnum.default("Alpha"), {
59
+ label: "Mask B Channel",
60
+ description: "Source channel to extract mask from Mask B (Alpha, Luminance, Red, Green, Blue)."
61
+ }).field("binarize", z.boolean().default(false), {
62
+ label: "Binarize",
63
+ description: "When enabled, outputs crisp 1-bit binary matte based on threshold."
64
+ }).field("invertResult", z.boolean().default(false), {
65
+ label: "Invert Result",
66
+ description: "Inverts final matte output."
67
+ }).field("outputFormat", MaskOutputFormatEnum.default("WhiteWithAlpha"), {
68
+ label: "Output Format",
69
+ description: "WhiteWithAlpha (rgba(1,1,1,a)), GrayscaleRGB (rgba(a,a,a,1)), AlphaOnly (rgba(0,0,0,a)), PassthroughRGB (Mask A RGB with new alpha)."
70
+ }).build();
71
+ const MaskMathNodeConfigSchema = maskMathConfig.schema;
72
+ const MaskMathResultSchema = z.union([ImageResultSchema, VideoResultSchema]);
73
+ const MASK_MATH_OUTPUT_TYPE_MAP = {
74
+ Video: "Video",
75
+ Lottie: "Video",
76
+ GIF: "GIF",
77
+ Image: "Image",
78
+ SVG: "Image"
79
+ };
80
+
81
+ //#endregion
82
+ //#region src/renderers/webgpu-renderer.ts
83
+ const OPERATION_MAP = {
84
+ Union: 0,
85
+ Intersect: 1,
86
+ Subtract: 2,
87
+ Difference: 3,
88
+ Invert: 4,
89
+ Dilate: 5,
90
+ Erode: 6,
91
+ Choke: 7,
92
+ Feather: 8
93
+ };
94
+ const CHANNEL_MAP = {
95
+ Alpha: 0,
96
+ Luminance: 1,
97
+ Red: 2,
98
+ Green: 3,
99
+ Blue: 4
100
+ };
101
+ const OUTPUT_FORMAT_MAP = {
102
+ WhiteWithAlpha: 0,
103
+ GrayscaleRGB: 1,
104
+ AlphaOnly: 2,
105
+ PassthroughRGB: 3
106
+ };
107
+ const WGSL_MASKMATH = `
108
+ struct MaskMathUniforms {
109
+ dirX : f32,
110
+ dirY : f32,
111
+ radius : f32,
112
+ threshold : f32,
113
+
114
+ clampMin : f32,
115
+ clampMax : f32,
116
+ operation : f32,
117
+ channelA : f32,
118
+
119
+ channelB : f32,
120
+ binarize : f32,
121
+ invertResult : f32,
122
+ outputFormat : f32,
123
+
124
+ hasMaskB : f32,
125
+ hasRadiusSig : f32,
126
+ hasThreshSig : f32,
127
+ hasClampMinSig : f32,
128
+
129
+ hasClampMaxSig : f32,
130
+ isSecondPass : f32,
131
+ pad0 : f32,
132
+ pad1 : f32,
133
+ };
134
+
135
+ @group(0) @binding(0) var<uniform> u : MaskMathUniforms;
136
+
137
+ @group(1) @binding(0) var texA : texture_2d<f32>;
138
+ @group(1) @binding(1) var texB : texture_2d<f32>;
139
+ @group(1) @binding(2) var origTexA : texture_2d<f32>;
140
+ @group(1) @binding(3) var samp : sampler;
141
+
142
+ @group(2) @binding(0) var radiusSigTex : texture_2d<f32>;
143
+ @group(2) @binding(1) var threshSigTex : texture_2d<f32>;
144
+ @group(2) @binding(2) var clampMinSigTex : texture_2d<f32>;
145
+ @group(2) @binding(3) var clampMaxSigTex : texture_2d<f32>;
146
+ @group(2) @binding(4) var signalSamp : sampler;
147
+
148
+ struct VSOut {
149
+ @builtin(position) pos : vec4<f32>,
150
+ @location(0) uv : vec2<f32>,
151
+ };
152
+
153
+ @vertex fn vs(@builtin(vertex_index) vi: u32) -> VSOut {
154
+ var pos = array<vec2<f32>, 4>(
155
+ vec2<f32>(-1.0, 1.0),
156
+ vec2<f32>(1.0, 1.0),
157
+ vec2<f32>(-1.0, -1.0),
158
+ vec2<f32>(1.0, -1.0)
159
+ );
160
+ var uv = array<vec2<f32>, 4>(
161
+ vec2<f32>(0.0, 0.0),
162
+ vec2<f32>(1.0, 0.0),
163
+ vec2<f32>(0.0, 1.0),
164
+ vec2<f32>(1.0, 1.0)
165
+ );
166
+ return VSOut(vec4<f32>(pos[vi], 0.0, 1.0), uv[vi]);
167
+ }
168
+
169
+ fn extractChannel(color : vec4<f32>, chCode : f32) -> f32 {
170
+ if (chCode < 0.5) {
171
+ // Alpha
172
+ return color.a;
173
+ } else if (chCode < 1.5) {
174
+ // Luminance
175
+ let luma = dot(color.rgb, vec3<f32>(0.2126, 0.7152, 0.0722));
176
+ if (color.a > 1e-4) {
177
+ return min(1.0, luma / color.a);
178
+ }
179
+ return luma;
180
+ } else if (chCode < 2.5) {
181
+ // Red
182
+ if (color.a > 1e-4) {
183
+ return min(1.0, color.r / color.a);
184
+ }
185
+ return color.r;
186
+ } else if (chCode < 3.5) {
187
+ // Green
188
+ if (color.a > 1e-4) {
189
+ return min(1.0, color.g / color.a);
190
+ }
191
+ return color.g;
192
+ } else {
193
+ // Blue
194
+ if (color.a > 1e-4) {
195
+ return min(1.0, color.b / color.a);
196
+ }
197
+ return color.b;
198
+ }
199
+ }
200
+
201
+ @fragment fn fs(in : VSOut) -> @location(0) vec4<f32> {
202
+ let dimensions = vec2<f32>(textureDimensions(texA));
203
+ let texelSize = 1.0 / max(dimensions, vec2<f32>(1.0, 1.0));
204
+
205
+ var radius = u.radius;
206
+ if (u.hasRadiusSig > 0.5) {
207
+ radius = clamp(textureSampleLevel(radiusSigTex, signalSamp, vec2<f32>(0.5, 0.5), 0.0).r, 0.0, 200.0);
208
+ }
209
+ var threshold = u.threshold;
210
+ if (u.hasThreshSig > 0.5) {
211
+ threshold = clamp(textureSampleLevel(threshSigTex, signalSamp, vec2<f32>(0.5, 0.5), 0.0).r, 0.0, 1.0);
212
+ }
213
+ var clampMin = u.clampMin;
214
+ if (u.hasClampMinSig > 0.5) {
215
+ clampMin = clamp(textureSampleLevel(clampMinSigTex, signalSamp, vec2<f32>(0.5, 0.5), 0.0).r, 0.0, 1.0);
216
+ }
217
+ var clampMax = u.clampMax;
218
+ if (u.hasClampMaxSig > 0.5) {
219
+ clampMax = clamp(textureSampleLevel(clampMaxSigTex, signalSamp, vec2<f32>(0.5, 0.5), 0.0).r, 0.0, 1.0);
220
+ }
221
+
222
+ let op = u.operation;
223
+ let direction = vec2<f32>(u.dirX, u.dirY);
224
+
225
+ var val = 0.0;
226
+
227
+ if (u.isSecondPass <= 0.5) {
228
+ // -------------------------------------------------------------
229
+ // PASS 1: Horizontal pass / Boolean operation
230
+ // -------------------------------------------------------------
231
+ let sampleA = textureSampleLevel(texA, samp, in.uv, 0.0);
232
+ let sampleB = textureSampleLevel(texB, samp, in.uv, 0.0);
233
+
234
+ let a = extractChannel(sampleA, u.channelA);
235
+ let b = select(0.0, extractChannel(sampleB, u.channelB), u.hasMaskB > 0.5);
236
+
237
+ if (op < 0.5) {
238
+ // Union (A + B)
239
+ val = select(a, clamp(a + b, 0.0, 1.0), u.hasMaskB > 0.5);
240
+ } else if (op < 1.5) {
241
+ // Intersect (min(A, B))
242
+ val = select(a, min(a, b), u.hasMaskB > 0.5);
243
+ } else if (op < 2.5) {
244
+ // Subtract (clamp(A - B, 0, 1))
245
+ val = select(a, clamp(a - b, 0.0, 1.0), u.hasMaskB > 0.5);
246
+ } else if (op < 3.5) {
247
+ // Difference (|A - B|)
248
+ val = select(a, abs(a - b), u.hasMaskB > 0.5);
249
+ } else if (op < 4.5) {
250
+ // Invert (1 - A)
251
+ val = 1.0 - a;
252
+ } else if (op < 5.5) {
253
+ // Dilate (Horizontal Max)
254
+ if (radius <= 0.0) {
255
+ val = a;
256
+ } else {
257
+ let stepScale = max(1.0, radius / 20.0);
258
+ let sampleRadius = min(i32(ceil(radius / stepScale)), 48);
259
+ var maxVal = a;
260
+ for (var i = -sampleRadius; i <= sampleRadius; i++) {
261
+ let offset = vec2<f32>(f32(i) * stepScale) * direction * texelSize;
262
+ let s = textureSampleLevel(texA, samp, in.uv + offset, 0.0);
263
+ maxVal = max(maxVal, extractChannel(s, u.channelA));
264
+ }
265
+ val = maxVal;
266
+ }
267
+ } else if (op < 7.5) {
268
+ // Erode & Choke (Horizontal Min)
269
+ if (radius <= 0.0) {
270
+ val = a;
271
+ } else {
272
+ let stepScale = max(1.0, radius / 20.0);
273
+ let sampleRadius = min(i32(ceil(radius / stepScale)), 48);
274
+ var minVal = a;
275
+ for (var i = -sampleRadius; i <= sampleRadius; i++) {
276
+ let offset = vec2<f32>(f32(i) * stepScale) * direction * texelSize;
277
+ let s = textureSampleLevel(texA, samp, in.uv + offset, 0.0);
278
+ minVal = min(minVal, extractChannel(s, u.channelA));
279
+ }
280
+ val = minVal;
281
+ }
282
+ } else {
283
+ // Feather (Horizontal Gaussian Blur)
284
+ if (radius <= 0.0) {
285
+ val = a;
286
+ } else {
287
+ let spatial_sigma = max(0.1, radius / 2.0);
288
+ let stepScale = max(1.0, spatial_sigma / 20.0);
289
+ let effectiveSigma = spatial_sigma / stepScale;
290
+ let sampleRadius = min(i32(ceil(3.0 * effectiveSigma)), 48);
291
+ var accum = 0.0;
292
+ var totalWeight = 0.0;
293
+ for (var i = -sampleRadius; i <= sampleRadius; i++) {
294
+ let offset = vec2<f32>(f32(i) * stepScale) * direction * texelSize;
295
+ let x = f32(i) * stepScale;
296
+ let weight = exp(-0.5 * (x / spatial_sigma) * (x / spatial_sigma));
297
+ let s = textureSampleLevel(texA, samp, in.uv + offset, 0.0);
298
+ accum += extractChannel(s, u.channelA) * weight;
299
+ totalWeight += weight;
300
+ }
301
+ val = accum / max(totalWeight, 1e-5);
302
+ }
303
+ }
304
+
305
+ return vec4<f32>(val, val, val, val);
306
+ }
307
+
308
+ // -------------------------------------------------------------
309
+ // PASS 2: Vertical pass & Post-processing (clamp, binarize, format)
310
+ // -------------------------------------------------------------
311
+ let samplePass1 = textureSampleLevel(texA, samp, in.uv, 0.0);
312
+ var matte = samplePass1.r;
313
+
314
+ // 1. Spatial kernel processing (Dilate, Erode, Choke, Feather) when radius > 0
315
+ if (radius > 0.0) {
316
+ if (op >= 4.5 && op < 5.5) {
317
+ // Dilate (Vertical Max)
318
+ let stepScale = max(1.0, radius / 20.0);
319
+ let sampleRadius = min(i32(ceil(radius / stepScale)), 48);
320
+ var maxVal = matte;
321
+ for (var i = -sampleRadius; i <= sampleRadius; i++) {
322
+ let offset = vec2<f32>(f32(i) * stepScale) * direction * texelSize;
323
+ let s = textureSampleLevel(texA, samp, in.uv + offset, 0.0);
324
+ maxVal = max(maxVal, s.r);
325
+ }
326
+ matte = maxVal;
327
+ } else if (op >= 5.5 && op < 7.5) {
328
+ // Erode & Choke (Vertical Min)
329
+ let stepScale = max(1.0, radius / 20.0);
330
+ let sampleRadius = min(i32(ceil(radius / stepScale)), 48);
331
+ var minVal = matte;
332
+ for (var i = -sampleRadius; i <= sampleRadius; i++) {
333
+ let offset = vec2<f32>(f32(i) * stepScale) * direction * texelSize;
334
+ let s = textureSampleLevel(texA, samp, in.uv + offset, 0.0);
335
+ minVal = min(minVal, s.r);
336
+ }
337
+ matte = minVal;
338
+ } else if (op >= 7.5) {
339
+ // Feather (Vertical Gaussian Blur)
340
+ let spatial_sigma = max(0.1, radius / 2.0);
341
+ let stepScale = max(1.0, spatial_sigma / 20.0);
342
+ let effectiveSigma = spatial_sigma / stepScale;
343
+ let sampleRadius = min(i32(ceil(3.0 * effectiveSigma)), 48);
344
+ var accum = 0.0;
345
+ var totalWeight = 0.0;
346
+ for (var i = -sampleRadius; i <= sampleRadius; i++) {
347
+ let offset = vec2<f32>(f32(i) * stepScale) * direction * texelSize;
348
+ let x = f32(i) * stepScale;
349
+ let weight = exp(-0.5 * (x / spatial_sigma) * (x / spatial_sigma));
350
+ let s = textureSampleLevel(texA, samp, in.uv + offset, 0.0);
351
+ accum += s.r * weight;
352
+ totalWeight += weight;
353
+ }
354
+ matte = accum / max(totalWeight, 1e-5);
355
+ }
356
+ }
357
+
358
+ // 2. Choke non-linear threshold contraction curve (applies even if radius == 0)
359
+ if (op >= 6.5 && op < 7.5) {
360
+ let threshRange = max(0.01, 1.0 - threshold);
361
+ matte = clamp((matte - threshold) / threshRange, 0.0, 1.0);
362
+ }
363
+
364
+ // Range clamping
365
+ let lowerLimit = min(clampMin, clampMax);
366
+ let upperLimit = max(clampMin, clampMax);
367
+ matte = clamp(matte, lowerLimit, upperLimit);
368
+
369
+ // Optional Binarization
370
+ if (u.binarize > 0.5) {
371
+ matte = select(0.0, 1.0, matte >= threshold);
372
+ }
373
+
374
+ // Optional Inversion
375
+ if (u.invertResult > 0.5) {
376
+ matte = 1.0 - matte;
377
+ }
378
+
379
+ // Output format conversion
380
+ let fmt = u.outputFormat;
381
+ if (fmt < 0.5) {
382
+ // WhiteWithAlpha: vec4(matte, matte, matte, matte) (premultiplied standard mask)
383
+ return vec4<f32>(matte, matte, matte, matte);
384
+ } else if (fmt < 1.5) {
385
+ // GrayscaleRGB: vec4(matte, matte, matte, 1.0)
386
+ return vec4<f32>(matte, matte, matte, 1.0);
387
+ } else if (fmt < 2.5) {
388
+ // AlphaOnly: vec4(0.0, 0.0, 0.0, matte)
389
+ return vec4<f32>(0.0, 0.0, 0.0, matte);
390
+ } else {
391
+ // PassthroughRGB: Mask A color with new alpha
392
+ let origCol = textureSampleLevel(origTexA, samp, in.uv, 0.0);
393
+ if (origCol.a < 1e-4) {
394
+ return vec4<f32>(0.0, 0.0, 0.0, 0.0);
395
+ }
396
+ let unpremult = origCol.rgb / origCol.a;
397
+ return vec4<f32>(unpremult * matte, matte);
398
+ }
399
+ }
400
+ `;
401
+ const deviceResourceCache = /* @__PURE__ */ new WeakMap();
402
+ function getMaskMathResources(device, targetFormat) {
403
+ const cached = deviceResourceCache.get(device);
404
+ if (cached) return cached;
405
+ const uniformLayout = device.createBindGroupLayout({ entries: [{
406
+ binding: 0,
407
+ visibility: GPUShaderStage.FRAGMENT,
408
+ buffer: { type: "uniform" }
409
+ }] });
410
+ const textureLayout = device.createBindGroupLayout({ entries: [
411
+ {
412
+ binding: 0,
413
+ visibility: GPUShaderStage.FRAGMENT,
414
+ texture: { sampleType: "float" }
415
+ },
416
+ {
417
+ binding: 1,
418
+ visibility: GPUShaderStage.FRAGMENT,
419
+ texture: { sampleType: "float" }
420
+ },
421
+ {
422
+ binding: 2,
423
+ visibility: GPUShaderStage.FRAGMENT,
424
+ texture: { sampleType: "float" }
425
+ },
426
+ {
427
+ binding: 3,
428
+ visibility: GPUShaderStage.FRAGMENT,
429
+ sampler: { type: "filtering" }
430
+ }
431
+ ] });
432
+ const signalTextureLayout = device.createBindGroupLayout({ entries: [
433
+ {
434
+ binding: 0,
435
+ visibility: GPUShaderStage.FRAGMENT,
436
+ texture: { sampleType: "float" }
437
+ },
438
+ {
439
+ binding: 1,
440
+ visibility: GPUShaderStage.FRAGMENT,
441
+ texture: { sampleType: "float" }
442
+ },
443
+ {
444
+ binding: 2,
445
+ visibility: GPUShaderStage.FRAGMENT,
446
+ texture: { sampleType: "float" }
447
+ },
448
+ {
449
+ binding: 3,
450
+ visibility: GPUShaderStage.FRAGMENT,
451
+ texture: { sampleType: "float" }
452
+ },
453
+ {
454
+ binding: 4,
455
+ visibility: GPUShaderStage.FRAGMENT,
456
+ sampler: { type: "filtering" }
457
+ }
458
+ ] });
459
+ const shaderModule = device.createShaderModule({ code: WGSL_MASKMATH });
460
+ const pipelineLayout = device.createPipelineLayout({ bindGroupLayouts: [
461
+ uniformLayout,
462
+ textureLayout,
463
+ signalTextureLayout
464
+ ] });
465
+ const res = {
466
+ uniformLayout,
467
+ textureLayout,
468
+ signalTextureLayout,
469
+ pipeline: device.createRenderPipeline({
470
+ layout: pipelineLayout,
471
+ vertex: {
472
+ module: shaderModule,
473
+ entryPoint: "vs"
474
+ },
475
+ fragment: {
476
+ module: shaderModule,
477
+ entryPoint: "fs",
478
+ targets: [{ format: targetFormat }]
479
+ },
480
+ primitive: { topology: "triangle-strip" }
481
+ })
482
+ };
483
+ deviceResourceCache.set(device, res);
484
+ return res;
485
+ }
486
+ const WebGPURenderer = async (args) => {
487
+ const { ctx, encoder, pass, targetView, targetTexture, targetWidth, targetHeight, props, drawChild } = args;
488
+ const op = props.virtualMedia?.operation;
489
+ if (op?.op !== "MaskMath" || !op) return;
490
+ const childMedia = props.virtualMedia?.children?.[0];
491
+ if (!childMedia) return;
492
+ pass.end();
493
+ const frame = props.frame ?? 0;
494
+ const fps = props.fps || 30;
495
+ const width = targetWidth;
496
+ const height = targetHeight;
497
+ const resolveField = (handleId, fallbackVal, clampMin = 0, clampMax = 1e3) => {
498
+ if (handleId && op.inputs?.[handleId]?.connectionValid) {
499
+ const outputItem = op.inputs[handleId].outputItem;
500
+ if (outputItem?.type === "Number") {
501
+ const numVal = typeof outputItem.data === "number" ? outputItem.data : outputItem.data?.value ?? fallbackVal;
502
+ return {
503
+ val: Math.max(clampMin, Math.min(clampMax, numVal)),
504
+ hasStaticSig: false,
505
+ sd: null,
506
+ handleId
507
+ };
508
+ }
509
+ if (outputItem?.type === "Signal" && outputItem.data) return {
510
+ val: fallbackVal,
511
+ hasStaticSig: true,
512
+ sd: outputItem.data,
513
+ handleId
514
+ };
515
+ }
516
+ return {
517
+ val: Math.max(clampMin, Math.min(clampMax, fallbackVal)),
518
+ hasStaticSig: false,
519
+ sd: null,
520
+ handleId
521
+ };
522
+ };
523
+ const radiusRes = resolveField(op.radiusHandleId, Number(op.radius ?? 0), 0, MAX_RADIUS);
524
+ const thresholdRes = resolveField(op.thresholdHandleId, Number(op.threshold ?? .5), 0, 1);
525
+ const clampMinRes = resolveField(op.clampMinHandleId, Number(op.clampMin ?? 0), 0, 1);
526
+ const clampMaxRes = resolveField(op.clampMaxHandleId, Number(op.clampMax ?? 1), 0, 1);
527
+ const tmpATex = ctx.renderer.getTemporaryTexture(width, height, [...props.excludeTextures || [], targetTexture]);
528
+ const tmpAView = tmpATex.createView();
529
+ ctx.renderer.beginFrame(encoder, tmpAView, {
530
+ r: 0,
531
+ g: 0,
532
+ b: 0,
533
+ a: 0
534
+ }, width, height, "clear").end();
535
+ ctx.renderer.pushScissor({
536
+ x: 0,
537
+ y: 0,
538
+ width,
539
+ height
540
+ });
541
+ ctx.renderer.pushIdentity();
542
+ await drawChild(childMedia, {
543
+ ...props,
544
+ virtualMedia: childMedia,
545
+ renderId: `${props.renderId}-maskA`,
546
+ excludeTextures: [...props.excludeTextures || [], tmpATex]
547
+ }, tmpAView, tmpATex, width, height);
548
+ ctx.renderer.popTransform();
549
+ ctx.renderer.popScissor();
550
+ let tmpBTex = null;
551
+ if (op.maskBMedia) {
552
+ tmpBTex = ctx.renderer.getTemporaryTexture(width, height, [
553
+ ...props.excludeTextures || [],
554
+ targetTexture,
555
+ tmpATex
556
+ ]);
557
+ const tmpBView = tmpBTex.createView();
558
+ ctx.renderer.beginFrame(encoder, tmpBView, {
559
+ r: 0,
560
+ g: 0,
561
+ b: 0,
562
+ a: 0
563
+ }, width, height, "clear").end();
564
+ ctx.renderer.pushScissor({
565
+ x: 0,
566
+ y: 0,
567
+ width,
568
+ height
569
+ });
570
+ ctx.renderer.pushIdentity();
571
+ await drawChild(op.maskBMedia, {
572
+ ...props,
573
+ virtualMedia: op.maskBMedia,
574
+ renderId: `${props.renderId}-maskB`,
575
+ excludeTextures: [
576
+ ...props.excludeTextures || [],
577
+ tmpATex,
578
+ tmpBTex
579
+ ]
580
+ }, tmpBView, tmpBTex, width, height);
581
+ ctx.renderer.popTransform();
582
+ ctx.renderer.popScissor();
583
+ }
584
+ const { pipeline, uniformLayout, textureLayout, signalTextureLayout } = getMaskMathResources(ctx.device, ctx.renderer.format);
585
+ const sampler = ctx.renderer.samplerCache.getSampler(ctx.device);
586
+ const elapsedSeconds = props.elapsedMs !== void 0 ? props.elapsedMs / 1e3 : frame / fps;
587
+ const durationSeconds = props.virtualMedia?.metadata?.durationMs ? props.virtualMedia.metadata.durationMs / 1e3 : props.durationMs !== void 0 ? props.durationMs / 1e3 : 0;
588
+ const getSignalView = (res, suffix) => {
589
+ if (res.hasStaticSig && res.sd) {
590
+ const cacheNodeId = res.sd.nodeId ?? res.handleId ?? `${props.renderId ?? "maskmath"}_${suffix}`;
591
+ return signalRegistry.getOrCreate2DTextureView(ctx.device, encoder, cacheNodeId, elapsedSeconds, durationSeconds, res.sd, width, height, props.renderId, frame, fps);
592
+ }
593
+ return signalRegistry.getDummy1x1TextureView(ctx.device);
594
+ };
595
+ const radiusView = getSignalView(radiusRes, "radius_sig");
596
+ const threshView = getSignalView(thresholdRes, "threshold_sig");
597
+ const clampMinView = getSignalView(clampMinRes, "clampMin_sig");
598
+ const clampMaxView = getSignalView(clampMaxRes, "clampMax_sig");
599
+ const sigBindGroup = ctx.device.createBindGroup({
600
+ layout: signalTextureLayout,
601
+ entries: [
602
+ {
603
+ binding: 0,
604
+ resource: radiusView
605
+ },
606
+ {
607
+ binding: 1,
608
+ resource: threshView
609
+ },
610
+ {
611
+ binding: 2,
612
+ resource: clampMinView
613
+ },
614
+ {
615
+ binding: 3,
616
+ resource: clampMaxView
617
+ },
618
+ {
619
+ binding: 4,
620
+ resource: sampler
621
+ }
622
+ ]
623
+ });
624
+ const operationCode = OPERATION_MAP[op.operation ?? "Union"] ?? 0;
625
+ const channelACode = CHANNEL_MAP[op.channelA ?? "Alpha"] ?? 0;
626
+ const channelBCode = CHANNEL_MAP[op.channelB ?? "Alpha"] ?? 0;
627
+ const outputFormatCode = OUTPUT_FORMAT_MAP[op.outputFormat ?? "WhiteWithAlpha"] ?? 0;
628
+ const dummyBView = tmpBTex ? tmpBTex.createView() : tmpAView;
629
+ const hUniformData = new Float32Array(20);
630
+ const vUniformData = new Float32Array(20);
631
+ const baseUniforms = [
632
+ 0,
633
+ 0,
634
+ radiusRes.val,
635
+ thresholdRes.val,
636
+ clampMinRes.val,
637
+ clampMaxRes.val,
638
+ operationCode,
639
+ channelACode,
640
+ channelBCode,
641
+ op.binarize ? 1 : 0,
642
+ op.invertResult ? 1 : 0,
643
+ outputFormatCode,
644
+ tmpBTex ? 1 : 0,
645
+ radiusRes.hasStaticSig ? 1 : 0,
646
+ thresholdRes.hasStaticSig ? 1 : 0,
647
+ clampMinRes.hasStaticSig ? 1 : 0,
648
+ clampMaxRes.hasStaticSig ? 1 : 0,
649
+ 0,
650
+ 0,
651
+ 0
652
+ ];
653
+ hUniformData.set(baseUniforms);
654
+ hUniformData[0] = 1;
655
+ hUniformData[1] = 0;
656
+ hUniformData[17] = 0;
657
+ vUniformData.set(baseUniforms);
658
+ vUniformData[0] = 0;
659
+ vUniformData[1] = 1;
660
+ vUniformData[17] = 1;
661
+ const outTex1 = ctx.renderer.getTemporaryTexture(width, height, [
662
+ tmpATex,
663
+ ...tmpBTex ? [tmpBTex] : [],
664
+ targetTexture,
665
+ ...props.excludeTextures || []
666
+ ]);
667
+ const outTex2 = ctx.renderer.getTemporaryTexture(width, height, [
668
+ tmpATex,
669
+ ...tmpBTex ? [tmpBTex] : [],
670
+ outTex1,
671
+ targetTexture,
672
+ ...props.excludeTextures || []
673
+ ]);
674
+ const hBuffer = ctx.renderer.getTemporaryBuffer(hUniformData);
675
+ const hPass = encoder.beginRenderPass({ colorAttachments: [{
676
+ view: outTex1.createView(),
677
+ loadOp: "clear",
678
+ storeOp: "store",
679
+ clearValue: {
680
+ r: 0,
681
+ g: 0,
682
+ b: 0,
683
+ a: 0
684
+ }
685
+ }] });
686
+ hPass.setPipeline(pipeline);
687
+ hPass.setBindGroup(0, ctx.device.createBindGroup({
688
+ layout: uniformLayout,
689
+ entries: [{
690
+ binding: 0,
691
+ resource: { buffer: hBuffer }
692
+ }]
693
+ }));
694
+ hPass.setBindGroup(1, ctx.device.createBindGroup({
695
+ layout: textureLayout,
696
+ entries: [
697
+ {
698
+ binding: 0,
699
+ resource: tmpAView
700
+ },
701
+ {
702
+ binding: 1,
703
+ resource: dummyBView
704
+ },
705
+ {
706
+ binding: 2,
707
+ resource: tmpAView
708
+ },
709
+ {
710
+ binding: 3,
711
+ resource: sampler
712
+ }
713
+ ]
714
+ }));
715
+ hPass.setBindGroup(2, sigBindGroup);
716
+ hPass.draw(4);
717
+ hPass.end();
718
+ const vBuffer = ctx.renderer.getTemporaryBuffer(vUniformData);
719
+ const vPass = encoder.beginRenderPass({ colorAttachments: [{
720
+ view: outTex2.createView(),
721
+ loadOp: "clear",
722
+ storeOp: "store",
723
+ clearValue: {
724
+ r: 0,
725
+ g: 0,
726
+ b: 0,
727
+ a: 0
728
+ }
729
+ }] });
730
+ vPass.setPipeline(pipeline);
731
+ vPass.setBindGroup(0, ctx.device.createBindGroup({
732
+ layout: uniformLayout,
733
+ entries: [{
734
+ binding: 0,
735
+ resource: { buffer: vBuffer }
736
+ }]
737
+ }));
738
+ vPass.setBindGroup(1, ctx.device.createBindGroup({
739
+ layout: textureLayout,
740
+ entries: [
741
+ {
742
+ binding: 0,
743
+ resource: outTex1.createView()
744
+ },
745
+ {
746
+ binding: 1,
747
+ resource: dummyBView
748
+ },
749
+ {
750
+ binding: 2,
751
+ resource: tmpAView
752
+ },
753
+ {
754
+ binding: 3,
755
+ resource: sampler
756
+ }
757
+ ]
758
+ }));
759
+ vPass.setBindGroup(2, sigBindGroup);
760
+ vPass.draw(4);
761
+ vPass.end();
762
+ const finalPass = ctx.renderer.beginFrame(encoder, targetView, {
763
+ r: 0,
764
+ g: 0,
765
+ b: 0,
766
+ a: 0
767
+ }, targetWidth, targetHeight, "load");
768
+ const finalOpacity = props.opacity ?? op.opacity ?? 1;
769
+ ctx.renderer.drawTexture(finalPass, outTex2, {
770
+ x: 0,
771
+ y: 0,
772
+ width: targetWidth,
773
+ height: targetHeight
774
+ }, { opacity: finalOpacity });
775
+ args.pass = finalPass;
776
+ };
777
+
778
+ //#endregion
779
+ //#region src/renderers/index.ts
780
+ var renderers_default = defineRenderer({ WebGPURenderer });
781
+
782
+ //#endregion
783
+ export { MaskChannelEnum as a, MaskOperationEnum as c, MAX_RADIUS as i, MaskOutputFormatEnum as l, WebGPURenderer as n, MaskMathNodeConfigSchema as o, MASK_MATH_OUTPUT_TYPE_MAP as r, MaskMathResultSchema as s, renderers_default as t, maskMathConfig as u };
784
+ //# sourceMappingURL=renderers-B_QRX8uG.mjs.map