@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
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { makeShaderDataDefinitions, makeStructuredView } from 'webgpu-utils';
|
|
2
|
+
import { assert } from '@pexip/utils';
|
|
3
|
+
import { RENDERING_EVENTS } from '../../constants';
|
|
4
|
+
import { updateTextureStorageFormat, syncWorkgroupSizeWithShader, } from './webgpuUtils';
|
|
5
|
+
import resizeShader from './shaders/resize.wgsl';
|
|
6
|
+
export const createTextureResizeRenderer = (device, isContextLost, width, height, textureFormat, filterMode) => {
|
|
7
|
+
let shaderCode = updateTextureStorageFormat(resizeShader, textureFormat);
|
|
8
|
+
const WORKGROUP_SIZE_X = 16;
|
|
9
|
+
const WORKGROUP_SIZE_Y = 16;
|
|
10
|
+
const WORKGROUP_COUNT_X = Math.ceil(width / WORKGROUP_SIZE_X);
|
|
11
|
+
const WORKGROUP_COUNT_Y = Math.ceil(height / WORKGROUP_SIZE_Y);
|
|
12
|
+
shaderCode = syncWorkgroupSizeWithShader(shaderCode, {
|
|
13
|
+
workgroupSizeX: WORKGROUP_SIZE_X,
|
|
14
|
+
workgroupSizeY: WORKGROUP_SIZE_Y,
|
|
15
|
+
});
|
|
16
|
+
const defs = makeShaderDataDefinitions(shaderCode);
|
|
17
|
+
// @ts-expect-error This is what we get from the API
|
|
18
|
+
const uniforms = makeStructuredView(defs.uniforms.params);
|
|
19
|
+
const uniformsBuffer = device.createBuffer({
|
|
20
|
+
size: uniforms.arrayBuffer.byteLength,
|
|
21
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
22
|
+
});
|
|
23
|
+
uniforms.set({ outWidth: width, outHeight: height });
|
|
24
|
+
device.queue.writeBuffer(uniformsBuffer, 0, uniforms.arrayBuffer);
|
|
25
|
+
const shaderModule = device.createShaderModule({
|
|
26
|
+
label: `Resize shader module to ${width}x${height} with ${textureFormat} & ${filterMode}`,
|
|
27
|
+
code: shaderCode,
|
|
28
|
+
});
|
|
29
|
+
const pipeline = device.createComputePipeline({
|
|
30
|
+
layout: 'auto',
|
|
31
|
+
label: `Resize shader pipeline to ${width}x${height} with ${textureFormat} & ${filterMode}`,
|
|
32
|
+
compute: { module: shaderModule },
|
|
33
|
+
});
|
|
34
|
+
const outTexture = device.createTexture({
|
|
35
|
+
label: `Resize out texture to ${width}x${height} with ${textureFormat} & ${filterMode}`,
|
|
36
|
+
format: textureFormat,
|
|
37
|
+
size: [width, height],
|
|
38
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING,
|
|
39
|
+
});
|
|
40
|
+
const outTextureView = outTexture.createView();
|
|
41
|
+
const sampler = device.createSampler({
|
|
42
|
+
magFilter: filterMode,
|
|
43
|
+
minFilter: filterMode,
|
|
44
|
+
addressModeU: 'clamp-to-edge',
|
|
45
|
+
addressModeV: 'clamp-to-edge',
|
|
46
|
+
});
|
|
47
|
+
let cachedBindGroup0 = null;
|
|
48
|
+
let cachedInputTexture = null;
|
|
49
|
+
return {
|
|
50
|
+
render(pass, texture) {
|
|
51
|
+
assert(!isContextLost(), RENDERING_EVENTS.ContextLost);
|
|
52
|
+
if (texture !== cachedInputTexture) {
|
|
53
|
+
cachedBindGroup0 = device.createBindGroup({
|
|
54
|
+
layout: pipeline.getBindGroupLayout(0),
|
|
55
|
+
entries: [
|
|
56
|
+
{ binding: 0, resource: sampler },
|
|
57
|
+
{ binding: 1, resource: texture.createView() },
|
|
58
|
+
{ binding: 2, resource: outTextureView },
|
|
59
|
+
{ binding: 3, resource: { buffer: uniformsBuffer } },
|
|
60
|
+
],
|
|
61
|
+
});
|
|
62
|
+
cachedInputTexture = texture;
|
|
63
|
+
}
|
|
64
|
+
pass.setPipeline(pipeline);
|
|
65
|
+
pass.setBindGroup(0, cachedBindGroup0);
|
|
66
|
+
pass.dispatchWorkgroups(WORKGROUP_COUNT_X, WORKGROUP_COUNT_Y);
|
|
67
|
+
return outTexture;
|
|
68
|
+
},
|
|
69
|
+
release() {
|
|
70
|
+
uniformsBuffer.destroy();
|
|
71
|
+
outTexture.destroy();
|
|
72
|
+
cachedBindGroup0 = null;
|
|
73
|
+
cachedInputTexture = null;
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RendererOptions } from '../../types/render';
|
|
2
|
-
export declare const createSmoothingMaskRenderer: (
|
|
3
|
-
render: (currMask: GPUTexture, prevMask: GPUTexture
|
|
2
|
+
export declare const createSmoothingMaskRenderer: (device: GPUDevice, isContextLost: () => boolean, maskFormat: GPUTextureFormat, width: number, height: number, supportF16: boolean) => {
|
|
3
|
+
render: (encoder: GPUCommandEncoder, currMask: GPUTexture, prevMask: GPUTexture, options: RendererOptions) => GPUTexture;
|
|
4
4
|
release: () => void;
|
|
5
5
|
};
|
|
@@ -1,66 +1,100 @@
|
|
|
1
|
+
import { makeShaderDataDefinitions, makeStructuredView } from 'webgpu-utils';
|
|
1
2
|
import { assert } from '@pexip/utils';
|
|
2
3
|
import { RENDERING_EVENTS } from '../../constants';
|
|
3
4
|
import smoothingMaskShader from './shaders/smoothingMaskShader.wgsl';
|
|
4
|
-
|
|
5
|
+
import { syncWorkgroupSizeWithShader, updateTextureStorageFormat, enableF16InShader, } from './webgpuUtils';
|
|
6
|
+
export const createSmoothingMaskRenderer = (device, isContextLost, maskFormat, width, height, supportF16) => {
|
|
7
|
+
const WORKGROUP_SIZE_X = 16;
|
|
8
|
+
const WORKGROUP_SIZE_Y = 16;
|
|
9
|
+
const WORKGROUP_COUNT_X = Math.ceil(width / WORKGROUP_SIZE_X);
|
|
10
|
+
const WORKGROUP_COUNT_Y = Math.ceil(height / WORKGROUP_SIZE_Y);
|
|
11
|
+
// Ensure the setup code and shader code are using the same constants
|
|
12
|
+
let shaderCode = syncWorkgroupSizeWithShader(smoothingMaskShader, {
|
|
13
|
+
workgroupSizeX: WORKGROUP_SIZE_X,
|
|
14
|
+
workgroupSizeY: WORKGROUP_SIZE_Y,
|
|
15
|
+
});
|
|
16
|
+
shaderCode = updateTextureStorageFormat(shaderCode, maskFormat);
|
|
17
|
+
if (supportF16) {
|
|
18
|
+
shaderCode = enableF16InShader(shaderCode);
|
|
19
|
+
}
|
|
5
20
|
const smoothedMaskTexture = device.createTexture({
|
|
6
21
|
label: 'Smoothed Mask Texture',
|
|
7
|
-
format:
|
|
8
|
-
size: [
|
|
22
|
+
format: maskFormat,
|
|
23
|
+
size: [width, height],
|
|
9
24
|
usage: GPUTextureUsage.TEXTURE_BINDING |
|
|
10
25
|
GPUTextureUsage.STORAGE_BINDING |
|
|
11
|
-
GPUTextureUsage.COPY_DST |
|
|
12
26
|
GPUTextureUsage.COPY_SRC,
|
|
13
27
|
});
|
|
14
|
-
const
|
|
15
|
-
|
|
28
|
+
const defs = makeShaderDataDefinitions(shaderCode);
|
|
29
|
+
// @ts-expect-error This is what we get from the API
|
|
30
|
+
const uniforms = makeStructuredView(defs.uniforms.params);
|
|
31
|
+
const uniformsBuffer = device.createBuffer({
|
|
32
|
+
size: uniforms.arrayBuffer.byteLength,
|
|
16
33
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
17
34
|
});
|
|
18
35
|
const updateParams = (opts) => {
|
|
19
|
-
|
|
36
|
+
uniforms.set({
|
|
37
|
+
maskCombineRatio: opts.maskCombineRatio,
|
|
38
|
+
});
|
|
39
|
+
device.queue.writeBuffer(uniformsBuffer, 0, uniforms.arrayBuffer);
|
|
20
40
|
};
|
|
41
|
+
// Cache pipeline and shader module at init time
|
|
42
|
+
const shaderModule = device.createShaderModule({
|
|
43
|
+
label: 'smoothing mask compute',
|
|
44
|
+
code: shaderCode,
|
|
45
|
+
});
|
|
46
|
+
const computePipeline = device.createComputePipeline({
|
|
47
|
+
label: 'smoothing mask pipeline',
|
|
48
|
+
layout: 'auto',
|
|
49
|
+
compute: { module: shaderModule },
|
|
50
|
+
});
|
|
51
|
+
const computeBindGroup0 = device.createBindGroup({
|
|
52
|
+
layout: computePipeline.getBindGroupLayout(0),
|
|
53
|
+
entries: [
|
|
54
|
+
{ binding: 0, resource: smoothedMaskTexture.createView() },
|
|
55
|
+
{ binding: 1, resource: { buffer: uniformsBuffer } },
|
|
56
|
+
],
|
|
57
|
+
});
|
|
58
|
+
let cacheMaskTexture = null;
|
|
59
|
+
let cachePrevMaskTexture = null;
|
|
60
|
+
let cacheBindGroup1 = null;
|
|
61
|
+
let cacheMaskCombineRatio = -1;
|
|
21
62
|
return {
|
|
22
|
-
render: (currMask, prevMask, options) => {
|
|
63
|
+
render: (encoder, currMask, prevMask, options) => {
|
|
23
64
|
assert(!isContextLost(), RENDERING_EVENTS.ContextLost);
|
|
24
|
-
if (!
|
|
25
|
-
|
|
65
|
+
if (!(currMask === cacheMaskTexture &&
|
|
66
|
+
prevMask === cachePrevMaskTexture)) {
|
|
67
|
+
cacheBindGroup1 = device.createBindGroup({
|
|
68
|
+
layout: computePipeline.getBindGroupLayout(1),
|
|
69
|
+
entries: [
|
|
70
|
+
{ binding: 0, resource: currMask.createView() },
|
|
71
|
+
{ binding: 1, resource: prevMask.createView() },
|
|
72
|
+
],
|
|
73
|
+
});
|
|
74
|
+
cacheMaskTexture = currMask;
|
|
75
|
+
cachePrevMaskTexture = prevMask;
|
|
26
76
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
code: smoothingMaskShader,
|
|
34
|
-
}),
|
|
35
|
-
},
|
|
36
|
-
});
|
|
37
|
-
updateParams(options);
|
|
38
|
-
const computeBindGroup = device.createBindGroup({
|
|
39
|
-
layout: computePipeline.getBindGroupLayout(0),
|
|
40
|
-
entries: [
|
|
41
|
-
{ binding: 1, resource: currMask.createView() },
|
|
42
|
-
{ binding: 2, resource: prevMask.createView() },
|
|
43
|
-
{ binding: 3, resource: smoothedMaskTexture.createView() },
|
|
44
|
-
{ binding: 4, resource: { buffer: paramsBuffer } },
|
|
45
|
-
],
|
|
46
|
-
});
|
|
47
|
-
const computeEncoder = device.createCommandEncoder({
|
|
48
|
-
label: 'smoothing mask encoder',
|
|
49
|
-
});
|
|
50
|
-
const computePass = computeEncoder.beginComputePass({
|
|
51
|
-
label: ' smoothing mask compute pass',
|
|
77
|
+
if (cacheMaskCombineRatio !== options.maskCombineRatio) {
|
|
78
|
+
updateParams(options);
|
|
79
|
+
cacheMaskCombineRatio = options.maskCombineRatio;
|
|
80
|
+
}
|
|
81
|
+
const computePass = encoder.beginComputePass({
|
|
82
|
+
label: 'smoothing mask compute pass',
|
|
52
83
|
});
|
|
53
84
|
computePass.setPipeline(computePipeline);
|
|
54
|
-
computePass.setBindGroup(0,
|
|
55
|
-
computePass.
|
|
85
|
+
computePass.setBindGroup(0, computeBindGroup0);
|
|
86
|
+
computePass.setBindGroup(1, cacheBindGroup1);
|
|
87
|
+
computePass.dispatchWorkgroups(WORKGROUP_COUNT_X, WORKGROUP_COUNT_Y);
|
|
56
88
|
computePass.end();
|
|
57
|
-
const computeCommandBuffer = computeEncoder.finish();
|
|
58
|
-
device.queue.submit([computeCommandBuffer]);
|
|
59
89
|
return smoothedMaskTexture;
|
|
60
90
|
},
|
|
61
91
|
release: () => {
|
|
62
92
|
smoothedMaskTexture.destroy();
|
|
63
|
-
|
|
93
|
+
uniformsBuffer.destroy();
|
|
94
|
+
cacheMaskTexture = null;
|
|
95
|
+
cachePrevMaskTexture = null;
|
|
96
|
+
cacheBindGroup1 = null;
|
|
97
|
+
cacheMaskCombineRatio = -1;
|
|
64
98
|
},
|
|
65
99
|
};
|
|
66
100
|
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { RendererOptions } from '../../types/render';
|
|
2
|
+
export declare const createTentBlurRenderer: (device: GPUDevice, isContextLost: () => boolean, width: number, height: number, textureFormat: GPUTextureFormat, supportF16: boolean) => {
|
|
3
|
+
render(encoder: GPUCommandEncoder, texture: GPUTexture, options: RendererOptions): GPUTexture;
|
|
4
|
+
release(): void;
|
|
5
|
+
};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { makeShaderDataDefinitions, makeStructuredView } from 'webgpu-utils';
|
|
2
|
+
import { assert } from '@pexip/utils';
|
|
3
|
+
import { RENDERING_EVENTS } from '../../constants';
|
|
4
|
+
import tentBlurHShader from './shaders/tentBlurH.wgsl';
|
|
5
|
+
import tentBlurVShader from './shaders/tentBlurV.wgsl';
|
|
6
|
+
import { clamping } from '../../utils';
|
|
7
|
+
import { updateTextureStorageFormat, syncWorkgroupSizeWithShader, enableF16InShader, } from './webgpuUtils';
|
|
8
|
+
const clampInRangeFrom0To9 = clamping(0, 9);
|
|
9
|
+
export const createTentBlurRenderer = (device, isContextLost, width, height, textureFormat, supportF16) => {
|
|
10
|
+
const WORKGROUP_SIZE_X = 16;
|
|
11
|
+
const WORKGROUP_SIZE_Y = 16;
|
|
12
|
+
const WORKGROUP_COUNT_X = Math.ceil(width / WORKGROUP_SIZE_X);
|
|
13
|
+
const WORKGROUP_COUNT_Y = Math.ceil(height / WORKGROUP_SIZE_Y);
|
|
14
|
+
// Patch shader code with the correct texture storage format and workgroup sizes
|
|
15
|
+
let shaderHCode = updateTextureStorageFormat(tentBlurHShader, textureFormat);
|
|
16
|
+
shaderHCode = syncWorkgroupSizeWithShader(shaderHCode, {
|
|
17
|
+
workgroupSizeX: WORKGROUP_SIZE_X,
|
|
18
|
+
workgroupSizeY: WORKGROUP_SIZE_Y,
|
|
19
|
+
});
|
|
20
|
+
shaderHCode = enableF16InShader(shaderHCode);
|
|
21
|
+
let shaderVCode = updateTextureStorageFormat(tentBlurVShader, textureFormat);
|
|
22
|
+
shaderVCode = syncWorkgroupSizeWithShader(shaderVCode, {
|
|
23
|
+
workgroupSizeX: WORKGROUP_SIZE_X,
|
|
24
|
+
workgroupSizeY: WORKGROUP_SIZE_Y,
|
|
25
|
+
});
|
|
26
|
+
if (supportF16) {
|
|
27
|
+
shaderVCode = enableF16InShader(shaderVCode);
|
|
28
|
+
}
|
|
29
|
+
const defs = makeShaderDataDefinitions(shaderHCode);
|
|
30
|
+
// @ts-expect-error This is what we get from the API
|
|
31
|
+
const uniforms = makeStructuredView(defs.uniforms.params);
|
|
32
|
+
// Create params buffer (shared for both passes — same struct layout)
|
|
33
|
+
const paramsBuffer = device.createBuffer({
|
|
34
|
+
size: uniforms.arrayBuffer.byteLength,
|
|
35
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
36
|
+
});
|
|
37
|
+
uniforms.set({ width, height });
|
|
38
|
+
const updateParams = (radius) => {
|
|
39
|
+
uniforms.set({ radius });
|
|
40
|
+
device.queue.writeBuffer(paramsBuffer, 0, uniforms.arrayBuffer);
|
|
41
|
+
};
|
|
42
|
+
// Intermediate texture (H pass output → V pass input)
|
|
43
|
+
const tempTexture = device.createTexture({
|
|
44
|
+
label: `Tent Blur intermediate texture ${width}x${height} ${textureFormat}`,
|
|
45
|
+
format: textureFormat,
|
|
46
|
+
size: [width, height],
|
|
47
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING,
|
|
48
|
+
});
|
|
49
|
+
const tempTextureView = tempTexture.createView();
|
|
50
|
+
// Output texture (V pass output)
|
|
51
|
+
const outTexture = device.createTexture({
|
|
52
|
+
label: `Tent Blur output texture ${width}x${height} ${textureFormat}`,
|
|
53
|
+
format: textureFormat,
|
|
54
|
+
size: [width, height],
|
|
55
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING,
|
|
56
|
+
});
|
|
57
|
+
// Shader modules
|
|
58
|
+
const shaderHModule = device.createShaderModule({
|
|
59
|
+
label: `Tent Blur horizontal compute ${width}x${height} ${textureFormat}`,
|
|
60
|
+
code: shaderHCode,
|
|
61
|
+
});
|
|
62
|
+
const shaderVModule = device.createShaderModule({
|
|
63
|
+
label: `Tent Blur vertical compute ${width}x${height} ${textureFormat}`,
|
|
64
|
+
code: shaderVCode,
|
|
65
|
+
});
|
|
66
|
+
// Compute pipelines
|
|
67
|
+
const computeHPipeline = device.createComputePipeline({
|
|
68
|
+
label: `Tent Blur horizontal pipeline ${width}x${height} ${textureFormat}`,
|
|
69
|
+
layout: 'auto',
|
|
70
|
+
compute: { module: shaderHModule },
|
|
71
|
+
});
|
|
72
|
+
const computeVPipeline = device.createComputePipeline({
|
|
73
|
+
label: `Tent Blur vertical pipeline ${width}x${height} ${textureFormat}`,
|
|
74
|
+
layout: 'auto',
|
|
75
|
+
compute: { module: shaderVModule },
|
|
76
|
+
});
|
|
77
|
+
// V pass bind group (static: tempTexture → outTexture)
|
|
78
|
+
const bindGroupV = device.createBindGroup({
|
|
79
|
+
layout: computeVPipeline.getBindGroupLayout(0),
|
|
80
|
+
entries: [
|
|
81
|
+
{ binding: 0, resource: tempTextureView },
|
|
82
|
+
{ binding: 1, resource: outTexture.createView() },
|
|
83
|
+
{ binding: 2, resource: { buffer: paramsBuffer } },
|
|
84
|
+
],
|
|
85
|
+
});
|
|
86
|
+
let cacheTexture = null;
|
|
87
|
+
let cacheBindGroupH = null;
|
|
88
|
+
let cacheRadius = -1;
|
|
89
|
+
return {
|
|
90
|
+
render(encoder, texture, options) {
|
|
91
|
+
assert(!isContextLost(), RENDERING_EVENTS.ContextLost);
|
|
92
|
+
const radius = clampInRangeFrom0To9(options.edgeBlurAmount);
|
|
93
|
+
if (cacheRadius !== radius) {
|
|
94
|
+
updateParams(radius);
|
|
95
|
+
cacheRadius = radius;
|
|
96
|
+
}
|
|
97
|
+
if (cacheTexture !== texture) {
|
|
98
|
+
cacheBindGroupH = device.createBindGroup({
|
|
99
|
+
layout: computeHPipeline.getBindGroupLayout(0),
|
|
100
|
+
entries: [
|
|
101
|
+
{ binding: 0, resource: texture.createView() },
|
|
102
|
+
{ binding: 1, resource: tempTextureView },
|
|
103
|
+
{ binding: 2, resource: { buffer: paramsBuffer } },
|
|
104
|
+
],
|
|
105
|
+
});
|
|
106
|
+
cacheTexture = texture;
|
|
107
|
+
}
|
|
108
|
+
// Horizontal
|
|
109
|
+
const passH = encoder.beginComputePass({ label: 'Tent Blur H pass' });
|
|
110
|
+
passH.setPipeline(computeHPipeline);
|
|
111
|
+
passH.setBindGroup(0, cacheBindGroupH);
|
|
112
|
+
passH.dispatchWorkgroups(WORKGROUP_COUNT_X, WORKGROUP_COUNT_Y);
|
|
113
|
+
passH.end();
|
|
114
|
+
// Vertical
|
|
115
|
+
const passV = encoder.beginComputePass({ label: 'Tent Blur V pass' });
|
|
116
|
+
passV.setPipeline(computeVPipeline);
|
|
117
|
+
passV.setBindGroup(0, bindGroupV);
|
|
118
|
+
passV.dispatchWorkgroups(WORKGROUP_COUNT_X, WORKGROUP_COUNT_Y);
|
|
119
|
+
passV.end();
|
|
120
|
+
return outTexture;
|
|
121
|
+
},
|
|
122
|
+
release() {
|
|
123
|
+
tempTexture.destroy();
|
|
124
|
+
outTexture.destroy();
|
|
125
|
+
paramsBuffer.destroy();
|
|
126
|
+
cacheTexture = null;
|
|
127
|
+
cacheBindGroupH = null;
|
|
128
|
+
cacheRadius = -1;
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { VideoFrameLike } from '../../types/media';
|
|
2
|
-
export declare const createTextureRenderer: (
|
|
3
|
-
render: (frame: VideoFrameLike) => GPUTexture;
|
|
2
|
+
export declare const createTextureRenderer: (device: GPUDevice, isContextLost: () => boolean, textureFormat: GPUTextureFormat, width: number, height: number, filterMode: GPUFilterMode) => {
|
|
3
|
+
render: (frame: VideoFrameLike, pass: GPUComputePassEncoder) => GPUTexture;
|
|
4
4
|
release: () => void;
|
|
5
5
|
};
|
|
@@ -1,35 +1,40 @@
|
|
|
1
1
|
import { assert } from '@pexip/utils';
|
|
2
2
|
import { RENDERING_EVENTS } from '../../constants';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
format: presentationFormat,
|
|
13
|
-
});
|
|
14
|
-
let texture = undefined;
|
|
15
|
-
return {
|
|
16
|
-
render: (frame) => {
|
|
17
|
-
assert(!isContextLost(), RENDERING_EVENTS.ContextLost);
|
|
3
|
+
import { createTextureResizeRenderer } from './resize';
|
|
4
|
+
export const createTextureRenderer = (device, isContextLost, textureFormat, width, height, filterMode) => {
|
|
5
|
+
// Cache to store textures by size
|
|
6
|
+
const textureCache = new Map();
|
|
7
|
+
const resizeRenderer = createTextureResizeRenderer(device, isContextLost, width, height, textureFormat, filterMode);
|
|
8
|
+
const getOrCreateTexture = (width, height) => {
|
|
9
|
+
const key = `${width}x${height}`;
|
|
10
|
+
let texture = textureCache.get(key);
|
|
11
|
+
if (!texture) {
|
|
18
12
|
texture = device.createTexture({
|
|
19
13
|
label: 'Video Frame to Texture',
|
|
20
14
|
format: 'rgba8unorm',
|
|
21
|
-
size: [
|
|
15
|
+
size: [width, height],
|
|
22
16
|
usage: GPUTextureUsage.TEXTURE_BINDING |
|
|
23
17
|
GPUTextureUsage.COPY_DST |
|
|
24
18
|
GPUTextureUsage.COPY_SRC |
|
|
25
19
|
GPUTextureUsage.RENDER_ATTACHMENT,
|
|
26
20
|
});
|
|
21
|
+
textureCache.set(key, texture);
|
|
22
|
+
}
|
|
23
|
+
return texture;
|
|
24
|
+
};
|
|
25
|
+
return {
|
|
26
|
+
render: (frame, pass) => {
|
|
27
|
+
assert(!isContextLost(), RENDERING_EVENTS.ContextLost);
|
|
28
|
+
const texture = getOrCreateTexture(frame.width, frame.height);
|
|
27
29
|
device.queue.copyExternalImageToTexture({ source: frame.frame, flipY: false }, { texture }, { width: frame.width, height: frame.height });
|
|
28
|
-
|
|
29
|
-
return texture;
|
|
30
|
+
return resizeRenderer.render(pass, texture);
|
|
30
31
|
},
|
|
31
32
|
release: () => {
|
|
32
|
-
texture
|
|
33
|
+
for (const texture of textureCache.values()) {
|
|
34
|
+
texture.destroy();
|
|
35
|
+
}
|
|
36
|
+
textureCache.clear();
|
|
37
|
+
resizeRenderer.release();
|
|
33
38
|
},
|
|
34
39
|
};
|
|
35
40
|
};
|
|
@@ -1,35 +1,48 @@
|
|
|
1
1
|
import { assert } from '@pexip/utils';
|
|
2
2
|
import { RENDERING_EVENTS } from '../../constants';
|
|
3
3
|
export const createTextureToTextureRenderer = (device, isContextLost) => {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
dstTexture = device.createTexture({
|
|
4
|
+
// Cache to store destination textures by size and format
|
|
5
|
+
const textureCache = new Map();
|
|
6
|
+
const getOrCreateTexture = (width, height, depthOrArrayLayers, format) => {
|
|
7
|
+
const key = `${width}x${height}x${depthOrArrayLayers}-${format}`;
|
|
8
|
+
let texture = textureCache.get(key);
|
|
9
|
+
if (!texture) {
|
|
10
|
+
texture = device.createTexture({
|
|
12
11
|
size: {
|
|
13
|
-
width
|
|
14
|
-
height
|
|
15
|
-
depthOrArrayLayers
|
|
12
|
+
width,
|
|
13
|
+
height,
|
|
14
|
+
depthOrArrayLayers,
|
|
16
15
|
},
|
|
17
|
-
format
|
|
16
|
+
format,
|
|
18
17
|
usage: GPUTextureUsage.RENDER_ATTACHMENT |
|
|
19
18
|
GPUTextureUsage.TEXTURE_BINDING |
|
|
20
19
|
GPUTextureUsage.COPY_DST,
|
|
21
20
|
});
|
|
22
|
-
|
|
21
|
+
textureCache.set(key, texture);
|
|
22
|
+
}
|
|
23
|
+
return texture;
|
|
24
|
+
};
|
|
25
|
+
return {
|
|
26
|
+
render: (encoder, srcTexture) => {
|
|
27
|
+
assert(!isContextLost(), RENDERING_EVENTS.ContextLost);
|
|
28
|
+
// Create destination texture with same format as source
|
|
29
|
+
// This preserves format (e.g., r16float, rgba8unorm) across the copy
|
|
30
|
+
// The usage flags (RENDER_ATTACHMENT | TEXTURE_BINDING | COPY_DST)
|
|
31
|
+
// are compatible with all formats used in this pipeline
|
|
32
|
+
const dstTexture = getOrCreateTexture(srcTexture.width, srcTexture.height, srcTexture.depthOrArrayLayers, srcTexture.format);
|
|
33
|
+
//copy src texture to destination texture
|
|
23
34
|
encoder.copyTextureToTexture({ texture: srcTexture }, { texture: dstTexture }, {
|
|
24
35
|
width: dstTexture.width,
|
|
25
36
|
height: dstTexture.height,
|
|
26
37
|
depthOrArrayLayers: dstTexture.depthOrArrayLayers,
|
|
27
38
|
});
|
|
28
|
-
device.queue.submit([encoder.finish()]);
|
|
29
39
|
return dstTexture;
|
|
30
40
|
},
|
|
31
41
|
release: () => {
|
|
32
|
-
|
|
42
|
+
for (const texture of textureCache.values()) {
|
|
43
|
+
texture.destroy();
|
|
44
|
+
}
|
|
45
|
+
textureCache.clear();
|
|
33
46
|
},
|
|
34
47
|
};
|
|
35
48
|
};
|
|
@@ -1,7 +1,27 @@
|
|
|
1
|
+
export declare const hasR16FloatTextureStorage: (adapter: GPUAdapter) => boolean;
|
|
2
|
+
export declare const hasF16: (adapter: GPUAdapter) => boolean;
|
|
3
|
+
export declare const getGPUDevice: (adapter: GPUAdapter) => Promise<GPUDevice>;
|
|
1
4
|
/**
|
|
2
|
-
* Check if
|
|
5
|
+
* Check if provided format is supported
|
|
3
6
|
*
|
|
4
|
-
*
|
|
7
|
+
* @param device - GPUDevice to check the format support on
|
|
8
|
+
* @param format - GPUTextureFormat to check
|
|
9
|
+
* @param usage - Optional GPUTextureUsage flags to check for the format support
|
|
10
|
+
* @default STORAGE_BINDING | TEXTURE_BINDING | COPY_DST | RENDER_ATTACHMENT
|
|
5
11
|
*/
|
|
6
|
-
export declare
|
|
7
|
-
|
|
12
|
+
export declare function supportsTextureFormat(device: GPUDevice, format: GPUTextureFormat, usage?: number): boolean;
|
|
13
|
+
interface WorkgroupInfo {
|
|
14
|
+
workgroupSizeX: number;
|
|
15
|
+
workgroupSizeY: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Replace the WGSL codes with predefined patterns to sync the workgroup size
|
|
19
|
+
* and tile size between the shader code and the setup code.
|
|
20
|
+
*/
|
|
21
|
+
export declare const syncWorkgroupSizeWithShader: (shaderCode: string, { workgroupSizeX, workgroupSizeY }: WorkgroupInfo) => string;
|
|
22
|
+
/**
|
|
23
|
+
* Replace the WGSL texture_storage_2d format with another format
|
|
24
|
+
*/
|
|
25
|
+
export declare const updateTextureStorageFormat: (shaderCode: string, format: GPUTextureFormat) => string;
|
|
26
|
+
export declare const enableF16InShader: (shaderCode: string) => string;
|
|
27
|
+
export {};
|
|
@@ -1,14 +1,63 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* https://webgpufundamentals.org/webgpu/lessons/webgpu-storage-textures.html#canvas-as-a-storage-texture
|
|
5
|
-
*/
|
|
6
|
-
export const hasBGRA8unormStorage = (adapter) => adapter.features.has('bgra8unorm-storage');
|
|
1
|
+
export const hasR16FloatTextureStorage = (adapter) => adapter.features.has('texture-formats-tier1');
|
|
2
|
+
export const hasF16 = (adapter) => adapter.features.has('shader-f16');
|
|
7
3
|
export const getGPUDevice = async (adapter) => {
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
const requiredFeatures = [];
|
|
5
|
+
if (hasR16FloatTextureStorage(adapter)) {
|
|
6
|
+
// https://gpuweb.github.io/gpuweb/#plain-color-formats
|
|
7
|
+
// Use `r16float` for single channel texture, which is more efficient than rgba8unorm
|
|
8
|
+
// It requires texture-formats-tier1 feature for storage
|
|
9
|
+
requiredFeatures.push('texture-formats-tier1');
|
|
10
|
+
}
|
|
11
|
+
if (hasF16(adapter)) {
|
|
12
|
+
// Use f16 type to reduce bandwidth and storage for performance critical
|
|
13
|
+
// textures and computations, such as the smoothing mask
|
|
14
|
+
// https://developer.chrome.com/blog/new-in-webgpu-120
|
|
15
|
+
requiredFeatures.push('shader-f16');
|
|
16
|
+
}
|
|
17
|
+
const device = await adapter?.requestDevice({ requiredFeatures });
|
|
13
18
|
return device;
|
|
14
19
|
};
|
|
20
|
+
/**
|
|
21
|
+
* Check if provided format is supported
|
|
22
|
+
*
|
|
23
|
+
* @param device - GPUDevice to check the format support on
|
|
24
|
+
* @param format - GPUTextureFormat to check
|
|
25
|
+
* @param usage - Optional GPUTextureUsage flags to check for the format support
|
|
26
|
+
* @default STORAGE_BINDING | TEXTURE_BINDING | COPY_DST | RENDER_ATTACHMENT
|
|
27
|
+
*/
|
|
28
|
+
export function supportsTextureFormat(device, format, usage = GPUTextureUsage.STORAGE_BINDING |
|
|
29
|
+
GPUTextureUsage.TEXTURE_BINDING |
|
|
30
|
+
GPUTextureUsage.COPY_DST |
|
|
31
|
+
GPUTextureUsage.RENDER_ATTACHMENT) {
|
|
32
|
+
try {
|
|
33
|
+
// Try to create a tiny texture with the desired format and usage
|
|
34
|
+
const texture = device.createTexture({
|
|
35
|
+
size: [4, 4, 1],
|
|
36
|
+
format,
|
|
37
|
+
usage,
|
|
38
|
+
});
|
|
39
|
+
// Optionally, you could also try to create a compute/render pipeline
|
|
40
|
+
// using this format for storage textures.
|
|
41
|
+
texture.destroy();
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const WorkgroupSizeXPattern = /const WORKGROUP_SIZE_X(\s*)=(\s*)\d+u*;/;
|
|
49
|
+
const WorkgroupSizeYPattern = /const WORKGROUP_SIZE_Y(\s*)=(\s*)\d+u*;/;
|
|
50
|
+
/**
|
|
51
|
+
* Replace the WGSL codes with predefined patterns to sync the workgroup size
|
|
52
|
+
* and tile size between the shader code and the setup code.
|
|
53
|
+
*/
|
|
54
|
+
export const syncWorkgroupSizeWithShader = (shaderCode, { workgroupSizeX, workgroupSizeY }) => shaderCode
|
|
55
|
+
.replace(WorkgroupSizeXPattern, `const WORKGROUP_SIZE_X$1=$2${workgroupSizeX}u;`)
|
|
56
|
+
.replace(WorkgroupSizeYPattern, `const WORKGROUP_SIZE_Y$1=$2${workgroupSizeY}u;`);
|
|
57
|
+
const TextureStoragePattern = /texture_storage_2d<\w+,(\s*)write>/;
|
|
58
|
+
/**
|
|
59
|
+
* Replace the WGSL texture_storage_2d format with another format
|
|
60
|
+
*/
|
|
61
|
+
export const updateTextureStorageFormat = (shaderCode, format) => shaderCode.replace(TextureStoragePattern, `texture_storage_2d<${format},$1write>`);
|
|
62
|
+
const FloatPattern = /alias float(\s*)=(\s*)f32;/;
|
|
63
|
+
export const enableF16InShader = (shaderCode) => shaderCode.replace(FloatPattern, 'enable f16;alias float$1=$2f16;');
|
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
type ExtractValueTypes<T> = T extends object ? T[keyof T] : never;
|
|
2
|
-
export declare const PROCESSING_WIDTH = 768;
|
|
3
|
-
export declare const PROCESSING_HEIGHT = 432;
|
|
4
|
-
export declare const FOREGROUND_THRESHOLD = 0.5;
|
|
5
2
|
export declare const BACKGROUND_BLUR_AMOUNT = 3;
|
|
6
|
-
export declare const
|
|
7
|
-
export declare const
|
|
3
|
+
export declare const BACKGROUND_THRESHOLD = 0.35;
|
|
4
|
+
export declare const DOWN_SAMPLE_FACTOR = 2;
|
|
5
|
+
export declare const EDGE_BLUR_AMOUNT = 4;
|
|
6
|
+
export declare const EXCLUDE_BYSTANDERS = false;
|
|
7
|
+
export declare const FOREGROUND_THRESHOLD = 0.85;
|
|
8
|
+
export declare const LIGHT_WRAP_BLUR_AMOUNT = 1;
|
|
9
|
+
export declare const LIGHT_WRAP_EDGE_BAND = 0.25;
|
|
10
|
+
export declare const LIGHT_WRAP_INTENSITY = 0.8;
|
|
11
|
+
export declare const LIGHT_WRAP_TIGHTNESS = 1;
|
|
12
|
+
export declare const MASK_COMBINE_RATIO = 0.8;
|
|
13
|
+
export declare const MORPH_DILATE_RADIUS_PX = 6;
|
|
14
|
+
export declare const MORPH_ERODE_RADIUS_PX = 5;
|
|
15
|
+
export declare const MORPH_PASS = 4;
|
|
16
|
+
export declare const PERSON_CENTER: [number, number];
|
|
17
|
+
export declare const PROCESSING_HEIGHT = 432;
|
|
18
|
+
export declare const PROCESSING_WIDTH = 768;
|
|
19
|
+
export declare const SIGMA_RANGE: [number, number];
|
|
20
|
+
export declare const SIGMA_SPACE = 5;
|
|
21
|
+
export declare const STRONG_EDGE_BLUR_AMOUNT = 2;
|
|
8
22
|
/**
|
|
9
23
|
* Process status
|
|
10
24
|
*/
|