@pexip/media-processor 20.3.5 → 22.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 +109 -0
- package/README.md +17 -10
- package/api-docs/README.mdx +69 -23
- package/api-docs/functions/assertMediaStreamVideoTrack.mdx +13 -0
- package/api-docs/functions/createBenchmark.mdx +17 -5
- package/api-docs/functions/createFrameCallbackRequest.mdx +8 -2
- package/api-docs/functions/createMediaStreamTrackInputProcessAdaptor.mdx +10 -0
- package/api-docs/functions/createReadWritableStreamFallbackInputProcessAdaptor.mdx +17 -0
- package/api-docs/functions/createReadWritableStreamInputProcessAdaptor.mdx +10 -0
- package/api-docs/functions/createVideoProcessor.mdx +3 -3
- package/api-docs/functions/createWindowedStats.mdx +23 -0
- package/api-docs/functions/getDefaultInputProcessAdaptor.mdx +17 -0
- package/api-docs/functions/iou.mdx +19 -0
- package/api-docs/functions/isMediaStreamVideoTrack.mdx +13 -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/Analyzer.mdx +4 -0
- package/api-docs/interfaces/Benchmark.mdx +26 -2
- package/api-docs/interfaces/FrameTransformStats.mdx +9 -0
- package/api-docs/interfaces/Gain.mdx +4 -0
- package/api-docs/interfaces/ImageSegmenterOptions.mdx +0 -2
- package/api-docs/interfaces/InputProcessAdaptor.mdx +28 -0
- package/api-docs/interfaces/Process.mdx +8 -3
- package/api-docs/interfaces/ProcessEventMute.mdx +6 -0
- package/api-docs/interfaces/ProcessWorkerEventOpened.mdx +7 -0
- package/api-docs/interfaces/ProcessWorkerEventStats.mdx +6 -0
- package/api-docs/interfaces/ProcessWorkerEventUpdated.mdx +7 -0
- package/api-docs/interfaces/ProcessorEvent.mdx +1 -1
- package/api-docs/interfaces/ProcessorOptions.mdx +0 -1
- package/api-docs/interfaces/ProcessorProcessOptions.mdx +3 -1
- package/api-docs/interfaces/ProcessorUpdateOptions.mdx +15 -0
- package/api-docs/interfaces/ReadWritableStream.mdx +12 -0
- package/api-docs/interfaces/Renderer.mdx +2 -2
- package/api-docs/interfaces/RendererOptions.mdx +25 -12
- package/api-docs/interfaces/Segmenter.mdx +89 -10
- package/api-docs/interfaces/SegmenterOptions.mdx +0 -1
- package/api-docs/interfaces/SelectionOptions.mdx +15 -0
- package/api-docs/interfaces/Stats.mdx +11 -0
- package/api-docs/interfaces/VideoProcessor.mdx +49 -5
- package/api-docs/interfaces/Weights.mdx +12 -0
- package/api-docs/type-aliases/OptionalKeys.mdx +10 -0
- package/api-docs/type-aliases/ProcessEvents.mdx +1 -0
- package/api-docs/type-aliases/ProcessInputType.mdx +3 -1
- package/api-docs/type-aliases/ProcessWorkerEvents.mdx +6 -4
- package/api-docs/type-aliases/ProcessorWorkerEvents.mdx +1 -1
- package/api-docs/type-aliases/Struct.mdx +9 -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/FRAME_RATE.mdx +1 -1
- package/api-docs/variables/ImageFetchError.mdx +3 -0
- 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/MediaStreamTrackTransferable.mdx +5 -0
- package/api-docs/variables/PERSON_CENTER.mdx +3 -0
- package/api-docs/variables/RENDERER_OPTIONS_STRUCT.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/cloneImageRecord.mdx +19 -0
- package/api-docs/variables/compareImageRecords.mdx +18 -0
- package/api-docs/variables/createRemoteImageBitmap.mdx +1 -1
- package/api-docs/variables/getCanvasRenderingContext2D.mdx +14 -0
- package/api-docs/variables/getImageSize.mdx +13 -0
- package/api-docs/variables/isRenderOptionsKey.mdx +13 -0
- package/api-docs/variables/isValidGpuAPI.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 -44
- 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 +2 -3
- package/dist/common/backends/webgl/texture.js +21 -19
- 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 +72 -10
- 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 +205 -43
- 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 -3
- package/dist/common/backends/webgpu/texture.js +29 -20
- 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 +22 -5
- package/dist/common/constants.js +23 -5
- package/dist/common/index.d.ts +2 -0
- package/dist/common/index.js +2 -0
- package/dist/common/tsconfig.tsbuildinfo +1 -1
- package/dist/common/typeGuards.d.ts +2 -0
- package/dist/common/typeGuards.js +7 -0
- package/dist/common/types/media.d.ts +5 -1
- package/dist/common/types/messageEvents.d.ts +31 -4
- package/dist/common/types/processor.d.ts +6 -4
- package/dist/common/types/render.d.ts +147 -15
- package/dist/common/types/render.js +23 -1
- package/dist/common/types/segmentation.d.ts +0 -12
- package/dist/common/types/utils.d.ts +21 -0
- package/dist/common/utils.d.ts +29 -2
- package/dist/common/utils.js +206 -10
- package/dist/main/audio.js +3 -3
- package/dist/main/benchUtils.d.ts +35 -3
- package/dist/main/benchUtils.js +149 -15
- package/dist/main/index.d.ts +1 -1
- package/dist/main/index.js +1 -1
- package/dist/main/tsconfig.tsbuildinfo +1 -1
- package/dist/main/types.d.ts +0 -6
- package/dist/main/utils.d.ts +4 -0
- package/dist/main/utils.js +31 -0
- package/dist/main/video/constants.d.ts +1 -4
- package/dist/main/video/constants.js +1 -5
- package/dist/main/video/index.d.ts +3 -4
- package/dist/main/video/index.js +3 -4
- package/dist/main/video/inputProcessAdaptor.d.ts +30 -0
- package/dist/main/video/inputProcessAdaptor.js +198 -0
- package/dist/main/video/segmenter.d.ts +35 -0
- package/dist/main/video/{segmenters/mediapipe.js → segmenter.js} +93 -53
- package/dist/main/video/typeGuards.d.ts +2 -0
- package/dist/main/video/typeGuards.js +8 -1
- package/dist/main/video/types.d.ts +36 -16
- package/dist/main/video/utils.d.ts +5 -4
- package/dist/main/video/utils.js +49 -44
- package/dist/main/video/video.d.ts +29 -7
- package/dist/main/video/video.js +323 -28
- package/dist/workers/mediaWorker.js +35 -162
- package/dist/workers/tsconfig.tsbuildinfo +1 -1
- package/dist/worklets/tsconfig.tsbuildinfo +1 -1
- package/package.json +13 -12
- package/api-docs/functions/createCanvasTransform.mdx +0 -14
- package/api-docs/functions/createVideoTrackProcessor.mdx +0 -9
- package/api-docs/functions/createVideoTrackProcessorWithFallback.mdx +0 -15
- package/api-docs/interfaces/Detector.mdx +0 -81
- package/api-docs/interfaces/SegmentationParams.mdx +0 -18
- package/api-docs/interfaces/Transform.mdx +0 -62
- package/api-docs/type-aliases/ProcessVideoTrack.mdx +0 -15
- package/api-docs/type-aliases/SegmentationTransform.mdx +0 -3
- 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
- package/dist/main/transformer.d.ts +0 -1
- package/dist/main/transformer.js +0 -4
- package/dist/main/video/canvasTransform.d.ts +0 -17
- package/dist/main/video/canvasTransform.js +0 -253
- package/dist/main/video/segmenters/mediapipe.d.ts +0 -22
- package/dist/main/video/transformer.d.ts +0 -9
- package/dist/main/video/transformer.js +0 -68
- package/dist/main/video/videoStreamTrackProcessor.d.ts +0 -20
- package/dist/main/video/videoStreamTrackProcessor.js +0 -86
|
@@ -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 {};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { makeShaderDataDefinitions, makeStructuredView } from 'webgpu-utils';
|
|
2
|
+
import { assert } from '@pexip/utils';
|
|
3
|
+
import { RENDERING_EVENTS } from '../../constants';
|
|
4
|
+
import jointBilateralFilterHorizontalShader from './shaders/jointBilateralFilterHorizontal.wgsl';
|
|
5
|
+
import jointBilateralFilterVerticalShader from './shaders/jointBilateralFilterVertical.wgsl';
|
|
6
|
+
import { updateTextureStorageFormat } from './webgpuUtils';
|
|
7
|
+
export const createJointBilateralFilterRenderer = (processor, device, isContextLost, width, height, maskFormat) => {
|
|
8
|
+
// Dispatch workgroups based on the shader's workgroup size (64x4)
|
|
9
|
+
const TILE_W = 32;
|
|
10
|
+
const TILE_ROWS = 4;
|
|
11
|
+
const TILE_H = 32;
|
|
12
|
+
const TILE_COLS = 4;
|
|
13
|
+
// Create intermediate texture for the filtered result
|
|
14
|
+
const tempTexture = device.createTexture({
|
|
15
|
+
label: 'Joint Bilateral Filtered temporary mask texture',
|
|
16
|
+
format: maskFormat,
|
|
17
|
+
size: [width, height],
|
|
18
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING,
|
|
19
|
+
});
|
|
20
|
+
const outputTexture = device.createTexture({
|
|
21
|
+
label: 'Joint Bilateral Filtered Mask Texture',
|
|
22
|
+
format: maskFormat,
|
|
23
|
+
size: [width, height],
|
|
24
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING,
|
|
25
|
+
});
|
|
26
|
+
const defs = makeShaderDataDefinitions(jointBilateralFilterHorizontalShader);
|
|
27
|
+
// @ts-expect-error This is what we get from the API
|
|
28
|
+
const uniforms = makeStructuredView(defs.uniforms.params);
|
|
29
|
+
// Create params buffer
|
|
30
|
+
const paramsBuffer = device.createBuffer({
|
|
31
|
+
size: uniforms.arrayBuffer.byteLength,
|
|
32
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
33
|
+
});
|
|
34
|
+
const scaleFactor = width / processor.width;
|
|
35
|
+
const texelWidth = 1 / width;
|
|
36
|
+
const texelHeight = 1 / height;
|
|
37
|
+
uniforms.set({
|
|
38
|
+
width,
|
|
39
|
+
height,
|
|
40
|
+
texelWidth,
|
|
41
|
+
texelHeight,
|
|
42
|
+
});
|
|
43
|
+
const updateParams = (params) => {
|
|
44
|
+
uniforms.set({
|
|
45
|
+
...params,
|
|
46
|
+
sigmaSpace: params.sigmaSpace * scaleFactor,
|
|
47
|
+
});
|
|
48
|
+
device.queue.writeBuffer(paramsBuffer, 0, uniforms.arrayBuffer);
|
|
49
|
+
};
|
|
50
|
+
// Generate shader code with appropriate format
|
|
51
|
+
const shaderHCode = updateTextureStorageFormat(jointBilateralFilterHorizontalShader, maskFormat);
|
|
52
|
+
const shaderVCode = updateTextureStorageFormat(jointBilateralFilterVerticalShader, maskFormat);
|
|
53
|
+
// Cache shader module and pipeline at init time
|
|
54
|
+
const shaderHModule = device.createShaderModule({
|
|
55
|
+
label: 'joint bilateral filter horizontal compute',
|
|
56
|
+
code: shaderHCode,
|
|
57
|
+
});
|
|
58
|
+
const shaderVModule = device.createShaderModule({
|
|
59
|
+
label: 'joint bilateral filter vertical compute',
|
|
60
|
+
code: shaderVCode,
|
|
61
|
+
});
|
|
62
|
+
const computeHPipeline = device.createComputePipeline({
|
|
63
|
+
label: 'joint bilateral filter horizontal pipeline',
|
|
64
|
+
layout: 'auto',
|
|
65
|
+
compute: { module: shaderHModule },
|
|
66
|
+
});
|
|
67
|
+
const computeVPipeline = device.createComputePipeline({
|
|
68
|
+
label: 'joint bilateral filter vertical pipeline',
|
|
69
|
+
layout: 'auto',
|
|
70
|
+
compute: { module: shaderVModule },
|
|
71
|
+
});
|
|
72
|
+
const uniformsHBindGroups = device.createBindGroup({
|
|
73
|
+
layout: computeHPipeline.getBindGroupLayout(1),
|
|
74
|
+
entries: [
|
|
75
|
+
{ binding: 0, resource: { buffer: paramsBuffer } },
|
|
76
|
+
{ binding: 1, resource: tempTexture.createView() },
|
|
77
|
+
],
|
|
78
|
+
});
|
|
79
|
+
const uniformsVBindGroups = device.createBindGroup({
|
|
80
|
+
layout: computeVPipeline.getBindGroupLayout(1),
|
|
81
|
+
entries: [
|
|
82
|
+
{ binding: 0, resource: { buffer: paramsBuffer } },
|
|
83
|
+
{ binding: 1, resource: outputTexture.createView() },
|
|
84
|
+
],
|
|
85
|
+
});
|
|
86
|
+
const compute = (label, encoder, pipeline, bindGroup0, bindGroup1, workgroupCountX, workgroupCountY) => {
|
|
87
|
+
const computePass = encoder.beginComputePass({ label });
|
|
88
|
+
computePass.setPipeline(pipeline);
|
|
89
|
+
computePass.setBindGroup(0, bindGroup0);
|
|
90
|
+
computePass.setBindGroup(1, bindGroup1);
|
|
91
|
+
computePass.dispatchWorkgroups(workgroupCountX, workgroupCountY);
|
|
92
|
+
computePass.end();
|
|
93
|
+
};
|
|
94
|
+
const wgCntXH = Math.ceil(width / TILE_W);
|
|
95
|
+
const wgCntYH = Math.ceil(height / TILE_ROWS);
|
|
96
|
+
const wgCntXV = Math.ceil(width / TILE_COLS);
|
|
97
|
+
const wgCntYV = Math.ceil(height / TILE_H);
|
|
98
|
+
let cacheMaskTexture = null;
|
|
99
|
+
let cacheGuideTexture = null;
|
|
100
|
+
let cacheBindGroupH = null;
|
|
101
|
+
let cacheBindGroupV = null;
|
|
102
|
+
const cache = {
|
|
103
|
+
sigmaSpace: -1,
|
|
104
|
+
sigmaRangeLo: -1,
|
|
105
|
+
sigmaRangeHi: -1,
|
|
106
|
+
backgroundThreshold: -1,
|
|
107
|
+
foregroundThreshold: -1,
|
|
108
|
+
};
|
|
109
|
+
return {
|
|
110
|
+
render: (encoder, maskTexture, guideTexture, params) => {
|
|
111
|
+
assert(!isContextLost(), RENDERING_EVENTS.ContextLost);
|
|
112
|
+
if (!(cache.sigmaSpace === params.sigmaSpace &&
|
|
113
|
+
cache.sigmaRangeLo === params.sigmaRangeLo &&
|
|
114
|
+
cache.sigmaRangeHi === params.sigmaRangeHi &&
|
|
115
|
+
cache.backgroundThreshold === params.backgroundThreshold &&
|
|
116
|
+
cache.foregroundThreshold === params.foregroundThreshold)) {
|
|
117
|
+
updateParams(params);
|
|
118
|
+
cache.sigmaSpace = params.sigmaSpace;
|
|
119
|
+
cache.sigmaRangeLo = params.sigmaRangeLo;
|
|
120
|
+
cache.sigmaRangeHi = params.sigmaRangeHi;
|
|
121
|
+
cache.backgroundThreshold = params.backgroundThreshold;
|
|
122
|
+
cache.foregroundThreshold = params.foregroundThreshold;
|
|
123
|
+
}
|
|
124
|
+
// Horizontal
|
|
125
|
+
if (cacheMaskTexture !== maskTexture ||
|
|
126
|
+
cacheGuideTexture !== guideTexture) {
|
|
127
|
+
cacheBindGroupH = device.createBindGroup({
|
|
128
|
+
layout: computeHPipeline.getBindGroupLayout(0),
|
|
129
|
+
entries: [
|
|
130
|
+
{ binding: 0, resource: maskTexture.createView() },
|
|
131
|
+
{ binding: 1, resource: guideTexture.createView() },
|
|
132
|
+
],
|
|
133
|
+
});
|
|
134
|
+
cacheMaskTexture = maskTexture;
|
|
135
|
+
}
|
|
136
|
+
// Horizontal pipeline
|
|
137
|
+
compute('Joint Bilateral Filter Horizontal pass', encoder, computeHPipeline, cacheBindGroupH, uniformsHBindGroups, wgCntXH, wgCntYH);
|
|
138
|
+
// Vertical
|
|
139
|
+
if (cacheGuideTexture !== guideTexture) {
|
|
140
|
+
cacheBindGroupV = device.createBindGroup({
|
|
141
|
+
layout: computeVPipeline.getBindGroupLayout(0),
|
|
142
|
+
entries: [
|
|
143
|
+
{ binding: 0, resource: tempTexture.createView() },
|
|
144
|
+
{ binding: 1, resource: guideTexture.createView() },
|
|
145
|
+
],
|
|
146
|
+
});
|
|
147
|
+
cacheGuideTexture = guideTexture;
|
|
148
|
+
}
|
|
149
|
+
compute('Joint Bilateral Filter Vertical pass', encoder, computeVPipeline, cacheBindGroupV, uniformsVBindGroups, wgCntXV, wgCntYV);
|
|
150
|
+
return outputTexture;
|
|
151
|
+
},
|
|
152
|
+
release: () => {
|
|
153
|
+
tempTexture.destroy();
|
|
154
|
+
outputTexture.destroy();
|
|
155
|
+
paramsBuffer.destroy();
|
|
156
|
+
cacheMaskTexture = null;
|
|
157
|
+
cacheGuideTexture = null;
|
|
158
|
+
cacheBindGroupH = null;
|
|
159
|
+
cacheBindGroupV = null;
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
};
|