@pexip/media-processor 20.3.5 → 21.0.0
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/CHANGELOG.md +39 -0
- package/api-docs/README.mdx +33 -6
- package/api-docs/functions/createBenchmark.mdx +17 -5
- package/api-docs/functions/createWindowedStats.mdx +23 -0
- package/api-docs/functions/iou.mdx +19 -0
- package/api-docs/functions/labelComponents.mdx +20 -0
- package/api-docs/functions/nearestPowerOfTwo.mdx +21 -0
- package/api-docs/functions/playVideo.mdx +16 -0
- package/api-docs/functions/scoreComponent.mdx +22 -0
- package/api-docs/functions/setVideoElementSrc.mdx +14 -0
- package/api-docs/interfaces/Benchmark.mdx +12 -0
- package/api-docs/interfaces/RendererOptions.mdx +26 -9
- package/api-docs/interfaces/SegmentationParams.mdx +29 -12
- package/api-docs/interfaces/SelectionOptions.mdx +15 -0
- package/api-docs/interfaces/Stats.mdx +11 -0
- package/api-docs/interfaces/Weights.mdx +12 -0
- package/api-docs/type-aliases/OptionalKeys.mdx +10 -0
- package/api-docs/variables/BACKGROUND_THRESHOLD.mdx +3 -0
- package/api-docs/variables/DOWN_SAMPLE_FACTOR.mdx +3 -0
- package/api-docs/variables/EDGE_BLUR_AMOUNT.mdx +1 -1
- package/api-docs/variables/EXCLUDE_BYSTANDERS.mdx +3 -0
- package/api-docs/variables/FOREGROUND_THRESHOLD.mdx +1 -1
- package/api-docs/variables/LIGHT_WRAP_BLUR_AMOUNT.mdx +3 -0
- package/api-docs/variables/LIGHT_WRAP_EDGE_BAND.mdx +3 -0
- package/api-docs/variables/LIGHT_WRAP_INTENSITY.mdx +3 -0
- package/api-docs/variables/LIGHT_WRAP_TIGHTNESS.mdx +3 -0
- package/api-docs/variables/MASK_COMBINE_RATIO.mdx +1 -1
- package/api-docs/variables/MORPH_DILATE_RADIUS_PX.mdx +3 -0
- package/api-docs/variables/MORPH_ERODE_RADIUS_PX.mdx +3 -0
- package/api-docs/variables/MORPH_PASS.mdx +3 -0
- package/api-docs/variables/PERSON_CENTER.mdx +3 -0
- package/api-docs/variables/SIGMA_RANGE.mdx +3 -0
- package/api-docs/variables/SIGMA_SPACE.mdx +3 -0
- package/api-docs/variables/STRONG_EDGE_BLUR_AMOUNT.mdx +3 -0
- package/api-docs/variables/getCanvasRenderingContext2D.mdx +14 -0
- package/api-docs/variables/getImageSize.mdx +13 -0
- package/dist/common/backends/webgl/binaryMask.d.ts +8 -0
- package/dist/common/backends/webgl/binaryMask.js +95 -0
- package/dist/common/backends/webgl/blender.d.ts +2 -1
- package/dist/common/backends/webgl/blender.js +18 -15
- package/dist/common/backends/webgl/blur.d.ts +1 -1
- package/dist/common/backends/webgl/blur.js +23 -16
- package/dist/common/backends/webgl/canvas.js +9 -4
- package/dist/common/backends/webgl/diffAbsMask.d.ts +10 -0
- package/dist/common/backends/webgl/diffAbsMask.js +103 -0
- package/dist/common/backends/webgl/geodesicReconstruction.d.ts +13 -0
- package/dist/common/backends/webgl/geodesicReconstruction.js +176 -0
- package/dist/common/backends/webgl/jointBilateralFilter.d.ts +12 -0
- package/dist/common/backends/webgl/jointBilateralFilter.js +108 -0
- package/dist/common/backends/webgl/morphErode.d.ts +13 -0
- package/dist/common/backends/webgl/morphErode.js +121 -0
- package/dist/common/backends/webgl/renderer.js +194 -34
- package/dist/common/backends/webgl/replace.d.ts +17 -0
- package/dist/common/backends/webgl/replace.js +126 -0
- package/dist/common/backends/webgl/selectComponent.d.ts +15 -0
- package/dist/common/backends/webgl/selectComponent.js +102 -0
- package/dist/common/backends/webgl/smoothingMask.d.ts +3 -3
- package/dist/common/backends/webgl/smoothingMask.js +22 -21
- package/dist/common/backends/webgl/tentBlur.d.ts +9 -0
- package/dist/common/backends/webgl/tentBlur.js +118 -0
- package/dist/common/backends/webgl/texture.d.ts +1 -1
- package/dist/common/backends/webgl/texture.js +19 -17
- package/dist/common/backends/webgl/textureToTexture.d.ts +1 -1
- package/dist/common/backends/webgl/textureToTexture.js +19 -16
- package/dist/common/backends/webgl/webglUtils.d.ts +7 -1
- package/dist/common/backends/webgl/webglUtils.js +64 -6
- package/dist/common/backends/webgpu/blender.d.ts +6 -1
- package/dist/common/backends/webgpu/blender.js +96 -51
- package/dist/common/backends/webgpu/constants.js +1 -1
- package/dist/common/backends/webgpu/dualFilterBlur.d.ts +2 -3
- package/dist/common/backends/webgpu/dualFilterBlur.js +146 -140
- package/dist/common/backends/webgpu/jointBilateralFilter.d.ts +7 -0
- package/dist/common/backends/webgpu/jointBilateralFilter.js +162 -0
- package/dist/common/backends/webgpu/renderer.js +211 -39
- package/dist/common/backends/webgpu/replace.d.ts +11 -0
- package/dist/common/backends/webgpu/replace.js +175 -0
- package/dist/common/backends/webgpu/resize.d.ts +4 -0
- package/dist/common/backends/webgpu/resize.js +76 -0
- package/dist/common/backends/webgpu/smoothingMask.d.ts +2 -2
- package/dist/common/backends/webgpu/smoothingMask.js +74 -40
- package/dist/common/backends/webgpu/tentBlur.d.ts +5 -0
- package/dist/common/backends/webgpu/tentBlur.js +131 -0
- package/dist/common/backends/webgpu/texture.d.ts +2 -2
- package/dist/common/backends/webgpu/texture.js +24 -19
- package/dist/common/backends/webgpu/textureToTexture.d.ts +1 -1
- package/dist/common/backends/webgpu/textureToTexture.js +28 -15
- package/dist/common/backends/webgpu/types.d.ts +2 -0
- package/dist/common/backends/webgpu/webgpuUtils.d.ts +24 -4
- package/dist/common/backends/webgpu/webgpuUtils.js +60 -11
- package/dist/common/constants.d.ts +19 -5
- package/dist/common/constants.js +19 -5
- package/dist/common/index.d.ts +1 -0
- package/dist/common/index.js +1 -0
- package/dist/common/tsconfig.tsbuildinfo +1 -1
- package/dist/common/types/render.d.ts +142 -13
- package/dist/common/types/utils.d.ts +21 -0
- package/dist/common/utils.d.ts +16 -0
- package/dist/common/utils.js +152 -0
- package/dist/main/benchUtils.d.ts +33 -1
- package/dist/main/benchUtils.js +145 -11
- package/dist/main/tsconfig.tsbuildinfo +1 -1
- package/dist/main/video/canvasTransform.d.ts +1 -1
- package/dist/main/video/canvasTransform.js +135 -47
- package/dist/main/video/index.d.ts +1 -1
- package/dist/main/video/index.js +1 -1
- package/dist/workers/mediaWorker.js +35 -162
- package/dist/workers/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -7
- package/dist/common/backends/webgl/shaders/blendFragmentShader.d.ts +0 -1
- package/dist/common/backends/webgl/shaders/blendFragmentShader.js +0 -21
- package/dist/common/backends/webgl/shaders/dualFilterBlurFragmentShader.d.ts +0 -1
- package/dist/common/backends/webgl/shaders/dualFilterBlurFragmentShader.js +0 -45
- package/dist/common/backends/webgl/shaders/smoothingMaskFragmentShader.d.ts +0 -1
- package/dist/common/backends/webgl/shaders/smoothingMaskFragmentShader.js +0 -57
- package/dist/common/backends/webgl/shaders/textureFragmentShader.d.ts +0 -1
- package/dist/common/backends/webgl/shaders/textureFragmentShader.js +0 -16
- package/dist/common/backends/webgl/shaders/vertexShader.d.ts +0 -1
- package/dist/common/backends/webgl/shaders/vertexShader.js +0 -19
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
import type { RendererOptions } from '../../types/render';
|
|
1
2
|
/**
|
|
2
3
|
*
|
|
3
4
|
* Blend foreground and background according to the mask
|
|
4
5
|
*/
|
|
5
6
|
export declare const createBlendRenderer: (processor: OffscreenCanvas, device: GPUDevice, presentationFormat: GPUTextureFormat, isContextLost: () => boolean) => {
|
|
6
|
-
render: (
|
|
7
|
+
render: (encoder: GPUCommandEncoder, { foreground, background, mask, }: {
|
|
8
|
+
foreground: GPUTexture;
|
|
9
|
+
background: GPUTexture;
|
|
10
|
+
mask: GPUTexture;
|
|
11
|
+
}, options: RendererOptions) => void;
|
|
7
12
|
release: () => void;
|
|
8
13
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { makeShaderDataDefinitions, makeStructuredView } from 'webgpu-utils';
|
|
1
2
|
import { assert } from '@pexip/utils';
|
|
2
3
|
import { RENDERING_EVENTS } from '../../constants';
|
|
3
4
|
import blenderShader from './shaders/blenderShader.wgsl';
|
|
4
|
-
import { RENDER_PASS_COLOR_ATTACHMENT_CLEAR_VALUE } from './constants';
|
|
5
5
|
/**
|
|
6
6
|
*
|
|
7
7
|
* Blend foreground and background according to the mask
|
|
@@ -16,63 +16,108 @@ export const createBlendRenderer = (processor, device, presentationFormat, isCon
|
|
|
16
16
|
format: presentationFormat,
|
|
17
17
|
alphaMode: 'premultiplied',
|
|
18
18
|
});
|
|
19
|
+
const defs = makeShaderDataDefinitions(blenderShader);
|
|
20
|
+
// @ts-expect-error This is what we get from the API
|
|
21
|
+
const uniforms = makeStructuredView(defs.uniforms.thresholds);
|
|
22
|
+
// Cache shader module and pipeline at init time
|
|
23
|
+
const shaderModule = device.createShaderModule({
|
|
24
|
+
label: 'blender shader',
|
|
25
|
+
code: blenderShader,
|
|
26
|
+
});
|
|
27
|
+
const pipeline = device.createRenderPipeline({
|
|
28
|
+
label: 'blender pipeline',
|
|
29
|
+
layout: 'auto',
|
|
30
|
+
vertex: {
|
|
31
|
+
module: shaderModule,
|
|
32
|
+
},
|
|
33
|
+
fragment: {
|
|
34
|
+
module: shaderModule,
|
|
35
|
+
targets: [{ format: presentationFormat }],
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
// Cache sampler at init time
|
|
39
|
+
const linearSampler = device.createSampler({
|
|
40
|
+
addressModeU: 'clamp-to-edge',
|
|
41
|
+
addressModeV: 'clamp-to-edge',
|
|
42
|
+
magFilter: 'linear',
|
|
43
|
+
minFilter: 'linear',
|
|
44
|
+
});
|
|
45
|
+
const nearestSampler = device.createSampler({
|
|
46
|
+
addressModeU: 'clamp-to-edge',
|
|
47
|
+
addressModeV: 'clamp-to-edge',
|
|
48
|
+
magFilter: 'nearest',
|
|
49
|
+
minFilter: 'nearest',
|
|
50
|
+
});
|
|
51
|
+
const uniformsBuffer = device.createBuffer({
|
|
52
|
+
size: uniforms.arrayBuffer.byteLength,
|
|
53
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
54
|
+
});
|
|
55
|
+
const updateUniforms = (options) => {
|
|
56
|
+
uniforms.set({
|
|
57
|
+
background: options.backgroundThreshold,
|
|
58
|
+
foreground: options.foregroundThreshold,
|
|
59
|
+
});
|
|
60
|
+
// Upload to GPU
|
|
61
|
+
device.queue.writeBuffer(uniformsBuffer, 0, uniforms.arrayBuffer);
|
|
62
|
+
};
|
|
63
|
+
const colorAttachment0 = {
|
|
64
|
+
loadOp: 'load',
|
|
65
|
+
storeOp: 'store',
|
|
66
|
+
};
|
|
67
|
+
const renderPassDescriptor = {
|
|
68
|
+
label: 'blender renderPass',
|
|
69
|
+
colorAttachments: [colorAttachment0],
|
|
70
|
+
};
|
|
71
|
+
const bindGroup0 = device.createBindGroup({
|
|
72
|
+
layout: pipeline.getBindGroupLayout(0),
|
|
73
|
+
entries: [
|
|
74
|
+
{ binding: 0, resource: linearSampler },
|
|
75
|
+
{ binding: 1, resource: nearestSampler },
|
|
76
|
+
{ binding: 2, resource: { buffer: uniformsBuffer } },
|
|
77
|
+
],
|
|
78
|
+
});
|
|
79
|
+
let cachedBindGroup1 = null;
|
|
80
|
+
let cachedMask = null;
|
|
81
|
+
let cachedBackground = null;
|
|
82
|
+
let cachedForeground = null;
|
|
83
|
+
let cachedBackgroundThreshold = -1;
|
|
84
|
+
let cachedForegroundThreshold = -1;
|
|
19
85
|
return {
|
|
20
|
-
render: (
|
|
86
|
+
render: (encoder, { foreground, background, mask, }, options) => {
|
|
21
87
|
assert(!isContextLost(), RENDERING_EVENTS.ContextLost);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
{
|
|
46
|
-
view: context.getCurrentTexture().createView(),
|
|
47
|
-
clearValue: RENDER_PASS_COLOR_ATTACHMENT_CLEAR_VALUE,
|
|
48
|
-
loadOp: 'clear',
|
|
49
|
-
storeOp: 'store',
|
|
50
|
-
},
|
|
51
|
-
],
|
|
52
|
-
});
|
|
88
|
+
colorAttachment0.view = context.getCurrentTexture().createView();
|
|
89
|
+
if (!(cachedBackground === background &&
|
|
90
|
+
cachedForeground === foreground &&
|
|
91
|
+
cachedMask === mask)) {
|
|
92
|
+
cachedBindGroup1 = device.createBindGroup({
|
|
93
|
+
layout: pipeline.getBindGroupLayout(1),
|
|
94
|
+
entries: [
|
|
95
|
+
{ binding: 0, resource: background.createView() },
|
|
96
|
+
{ binding: 1, resource: foreground.createView() },
|
|
97
|
+
{ binding: 2, resource: mask.createView() },
|
|
98
|
+
],
|
|
99
|
+
});
|
|
100
|
+
cachedBackground = background;
|
|
101
|
+
cachedForeground = foreground;
|
|
102
|
+
cachedMask = mask;
|
|
103
|
+
}
|
|
104
|
+
if (!(cachedBackgroundThreshold === options.backgroundThreshold &&
|
|
105
|
+
cachedForegroundThreshold === options.foregroundThreshold)) {
|
|
106
|
+
updateUniforms(options);
|
|
107
|
+
cachedBackgroundThreshold = options.backgroundThreshold;
|
|
108
|
+
cachedForegroundThreshold = options.foregroundThreshold;
|
|
109
|
+
}
|
|
110
|
+
const pass = encoder.beginRenderPass(renderPassDescriptor);
|
|
53
111
|
pass.setPipeline(pipeline);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
addressModeV: 'repeat',
|
|
57
|
-
magFilter: 'nearest',
|
|
58
|
-
});
|
|
59
|
-
const bindGroup = device.createBindGroup({
|
|
60
|
-
layout: pipeline.getBindGroupLayout(0),
|
|
61
|
-
entries: [
|
|
62
|
-
{ binding: 0, resource: sampler },
|
|
63
|
-
{ binding: 1, resource: bgTexture.createView() },
|
|
64
|
-
{ binding: 2, resource: frameTexture.createView() },
|
|
65
|
-
{ binding: 3, resource: maskTexture.createView() },
|
|
66
|
-
],
|
|
67
|
-
});
|
|
68
|
-
pass.setBindGroup(0, bindGroup);
|
|
112
|
+
pass.setBindGroup(0, bindGroup0);
|
|
113
|
+
pass.setBindGroup(1, cachedBindGroup1);
|
|
69
114
|
pass.draw(6); // call our vertex shader 6 times
|
|
70
115
|
pass.end();
|
|
71
|
-
const commandBuffer = encoder.finish();
|
|
72
|
-
device.queue.submit([commandBuffer]);
|
|
73
116
|
},
|
|
74
117
|
release: () => {
|
|
75
|
-
|
|
118
|
+
uniformsBuffer.destroy();
|
|
119
|
+
cachedBackgroundThreshold = -1;
|
|
120
|
+
cachedForegroundThreshold = -1;
|
|
76
121
|
},
|
|
77
122
|
};
|
|
78
123
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const RENDER_PASS_COLOR_ATTACHMENT_CLEAR_VALUE = [0.0, 0.0, 0.0, 0];
|
|
1
|
+
export const RENDER_PASS_COLOR_ATTACHMENT_CLEAR_VALUE = [0.0, 0.0, 0.0, 1.0];
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
render: (frame: VideoFrameLike, pass: number) => GPUTexture;
|
|
1
|
+
export declare const createBlurRenderer: (device: GPUDevice, width: number, height: number, textureFormat: GPUTextureFormat, isContextLost: () => boolean) => {
|
|
2
|
+
render: (encoder: GPUCommandEncoder, frame: GPUTexture, pass: number) => GPUTexture;
|
|
4
3
|
release: () => void;
|
|
5
4
|
};
|
|
@@ -1,192 +1,194 @@
|
|
|
1
1
|
import { assert } from '@pexip/utils';
|
|
2
2
|
import { RENDERING_EVENTS } from '../../constants';
|
|
3
|
-
import
|
|
3
|
+
import blurComputeShader from './shaders/dualBlurComputeShader.wgsl';
|
|
4
4
|
import { clamping } from '../../utils';
|
|
5
|
-
import { RENDER_PASS_COLOR_ATTACHMENT_CLEAR_VALUE } from './constants';
|
|
6
5
|
const clampBlurAmount = clamping(1, 9);
|
|
7
|
-
export const createBlurRenderer = (
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
context.configure({
|
|
13
|
-
device,
|
|
14
|
-
format: presentationFormat,
|
|
15
|
-
alphaMode: 'premultiplied',
|
|
16
|
-
});
|
|
17
|
-
// Helper to create a texture of given size
|
|
18
|
-
const createTexture = (width, height) => device.createTexture({
|
|
19
|
-
format: presentationFormat,
|
|
20
|
-
size: [width, height],
|
|
21
|
-
usage: GPUTextureUsage.TEXTURE_BINDING |
|
|
22
|
-
GPUTextureUsage.COPY_DST |
|
|
23
|
-
GPUTextureUsage.RENDER_ATTACHMENT,
|
|
24
|
-
});
|
|
25
|
-
// Helper to create a uniform buffer to flip downsampling vs upsampling
|
|
26
|
-
const createFlipBuffer = (value) => {
|
|
27
|
-
const buffer = device.createBuffer({
|
|
28
|
-
size: 4,
|
|
29
|
-
mappedAtCreation: true,
|
|
30
|
-
usage: GPUBufferUsage.UNIFORM,
|
|
31
|
-
});
|
|
32
|
-
new Uint32Array(buffer.getMappedRange())[0] = value;
|
|
33
|
-
buffer.unmap();
|
|
34
|
-
return buffer;
|
|
35
|
-
};
|
|
36
|
-
const sampler = device.createSampler({
|
|
37
|
-
magFilter: 'linear',
|
|
38
|
-
minFilter: 'linear',
|
|
6
|
+
export const createBlurRenderer = (device, width, height, textureFormat, isContextLost) => {
|
|
7
|
+
const shaderModule = device.createShaderModule({
|
|
8
|
+
label: 'dual blur compute shader',
|
|
9
|
+
code: blurComputeShader,
|
|
39
10
|
});
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
label: 'dual blur render pipeline',
|
|
11
|
+
const downsamplePipeline = device.createRenderPipeline({
|
|
12
|
+
label: 'dual blur downsample compute pipeline',
|
|
43
13
|
layout: 'auto',
|
|
44
|
-
vertex: {
|
|
14
|
+
vertex: { module: shaderModule, entryPoint: 'vs_main' },
|
|
15
|
+
fragment: {
|
|
45
16
|
module: shaderModule,
|
|
46
|
-
|
|
47
|
-
|
|
17
|
+
targets: [{ format: textureFormat }],
|
|
18
|
+
entryPoint: 'downsample_fs',
|
|
48
19
|
},
|
|
20
|
+
});
|
|
21
|
+
const upsamplePipeline = device.createRenderPipeline({
|
|
22
|
+
label: 'dual blur upsample compute pipeline',
|
|
23
|
+
layout: 'auto',
|
|
24
|
+
vertex: { module: shaderModule, entryPoint: 'vs_main' },
|
|
49
25
|
fragment: {
|
|
50
26
|
module: shaderModule,
|
|
51
|
-
|
|
52
|
-
|
|
27
|
+
targets: [{ format: textureFormat }],
|
|
28
|
+
entryPoint: 'upsample_fs',
|
|
53
29
|
},
|
|
54
|
-
primitive: { topology: 'triangle-list' },
|
|
55
30
|
});
|
|
56
|
-
|
|
31
|
+
const sampler = device.createSampler({
|
|
32
|
+
magFilter: 'linear',
|
|
33
|
+
minFilter: 'linear',
|
|
34
|
+
addressModeU: 'clamp-to-edge',
|
|
35
|
+
addressModeV: 'clamp-to-edge',
|
|
36
|
+
});
|
|
37
|
+
// Helper to create a texture of given size
|
|
38
|
+
const createTexture = (w, h) => device.createTexture({
|
|
39
|
+
label: 'dual filter blur texture',
|
|
40
|
+
format: textureFormat,
|
|
41
|
+
size: [w, h],
|
|
42
|
+
usage: GPUTextureUsage.TEXTURE_BINDING |
|
|
43
|
+
GPUTextureUsage.RENDER_ATTACHMENT,
|
|
44
|
+
});
|
|
45
|
+
// --- State for persistent textures and bind groups ---
|
|
57
46
|
let blurTextures = [];
|
|
58
47
|
let lastWidth = -1;
|
|
59
48
|
let lastHeight = -1;
|
|
60
|
-
|
|
61
|
-
|
|
49
|
+
let downsampleRenderPassDescriptor = [];
|
|
50
|
+
let upsampleRenderPassDescriptor = [];
|
|
51
|
+
let downsampleBindGroups = [];
|
|
52
|
+
let upsampleBindGroups = [];
|
|
53
|
+
let cacheFrameTexture = null;
|
|
54
|
+
let cacheFirstDownsampleBindGroup = null;
|
|
55
|
+
let cacheFirstDownsampleRenderPassDescriptor = null;
|
|
56
|
+
// Helper to (re)generate textures and bind groups if needed
|
|
57
|
+
function ensureBlurTextures(pass, frameWidth, frameHeight) {
|
|
62
58
|
const length = clampBlurAmount(pass + 1);
|
|
63
|
-
let needsUpdate = false;
|
|
64
59
|
if (blurTextures.length !== length ||
|
|
65
|
-
lastWidth !==
|
|
66
|
-
lastHeight !==
|
|
60
|
+
lastWidth !== frameWidth ||
|
|
61
|
+
lastHeight !== frameHeight) {
|
|
67
62
|
// Destroy old textures
|
|
68
63
|
for (const t of blurTextures) {
|
|
69
64
|
t.texture.destroy();
|
|
70
65
|
}
|
|
71
66
|
blurTextures = [];
|
|
67
|
+
downsampleBindGroups = [];
|
|
68
|
+
downsampleRenderPassDescriptor = [];
|
|
69
|
+
upsampleBindGroups = [];
|
|
70
|
+
// Invalidate frame cache since firstDst view has changed
|
|
71
|
+
cacheFrameTexture = null;
|
|
72
|
+
cacheFirstDownsampleBindGroup = null;
|
|
72
73
|
for (let i = 0; i < length; i++) {
|
|
73
|
-
const w = Math.max(Math.trunc(
|
|
74
|
-
const h = Math.max(Math.trunc(
|
|
74
|
+
const w = Math.max(Math.trunc(frameWidth / 2 ** i), 1);
|
|
75
|
+
const h = Math.max(Math.trunc(frameHeight / 2 ** i), 1);
|
|
76
|
+
const texture = createTexture(w, h);
|
|
75
77
|
blurTextures.push({
|
|
76
|
-
texture
|
|
78
|
+
texture,
|
|
79
|
+
view: texture.createView(),
|
|
77
80
|
width: w,
|
|
78
81
|
height: h,
|
|
79
82
|
});
|
|
80
83
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
needsUpdate = true;
|
|
84
|
-
}
|
|
85
|
-
return needsUpdate;
|
|
86
|
-
}
|
|
87
|
-
const flipDownBuffer = createFlipBuffer(0); // create once
|
|
88
|
-
const flipUpBuffer = createFlipBuffer(1); // create once
|
|
89
|
-
return {
|
|
90
|
-
render: (frame, pass) => {
|
|
91
|
-
assert(!isContextLost(), RENDERING_EVENTS.ContextLost);
|
|
92
|
-
// Ensure textures and buffers are up-to-date
|
|
93
|
-
ensureBlurTextures(pass, processor.width, processor.height);
|
|
94
|
-
// Upload frame to the largest texture (level 0)
|
|
95
|
-
const largestTexture = blurTextures[0];
|
|
96
|
-
assert(largestTexture);
|
|
97
|
-
device.queue.copyExternalImageToTexture({ source: frame.frame, flipY: false }, { texture: largestTexture.texture }, { width: largestTexture.width, height: largestTexture.height });
|
|
98
|
-
// Downsampling passes
|
|
99
|
-
for (let i = 1; i < blurTextures.length; i++) {
|
|
84
|
+
// Pre-create downsample bind groups: blurTextures[i-1] → blurTextures[i]
|
|
85
|
+
for (let i = 2; i < blurTextures.length; i++) {
|
|
100
86
|
const src = blurTextures[i - 1];
|
|
101
|
-
assert(src);
|
|
102
87
|
const dst = blurTextures[i];
|
|
88
|
+
assert(src);
|
|
103
89
|
assert(dst);
|
|
104
|
-
|
|
105
|
-
layout:
|
|
90
|
+
downsampleBindGroups.push(device.createBindGroup({
|
|
91
|
+
layout: downsamplePipeline.getBindGroupLayout(0),
|
|
106
92
|
entries: [
|
|
107
93
|
{ binding: 0, resource: sampler },
|
|
108
|
-
{ binding: 1, resource: src.
|
|
109
|
-
{ binding: 2, resource: { buffer: flipDownBuffer } },
|
|
94
|
+
{ binding: 1, resource: src.view },
|
|
110
95
|
],
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
|
|
96
|
+
}));
|
|
97
|
+
downsampleRenderPassDescriptor.push({
|
|
98
|
+
label: `downsample render pass ${i}`,
|
|
114
99
|
colorAttachments: [
|
|
115
|
-
{
|
|
116
|
-
view: dst.texture.createView(),
|
|
117
|
-
loadOp: 'clear',
|
|
118
|
-
storeOp: 'store',
|
|
119
|
-
clearValue: RENDER_PASS_COLOR_ATTACHMENT_CLEAR_VALUE,
|
|
120
|
-
},
|
|
100
|
+
{ loadOp: 'load', storeOp: 'store', view: dst.view },
|
|
121
101
|
],
|
|
122
102
|
});
|
|
123
|
-
passEncoder.setPipeline(pipeline);
|
|
124
|
-
passEncoder.setBindGroup(0, bindGroup);
|
|
125
|
-
passEncoder.draw(6);
|
|
126
|
-
passEncoder.end();
|
|
127
|
-
device.queue.submit([encoder.finish()]);
|
|
128
103
|
}
|
|
129
|
-
//
|
|
104
|
+
// Pre-create upsample bind groups: blurTextures[i+1] → blurTextures[i]
|
|
105
|
+
// Filled in descending order so upsampleBindGroups[0] is the deepest pair.
|
|
130
106
|
for (let i = blurTextures.length - 2; i >= 0; i--) {
|
|
131
107
|
const src = blurTextures[i + 1];
|
|
132
|
-
assert(src);
|
|
133
108
|
const dst = blurTextures[i];
|
|
109
|
+
assert(src);
|
|
134
110
|
assert(dst);
|
|
135
|
-
|
|
136
|
-
layout:
|
|
111
|
+
upsampleBindGroups.push(device.createBindGroup({
|
|
112
|
+
layout: upsamplePipeline.getBindGroupLayout(0),
|
|
137
113
|
entries: [
|
|
138
114
|
{ binding: 0, resource: sampler },
|
|
139
|
-
{ binding: 1, resource: src.
|
|
140
|
-
{ binding: 2, resource: { buffer: flipUpBuffer } },
|
|
115
|
+
{ binding: 1, resource: src.view },
|
|
141
116
|
],
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
|
|
117
|
+
}));
|
|
118
|
+
upsampleRenderPassDescriptor.push({
|
|
119
|
+
label: `upsample render pass ${i}`,
|
|
145
120
|
colorAttachments: [
|
|
146
|
-
{
|
|
147
|
-
view: dst.texture.createView(),
|
|
148
|
-
loadOp: 'clear',
|
|
149
|
-
storeOp: 'store',
|
|
150
|
-
clearValue: RENDER_PASS_COLOR_ATTACHMENT_CLEAR_VALUE,
|
|
151
|
-
},
|
|
121
|
+
{ loadOp: 'load', storeOp: 'store', view: dst.view },
|
|
152
122
|
],
|
|
153
123
|
});
|
|
154
|
-
passEncoder.setPipeline(pipeline);
|
|
155
|
-
passEncoder.setBindGroup(0, bindGroup);
|
|
156
|
-
passEncoder.draw(6);
|
|
157
|
-
passEncoder.end();
|
|
158
|
-
device.queue.submit([encoder.finish()]);
|
|
159
124
|
}
|
|
160
|
-
|
|
125
|
+
lastWidth = frameWidth;
|
|
126
|
+
lastHeight = frameHeight;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
render: (encoder, frame, pass) => {
|
|
131
|
+
assert(!isContextLost(), RENDERING_EVENTS.ContextLost);
|
|
132
|
+
// Ensure textures and bind groups are up-to-date
|
|
133
|
+
ensureBlurTextures(pass, width, height);
|
|
134
|
+
const firstDst = blurTextures[1];
|
|
135
|
+
assert(firstDst);
|
|
136
|
+
// Cache the first downsample bind group (frame → blurTextures[1])
|
|
137
|
+
if (frame !== cacheFrameTexture) {
|
|
138
|
+
cacheFirstDownsampleBindGroup = device.createBindGroup({
|
|
139
|
+
layout: downsamplePipeline.getBindGroupLayout(0),
|
|
140
|
+
entries: [
|
|
141
|
+
{ binding: 0, resource: sampler },
|
|
142
|
+
{ binding: 1, resource: frame.createView() },
|
|
143
|
+
],
|
|
144
|
+
});
|
|
145
|
+
cacheFirstDownsampleRenderPassDescriptor = {
|
|
146
|
+
label: `downsample render pass 0`,
|
|
147
|
+
colorAttachments: [
|
|
148
|
+
{ loadOp: 'load', storeOp: 'store', view: firstDst.view },
|
|
149
|
+
],
|
|
150
|
+
};
|
|
151
|
+
cacheFrameTexture = frame;
|
|
152
|
+
}
|
|
153
|
+
assert(cacheFirstDownsampleRenderPassDescriptor);
|
|
154
|
+
// First downsampling: frame → blurTextures[1]
|
|
155
|
+
const firstComputePass = encoder.beginRenderPass(cacheFirstDownsampleRenderPassDescriptor);
|
|
156
|
+
firstComputePass.setPipeline(downsamplePipeline);
|
|
157
|
+
firstComputePass.setBindGroup(0, cacheFirstDownsampleBindGroup);
|
|
158
|
+
firstComputePass.draw(3);
|
|
159
|
+
firstComputePass.end();
|
|
160
|
+
// Subsequent downsampling passes: blurTextures[i-1] → blurTextures[i]
|
|
161
|
+
for (let i = 2; i < blurTextures.length; i++) {
|
|
162
|
+
const dst = blurTextures[i];
|
|
163
|
+
assert(dst);
|
|
164
|
+
// downsampleBindGroups[i-1] covers blurTextures[i-1] → blurTextures[i]
|
|
165
|
+
const bindGroup = downsampleBindGroups[i - 2];
|
|
166
|
+
const renderPassDescriptor = downsampleRenderPassDescriptor[i - 2];
|
|
167
|
+
assert(bindGroup);
|
|
168
|
+
assert(renderPassDescriptor);
|
|
169
|
+
const pass = encoder.beginRenderPass(renderPassDescriptor);
|
|
170
|
+
pass.setPipeline(downsamplePipeline);
|
|
171
|
+
pass.setBindGroup(0, bindGroup);
|
|
172
|
+
pass.draw(3);
|
|
173
|
+
pass.end();
|
|
174
|
+
}
|
|
175
|
+
// Upsampling passes (deepest level first, up to blurTextures[0])
|
|
176
|
+
for (let i = 0; i < upsampleBindGroups.length; i++) {
|
|
177
|
+
const dstIdx = blurTextures.length - 2 - i;
|
|
178
|
+
const dst = blurTextures[dstIdx];
|
|
179
|
+
assert(dst);
|
|
180
|
+
const bindGroup = upsampleBindGroups[i];
|
|
181
|
+
const renderPassDescriptor = upsampleRenderPassDescriptor[i];
|
|
182
|
+
assert(bindGroup);
|
|
183
|
+
assert(renderPassDescriptor);
|
|
184
|
+
const pass = encoder.beginRenderPass(renderPassDescriptor);
|
|
185
|
+
pass.setPipeline(upsamplePipeline);
|
|
186
|
+
pass.setBindGroup(0, bindGroup);
|
|
187
|
+
pass.draw(3);
|
|
188
|
+
pass.end();
|
|
189
|
+
}
|
|
161
190
|
const finalTexture = blurTextures[0];
|
|
162
191
|
assert(finalTexture);
|
|
163
|
-
const finalBindGroup = device.createBindGroup({
|
|
164
|
-
layout: pipeline.getBindGroupLayout(0),
|
|
165
|
-
entries: [
|
|
166
|
-
{ binding: 0, resource: sampler },
|
|
167
|
-
{
|
|
168
|
-
binding: 1,
|
|
169
|
-
resource: finalTexture.texture.createView(),
|
|
170
|
-
},
|
|
171
|
-
{ binding: 2, resource: { buffer: flipUpBuffer } },
|
|
172
|
-
],
|
|
173
|
-
});
|
|
174
|
-
const encoder = device.createCommandEncoder();
|
|
175
|
-
const passEncoder = encoder.beginRenderPass({
|
|
176
|
-
colorAttachments: [
|
|
177
|
-
{
|
|
178
|
-
view: context.getCurrentTexture().createView(),
|
|
179
|
-
loadOp: 'clear',
|
|
180
|
-
storeOp: 'store',
|
|
181
|
-
clearValue: RENDER_PASS_COLOR_ATTACHMENT_CLEAR_VALUE,
|
|
182
|
-
},
|
|
183
|
-
],
|
|
184
|
-
});
|
|
185
|
-
passEncoder.setPipeline(pipeline);
|
|
186
|
-
passEncoder.setBindGroup(0, finalBindGroup);
|
|
187
|
-
passEncoder.draw(6);
|
|
188
|
-
passEncoder.end();
|
|
189
|
-
device.queue.submit([encoder.finish()]);
|
|
190
192
|
return finalTexture.texture;
|
|
191
193
|
},
|
|
192
194
|
release: () => {
|
|
@@ -194,8 +196,12 @@ export const createBlurRenderer = (processor, device, presentationFormat, isCont
|
|
|
194
196
|
t.texture.destroy();
|
|
195
197
|
}
|
|
196
198
|
blurTextures = [];
|
|
197
|
-
|
|
198
|
-
|
|
199
|
+
downsampleRenderPassDescriptor = [];
|
|
200
|
+
upsampleRenderPassDescriptor = [];
|
|
201
|
+
downsampleBindGroups = [];
|
|
202
|
+
upsampleBindGroups = [];
|
|
203
|
+
cacheFrameTexture = null;
|
|
204
|
+
cacheFirstDownsampleBindGroup = null;
|
|
199
205
|
},
|
|
200
206
|
};
|
|
201
207
|
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { RendererOptions } from '../../types/render';
|
|
2
|
+
type JointBilateralFilterParams = Pick<RendererOptions, 'sigmaSpace' | 'sigmaRangeLo' | 'sigmaRangeHi' | 'backgroundThreshold' | 'foregroundThreshold'>;
|
|
3
|
+
export declare const createJointBilateralFilterRenderer: (processor: OffscreenCanvas, device: GPUDevice, isContextLost: () => boolean, width: number, height: number, maskFormat: GPUTextureFormat) => {
|
|
4
|
+
render: (encoder: GPUCommandEncoder, maskTexture: GPUTexture, guideTexture: GPUTexture, params: JointBilateralFilterParams) => GPUTexture;
|
|
5
|
+
release: () => void;
|
|
6
|
+
};
|
|
7
|
+
export {};
|