@pexip/media-processor 22.0.0 → 22.2.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 +41 -0
- package/api-docs/README.mdx +4 -1
- package/api-docs/functions/avg.mdx +1 -1
- package/api-docs/functions/createAnalyzerGraphNode.mdx +3 -1
- package/api-docs/functions/createAnalyzerSubscribableGraphNode.mdx +3 -1
- package/api-docs/functions/createAsyncCallbackLoop.mdx +6 -6
- package/api-docs/functions/createAudioDestinationGraphNode.mdx +1 -1
- package/api-docs/functions/createAudioSignalDetector.mdx +4 -4
- package/api-docs/functions/createAudioStats.mdx +1 -1
- package/api-docs/functions/createBenchmark.mdx +7 -6
- package/api-docs/functions/createChannelMergerGraphNode.mdx +1 -1
- package/api-docs/functions/createChannelSplitterGraphNode.mdx +1 -1
- package/api-docs/functions/createDelayGraphNode.mdx +1 -1
- package/api-docs/functions/createDenoiseWorkletGraphNode.mdx +2 -2
- package/api-docs/functions/createFrameCallbackRequest.mdx +3 -3
- package/api-docs/functions/createGainGraphNode.mdx +3 -1
- package/api-docs/functions/createMediaElementSourceGraphNode.mdx +1 -1
- package/api-docs/functions/createReadWritableStreamFallbackInputProcessAdaptor.mdx +1 -1
- package/api-docs/functions/createSegmenter.mdx +1 -1
- package/api-docs/functions/createStreamDestinationGraphNode.mdx +1 -1
- package/api-docs/functions/createStreamSourceGraphNode.mdx +1 -1
- package/api-docs/functions/createVADetector.mdx +3 -3
- package/api-docs/functions/createVoiceDetectorFromProbability.mdx +1 -1
- package/api-docs/functions/createVoiceDetectorFromTimeData.mdx +3 -1
- package/api-docs/functions/createWindowedStats.mdx +2 -2
- package/api-docs/functions/getBezierCurveControlPoints.mdx +3 -1
- package/api-docs/functions/labelComponents.mdx +3 -1
- package/api-docs/functions/loadScript.mdx +1 -1
- package/api-docs/functions/loadWasms.mdx +1 -1
- package/api-docs/functions/nearestPowerOfTwo.mdx +1 -1
- package/api-docs/functions/playVideo.mdx +6 -4
- package/api-docs/functions/processAverageVolume.mdx +1 -1
- package/api-docs/functions/rms.mdx +1 -1
- package/api-docs/functions/subscribeWorkletNode.mdx +1 -1
- package/api-docs/functions/sum.mdx +1 -1
- package/api-docs/interfaces/AnalyzerSubscribableOptions.mdx +5 -3
- package/api-docs/interfaces/AudioGraph.mdx +2 -2
- package/api-docs/interfaces/AudioNodeInit.mdx +10 -6
- package/api-docs/interfaces/AudioNodeProps.mdx +3 -3
- package/api-docs/interfaces/Benchmark.mdx +3 -3
- package/api-docs/interfaces/Clock.mdx +1 -1
- package/api-docs/interfaces/Denoise.mdx +2 -2
- package/api-docs/interfaces/InputProcessAdaptor.mdx +6 -2
- package/api-docs/interfaces/MPMask.mdx +1 -1
- package/api-docs/interfaces/Process.mdx +5 -5
- package/api-docs/interfaces/ProcessWorkerDebugMessage.mdx +1 -1
- package/api-docs/interfaces/ProcessWorkerEventClosed.mdx +5 -0
- package/api-docs/interfaces/ProcessorEvent.mdx +2 -1
- package/api-docs/interfaces/ProcessorProcessOptions.mdx +2 -2
- package/api-docs/interfaces/ProcessorUpdateOptions.mdx +3 -1
- package/api-docs/interfaces/ReadWritableStream.mdx +2 -2
- package/api-docs/interfaces/RenderEventHandlers.mdx +3 -3
- package/api-docs/interfaces/Renderer.mdx +3 -1
- package/api-docs/interfaces/RendererOptions.mdx +5 -5
- package/api-docs/interfaces/Runner.mdx +3 -3
- package/api-docs/interfaces/Segmentation.mdx +1 -1
- package/api-docs/interfaces/Segmenter.mdx +39 -19
- package/api-docs/interfaces/VideoFrameLike.mdx +1 -1
- package/api-docs/interfaces/VideoProcessor.mdx +7 -7
- package/api-docs/interfaces/WorkletMessagePortOptions.mdx +2 -2
- package/api-docs/type-aliases/AsyncCallback.mdx +1 -1
- package/api-docs/type-aliases/Callback.mdx +2 -2
- package/api-docs/type-aliases/ExtractMessageEventType.mdx +1 -1
- package/api-docs/type-aliases/IncludeMessageEventDataType.mdx +1 -1
- package/api-docs/type-aliases/Logger.mdx +12 -0
- package/api-docs/type-aliases/OptionalKeys.mdx +1 -1
- package/api-docs/type-aliases/ProcessWorkerEvents.mdx +1 -0
- package/api-docs/type-aliases/RunnerCreator.mdx +3 -3
- package/api-docs/type-aliases/TupleOf.mdx +1 -1
- package/api-docs/variables/RENDER_BACKEND.mdx +1 -0
- package/api-docs/variables/cloneImageRecord.mdx +3 -1
- package/api-docs/variables/createLazyProps.mdx +1 -1
- package/api-docs/variables/createObjectUpdater.mdx +2 -2
- package/api-docs/variables/createRemoteImageBitmap.mdx +5 -5
- package/api-docs/variables/getCanUseWebGL.mdx +7 -0
- package/api-docs/variables/handleWebGLContextLoss.mdx +1 -1
- package/api-docs/variables/urls.mdx +2 -2
- package/dist/common/backends/canvas2d/renderer.d.ts +2 -0
- package/dist/common/backends/canvas2d/renderer.js +36 -0
- package/dist/common/backends/webgl/blur.d.ts +1 -1
- package/dist/common/backends/webgl/blur.js +11 -25
- package/dist/common/backends/webgl/renderer.js +4 -4
- package/dist/common/backends/webgpu/dualFilterBlur.js +93 -114
- package/dist/common/canvasRenderUtils.d.ts +23 -4
- package/dist/common/canvasRenderUtils.js +48 -11
- package/dist/common/constants.d.ts +1 -0
- package/dist/common/constants.js +1 -0
- package/dist/common/inferencer.d.ts +18 -0
- package/dist/common/inferencer.js +60 -0
- package/dist/common/tsconfig.tsbuildinfo +1 -1
- package/dist/common/types/messageEvents.d.ts +4 -1
- package/dist/common/types/processor.d.ts +1 -0
- package/dist/common/utils.d.ts +1 -0
- package/dist/common/utils.js +13 -0
- package/dist/main/audio.js +20 -2
- package/dist/main/benchUtils.d.ts +28 -18
- package/dist/main/benchUtils.js +66 -56
- package/dist/main/tsconfig.tsbuildinfo +1 -1
- package/dist/main/utils.d.ts +9 -0
- package/dist/main/utils.js +12 -0
- package/dist/main/video/segmenter.d.ts +8 -1
- package/dist/main/video/segmenter.js +44 -29
- package/dist/main/video/types.d.ts +1 -1
- package/dist/main/video/video.d.ts +1 -1
- package/dist/main/video/video.js +25 -12
- package/dist/workers/mediaWorker.js +23 -23
- package/dist/workers/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -9
|
@@ -5,11 +5,12 @@ import { m3 } from '../../m3';
|
|
|
5
5
|
import { createFrameBuffer, createGLProgram, createMaskTexture, createRGBAFrameTexture, } from './webglUtils';
|
|
6
6
|
import vertexSrc from './shaders/matrix.vert';
|
|
7
7
|
import fragmentSrc from './shaders/dualFilterBlur.frag';
|
|
8
|
-
const
|
|
8
|
+
const MAX_BLUR_LEVEL = 9;
|
|
9
|
+
const clampBlurAmount = clamping(1, MAX_BLUR_LEVEL);
|
|
9
10
|
/**
|
|
10
11
|
* Blur provided frame
|
|
11
12
|
*/
|
|
12
|
-
export const createBlurRenderer = (processor,
|
|
13
|
+
export const createBlurRenderer = (processor, redOnly = false) => {
|
|
13
14
|
const { gl, program, attribLocations, uniformLocations, floatSingleChannelSupported, floatFullChannelSupported, release, } = createGLProgram(processor, {
|
|
14
15
|
attribs: {
|
|
15
16
|
position: 'a_position',
|
|
@@ -62,23 +63,19 @@ export const createBlurRenderer = (processor, pass, redOnly = false) => {
|
|
|
62
63
|
};
|
|
63
64
|
const createFramebufferAndTexture = (idx) => {
|
|
64
65
|
// Avoid fractional value
|
|
65
|
-
const width = Math.trunc(processor.width / 2 ** idx);
|
|
66
|
-
const height = Math.trunc(processor.height / 2 ** idx);
|
|
66
|
+
const width = Math.max(Math.trunc(processor.width / 2 ** idx), 1);
|
|
67
|
+
const height = Math.max(Math.trunc(processor.height / 2 ** idx), 1);
|
|
67
68
|
const texture = redOnly
|
|
68
69
|
? createMaskTexture(gl, width, height, true, floatSingleChannelSupported, floatFullChannelSupported)
|
|
69
70
|
: createRGBAFrameTexture(gl, width, height, true, floatFullChannelSupported);
|
|
70
71
|
const frameBuffer = createFrameBuffer(gl, texture);
|
|
71
72
|
return { texture, frameBuffer, width, height };
|
|
72
73
|
};
|
|
73
|
-
|
|
74
|
-
const length = Math.max(clampBlurAmount(offset + pass) - offset, 0);
|
|
75
|
-
return Array.from({
|
|
76
|
-
length,
|
|
77
|
-
}).map((_, currentIdx) => createFramebufferAndTexture(offset + currentIdx));
|
|
78
|
-
};
|
|
79
|
-
// Generate multiple FrameBuffers, textures and the widths and heights are of size
|
|
74
|
+
// Allocate all levels upfront, textures and the widths and heights are of size
|
|
80
75
|
// of half of the previous one. E.g. 640x480 -> 320x240 -> 160x120 -> 80x60
|
|
81
|
-
const blurBuffers =
|
|
76
|
+
const blurBuffers = Array.from({
|
|
77
|
+
length: MAX_BLUR_LEVEL,
|
|
78
|
+
}).map((_, currentIdx) => createFramebufferAndTexture(currentIdx));
|
|
82
79
|
return {
|
|
83
80
|
render: (frame, pass) => {
|
|
84
81
|
assert(!gl.isContextLost(), RENDERING_EVENTS.ContextLost);
|
|
@@ -98,18 +95,7 @@ export const createBlurRenderer = (processor, pass, redOnly = false) => {
|
|
|
98
95
|
// Dual filter blur
|
|
99
96
|
// Downsampling
|
|
100
97
|
gl.uniform1i(uniformLocations.downsampling, 1);
|
|
101
|
-
|
|
102
|
-
const currentPass = pass + 1;
|
|
103
|
-
if (currentPass > blurBuffers.length) {
|
|
104
|
-
for (const buffer of generateBlurBuffers(currentPass - blurBuffers.length, blurBuffers.length)) {
|
|
105
|
-
blurBuffers.push(buffer);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
while (currentPass < blurBuffers.length) {
|
|
109
|
-
const buffer = blurBuffers.pop();
|
|
110
|
-
gl.deleteTexture(buffer?.texture ?? null);
|
|
111
|
-
gl.deleteFramebuffer(buffer?.frameBuffer ?? null);
|
|
112
|
-
}
|
|
98
|
+
const currentPass = clampBlurAmount(pass + 1);
|
|
113
99
|
// Skip the first pass
|
|
114
100
|
for (let i = 1; i < currentPass; i++) {
|
|
115
101
|
const { frameBuffer = null, texture = null, width = processor.width, height = processor.height, } = blurBuffers[i] ?? {};
|
|
@@ -134,7 +120,7 @@ export const createBlurRenderer = (processor, pass, redOnly = false) => {
|
|
|
134
120
|
// Upsampling
|
|
135
121
|
gl.uniform1i(uniformLocations.downsampling, 0);
|
|
136
122
|
// Skip the first pass
|
|
137
|
-
for (let i =
|
|
123
|
+
for (let i = currentPass - 2; i >= 0; i--) {
|
|
138
124
|
const { frameBuffer = null, texture = null, width = processor.width, height = processor.height, } = blurBuffers[i] ?? {};
|
|
139
125
|
// console.log(`Drawing upsampling pass ${i}, ${width}x${height}`);
|
|
140
126
|
// Setup to draw to the frame buffer
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { assert } from '@pexip/utils';
|
|
2
|
-
import {
|
|
2
|
+
import { PROCESSING_HEIGHT, PROCESSING_WIDTH, RENDERING_EVENTS, STRONG_EDGE_BLUR_AMOUNT, } from '../../constants';
|
|
3
3
|
import { createLazyProps, handleWebGLContextLoss } from '../../utils';
|
|
4
4
|
import { createBinaryMaskRenderer } from './binaryMask';
|
|
5
5
|
import { createBlendRenderer } from './blender';
|
|
@@ -25,7 +25,7 @@ export const createRenderer = (renderEventHandlers, canvas = new OffscreenCanvas
|
|
|
25
25
|
const rendererCreator = {
|
|
26
26
|
backgroundImageRenderer: () => createTextureRenderer(canvas, false),
|
|
27
27
|
blendRenderer: () => createBlendRenderer(canvas),
|
|
28
|
-
blurRenderer: () => createBlurRenderer(canvas
|
|
28
|
+
blurRenderer: () => createBlurRenderer(canvas),
|
|
29
29
|
canvasRenderer: () => createCanvasRenderer(canvas),
|
|
30
30
|
frameDownsampler2: () => createTextureToTextureRenderer(canvas, false, true, halfWidth, halfHeight),
|
|
31
31
|
frameDownsampler4: () => createTextureToTextureRenderer(canvas, false, true, quarterWidth, quarterHeight),
|
|
@@ -34,9 +34,9 @@ export const createRenderer = (renderEventHandlers, canvas = new OffscreenCanvas
|
|
|
34
34
|
jointBilateralFilterRenderer2: () => createJointBilateralFilterRenderer(canvas, halfWidth, halfHeight),
|
|
35
35
|
jointBilateralFilterRenderer4: () => createJointBilateralFilterRenderer(canvas, quarterWidth, quarterHeight),
|
|
36
36
|
jointBilateralFilterRenderer: () => createJointBilateralFilterRenderer(canvas, canvas.width, canvas.height),
|
|
37
|
-
lightWrapBlurRenderer: () => createBlurRenderer(canvas
|
|
37
|
+
lightWrapBlurRenderer: () => createBlurRenderer(canvas),
|
|
38
38
|
maskBlurRenderer: () => createTentBlurRenderer(canvas, canvas.width, canvas.height),
|
|
39
|
-
strongMaskBlurRenderer: () => createBlurRenderer(canvas
|
|
39
|
+
strongMaskBlurRenderer: () => createBlurRenderer(canvas),
|
|
40
40
|
maskDownsampler2: () => createTextureToTextureRenderer(canvas, true, false, halfWidth, halfHeight),
|
|
41
41
|
maskDownsampler4: () => createTextureToTextureRenderer(canvas, true, false, quarterWidth, quarterHeight),
|
|
42
42
|
maskRenderer: () => createTextureRenderer(canvas, true),
|
|
@@ -2,7 +2,8 @@ import { assert } from '@pexip/utils';
|
|
|
2
2
|
import { RENDERING_EVENTS } from '../../constants';
|
|
3
3
|
import blurComputeShader from './shaders/dualBlurComputeShader.wgsl';
|
|
4
4
|
import { clamping } from '../../utils';
|
|
5
|
-
const
|
|
5
|
+
const MAX_BLUR_LEVEL = 9;
|
|
6
|
+
const clampBlurAmount = clamping(1, MAX_BLUR_LEVEL);
|
|
6
7
|
export const createBlurRenderer = (device, width, height, textureFormat, isContextLost) => {
|
|
7
8
|
const shaderModule = device.createShaderModule({
|
|
8
9
|
label: 'dual blur compute shader',
|
|
@@ -34,103 +35,80 @@ export const createBlurRenderer = (device, width, height, textureFormat, isConte
|
|
|
34
35
|
addressModeU: 'clamp-to-edge',
|
|
35
36
|
addressModeV: 'clamp-to-edge',
|
|
36
37
|
});
|
|
37
|
-
//
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
38
|
+
// Allocate all levels upfront at max blur depth
|
|
39
|
+
const blurTextures = [];
|
|
40
|
+
for (let i = 0; i < MAX_BLUR_LEVEL; i++) {
|
|
41
|
+
const w = Math.max(Math.trunc(width / 2 ** i), 1);
|
|
42
|
+
const h = Math.max(Math.trunc(height / 2 ** i), 1);
|
|
43
|
+
const texture = device.createTexture({
|
|
44
|
+
label: `dual filter blur texture level ${i}`,
|
|
45
|
+
format: textureFormat,
|
|
46
|
+
size: [w, h],
|
|
47
|
+
usage: GPUTextureUsage.TEXTURE_BINDING |
|
|
48
|
+
GPUTextureUsage.RENDER_ATTACHMENT,
|
|
49
|
+
});
|
|
50
|
+
blurTextures.push({
|
|
51
|
+
texture,
|
|
52
|
+
view: texture.createView(),
|
|
53
|
+
width: w,
|
|
54
|
+
height: h,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
// Pre-create all downsample bind groups: blurTextures[i-1] → blurTextures[i]
|
|
58
|
+
const downsampleBindGroups = [];
|
|
59
|
+
const downsampleRenderPassDescriptors = [];
|
|
60
|
+
for (let i = 2; i < MAX_BLUR_LEVEL; i++) {
|
|
61
|
+
const src = blurTextures[i - 1];
|
|
62
|
+
const dst = blurTextures[i];
|
|
63
|
+
assert(src);
|
|
64
|
+
assert(dst);
|
|
65
|
+
downsampleBindGroups.push(device.createBindGroup({
|
|
66
|
+
layout: downsamplePipeline.getBindGroupLayout(0),
|
|
67
|
+
entries: [
|
|
68
|
+
{ binding: 0, resource: sampler },
|
|
69
|
+
{ binding: 1, resource: src.view },
|
|
70
|
+
],
|
|
71
|
+
}));
|
|
72
|
+
downsampleRenderPassDescriptors.push({
|
|
73
|
+
label: `downsample render pass ${i}`,
|
|
74
|
+
colorAttachments: [
|
|
75
|
+
{ loadOp: 'load', storeOp: 'store', view: dst.view },
|
|
76
|
+
],
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
// Pre-create all upsample bind groups: blurTextures[i+1] → blurTextures[i]
|
|
80
|
+
// Index 0 = deepest pair (MAX_BLUR_LEVELS-1 → MAX_BLUR_LEVEL-2)
|
|
81
|
+
const upsampleBindGroups = [];
|
|
82
|
+
const upsampleRenderPassDescriptors = [];
|
|
83
|
+
for (let i = MAX_BLUR_LEVEL - 2; i >= 0; i--) {
|
|
84
|
+
const src = blurTextures[i + 1];
|
|
85
|
+
const dst = blurTextures[i];
|
|
86
|
+
assert(src);
|
|
87
|
+
assert(dst);
|
|
88
|
+
upsampleBindGroups.push(device.createBindGroup({
|
|
89
|
+
layout: upsamplePipeline.getBindGroupLayout(0),
|
|
90
|
+
entries: [
|
|
91
|
+
{ binding: 0, resource: sampler },
|
|
92
|
+
{ binding: 1, resource: src.view },
|
|
93
|
+
],
|
|
94
|
+
}));
|
|
95
|
+
upsampleRenderPassDescriptors.push({
|
|
96
|
+
label: `upsample render pass ${i}`,
|
|
97
|
+
colorAttachments: [
|
|
98
|
+
{ loadOp: 'load', storeOp: 'store', view: dst.view },
|
|
99
|
+
],
|
|
100
|
+
});
|
|
101
|
+
}
|
|
53
102
|
let cacheFrameTexture = null;
|
|
54
103
|
let cacheFirstDownsampleBindGroup = null;
|
|
55
104
|
let cacheFirstDownsampleRenderPassDescriptor = null;
|
|
56
|
-
// Helper to (re)generate textures and bind groups if needed
|
|
57
|
-
function ensureBlurTextures(pass, frameWidth, frameHeight) {
|
|
58
|
-
const length = clampBlurAmount(pass + 1);
|
|
59
|
-
if (blurTextures.length !== length ||
|
|
60
|
-
lastWidth !== frameWidth ||
|
|
61
|
-
lastHeight !== frameHeight) {
|
|
62
|
-
// Destroy old textures
|
|
63
|
-
for (const t of blurTextures) {
|
|
64
|
-
t.texture.destroy();
|
|
65
|
-
}
|
|
66
|
-
blurTextures = [];
|
|
67
|
-
downsampleBindGroups = [];
|
|
68
|
-
downsampleRenderPassDescriptor = [];
|
|
69
|
-
upsampleBindGroups = [];
|
|
70
|
-
// Invalidate frame cache since firstDst view has changed
|
|
71
|
-
cacheFrameTexture = null;
|
|
72
|
-
cacheFirstDownsampleBindGroup = null;
|
|
73
|
-
for (let i = 0; i < length; i++) {
|
|
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);
|
|
77
|
-
blurTextures.push({
|
|
78
|
-
texture,
|
|
79
|
-
view: texture.createView(),
|
|
80
|
-
width: w,
|
|
81
|
-
height: h,
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
// Pre-create downsample bind groups: blurTextures[i-1] → blurTextures[i]
|
|
85
|
-
for (let i = 2; i < blurTextures.length; i++) {
|
|
86
|
-
const src = blurTextures[i - 1];
|
|
87
|
-
const dst = blurTextures[i];
|
|
88
|
-
assert(src);
|
|
89
|
-
assert(dst);
|
|
90
|
-
downsampleBindGroups.push(device.createBindGroup({
|
|
91
|
-
layout: downsamplePipeline.getBindGroupLayout(0),
|
|
92
|
-
entries: [
|
|
93
|
-
{ binding: 0, resource: sampler },
|
|
94
|
-
{ binding: 1, resource: src.view },
|
|
95
|
-
],
|
|
96
|
-
}));
|
|
97
|
-
downsampleRenderPassDescriptor.push({
|
|
98
|
-
label: `downsample render pass ${i}`,
|
|
99
|
-
colorAttachments: [
|
|
100
|
-
{ loadOp: 'load', storeOp: 'store', view: dst.view },
|
|
101
|
-
],
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
// Pre-create upsample bind groups: blurTextures[i+1] → blurTextures[i]
|
|
105
|
-
// Filled in descending order so upsampleBindGroups[0] is the deepest pair.
|
|
106
|
-
for (let i = blurTextures.length - 2; i >= 0; i--) {
|
|
107
|
-
const src = blurTextures[i + 1];
|
|
108
|
-
const dst = blurTextures[i];
|
|
109
|
-
assert(src);
|
|
110
|
-
assert(dst);
|
|
111
|
-
upsampleBindGroups.push(device.createBindGroup({
|
|
112
|
-
layout: upsamplePipeline.getBindGroupLayout(0),
|
|
113
|
-
entries: [
|
|
114
|
-
{ binding: 0, resource: sampler },
|
|
115
|
-
{ binding: 1, resource: src.view },
|
|
116
|
-
],
|
|
117
|
-
}));
|
|
118
|
-
upsampleRenderPassDescriptor.push({
|
|
119
|
-
label: `upsample render pass ${i}`,
|
|
120
|
-
colorAttachments: [
|
|
121
|
-
{ loadOp: 'load', storeOp: 'store', view: dst.view },
|
|
122
|
-
],
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
lastWidth = frameWidth;
|
|
126
|
-
lastHeight = frameHeight;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
105
|
return {
|
|
130
106
|
render: (encoder, frame, pass) => {
|
|
131
107
|
assert(!isContextLost(), RENDERING_EVENTS.ContextLost);
|
|
132
|
-
|
|
133
|
-
|
|
108
|
+
if (!pass) {
|
|
109
|
+
return frame;
|
|
110
|
+
}
|
|
111
|
+
const length = clampBlurAmount(pass + 1);
|
|
134
112
|
const firstDst = blurTextures[1];
|
|
135
113
|
assert(firstDst);
|
|
136
114
|
// Cache the first downsample bind group (frame → blurTextures[1])
|
|
@@ -158,34 +136,34 @@ export const createBlurRenderer = (device, width, height, textureFormat, isConte
|
|
|
158
136
|
firstComputePass.draw(3);
|
|
159
137
|
firstComputePass.end();
|
|
160
138
|
// Subsequent downsampling passes: blurTextures[i-1] → blurTextures[i]
|
|
161
|
-
for (let i = 2; i <
|
|
139
|
+
for (let i = 2; i < length; i++) {
|
|
162
140
|
const dst = blurTextures[i];
|
|
163
141
|
assert(dst);
|
|
164
142
|
// downsampleBindGroups[i-1] covers blurTextures[i-1] → blurTextures[i]
|
|
165
143
|
const bindGroup = downsampleBindGroups[i - 2];
|
|
166
|
-
const renderPassDescriptor =
|
|
144
|
+
const renderPassDescriptor = downsampleRenderPassDescriptors[i - 2];
|
|
167
145
|
assert(bindGroup);
|
|
168
146
|
assert(renderPassDescriptor);
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
147
|
+
const renderPass = encoder.beginRenderPass(renderPassDescriptor);
|
|
148
|
+
renderPass.setPipeline(downsamplePipeline);
|
|
149
|
+
renderPass.setBindGroup(0, bindGroup);
|
|
150
|
+
renderPass.draw(3);
|
|
151
|
+
renderPass.end();
|
|
174
152
|
}
|
|
175
153
|
// Upsampling passes (deepest level first, up to blurTextures[0])
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
154
|
+
// upsampleBindGroups are indexed from deepest (MAX-2 → MAX-1) at [0]
|
|
155
|
+
// We need to start from the pair ending at `length-1` and go up to 0
|
|
156
|
+
const upsampleStartIdx = MAX_BLUR_LEVEL - 1 - (length - 1);
|
|
157
|
+
for (let i = upsampleStartIdx; i < upsampleBindGroups.length; i++) {
|
|
180
158
|
const bindGroup = upsampleBindGroups[i];
|
|
181
|
-
const renderPassDescriptor =
|
|
159
|
+
const renderPassDescriptor = upsampleRenderPassDescriptors[i];
|
|
182
160
|
assert(bindGroup);
|
|
183
161
|
assert(renderPassDescriptor);
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
162
|
+
const renderPass = encoder.beginRenderPass(renderPassDescriptor);
|
|
163
|
+
renderPass.setPipeline(upsamplePipeline);
|
|
164
|
+
renderPass.setBindGroup(0, bindGroup);
|
|
165
|
+
renderPass.draw(3);
|
|
166
|
+
renderPass.end();
|
|
189
167
|
}
|
|
190
168
|
const finalTexture = blurTextures[0];
|
|
191
169
|
assert(finalTexture);
|
|
@@ -195,13 +173,14 @@ export const createBlurRenderer = (device, width, height, textureFormat, isConte
|
|
|
195
173
|
for (const t of blurTextures) {
|
|
196
174
|
t.texture.destroy();
|
|
197
175
|
}
|
|
198
|
-
blurTextures =
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
176
|
+
blurTextures.length = 0;
|
|
177
|
+
downsampleBindGroups.length = 0;
|
|
178
|
+
downsampleRenderPassDescriptors.length = 0;
|
|
179
|
+
upsampleBindGroups.length = 0;
|
|
180
|
+
upsampleRenderPassDescriptors.length = 0;
|
|
203
181
|
cacheFrameTexture = null;
|
|
204
182
|
cacheFirstDownsampleBindGroup = null;
|
|
183
|
+
cacheFirstDownsampleRenderPassDescriptor = null;
|
|
205
184
|
},
|
|
206
185
|
};
|
|
207
186
|
};
|
|
@@ -1,4 +1,23 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Create a reusable converter that transforms a single-channel mask into
|
|
3
|
+
* RGBA ImageData with values written into the alpha channel.
|
|
4
|
+
* The returned function reuses the same ImageData buffer across calls.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* const toAlphaMask = createAlphaMaskConverter();
|
|
9
|
+
* const imageData = toAlphaMask(mask);
|
|
10
|
+
* ctx.putImageData(imageData, 0, 0);
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export declare const createAlphaMaskConverter: () => (mask: {
|
|
14
|
+
getAsUint8Array(): Uint8Array;
|
|
15
|
+
width: number;
|
|
16
|
+
height: number;
|
|
17
|
+
}) => ImageData;
|
|
18
|
+
export declare const createCpuMaskRenderer: (canvas: OffscreenCanvas) => {
|
|
19
|
+
render: (imageData: ImageData) => void;
|
|
20
|
+
};
|
|
2
21
|
declare const INTERNAL_CANVAS_KEYS: {
|
|
3
22
|
readonly drawImageDataCanvas: "drawImageDataCanvas";
|
|
4
23
|
readonly maskCanvas: "maskCanvas";
|
|
@@ -52,12 +71,12 @@ export declare const clearCanvas: (canvas: OffscreenCanvas) => void;
|
|
|
52
71
|
export declare const createCanvasRenderUtils: (processingWidth: number, processingHeight: number) => {
|
|
53
72
|
evaluateInput: (inputImage: CanvasImageSource) => OffscreenCanvas;
|
|
54
73
|
renderImageToCanvas: (image: CanvasImageSource, canvas: OffscreenCanvas, dw?: number, dh?: number, options?: CanvasRenderingContext2DOptions) => void;
|
|
55
|
-
drawBlurEffect: (canvas: OffscreenCanvas, inputImage: CanvasImageSource,
|
|
56
|
-
drawOverlayEffect: (canvas: OffscreenCanvas, inputImage: CanvasImageSource, backgroundImage: CanvasImageSource | OffscreenCanvas,
|
|
74
|
+
drawBlurEffect: (canvas: OffscreenCanvas, inputImage: CanvasImageSource, mask: CanvasImageSource | OffscreenCanvas, foregroundThreshold?: number, backgroundBlurAmount?: number, edgeBlurAmount?: number, flipHorizontal?: boolean) => void;
|
|
75
|
+
drawOverlayEffect: (canvas: OffscreenCanvas, inputImage: CanvasImageSource, backgroundImage: CanvasImageSource | OffscreenCanvas, mask: CanvasImageSource | OffscreenCanvas, foregroundThreshold?: number, backgroundBlurAmount?: number, edgeBlurAmount?: number, flipHorizontal?: boolean) => void;
|
|
57
76
|
renderImageToOffScreenCanvas: (image: CanvasImageSource, canvasName: keyof InternalCanvases) => OffscreenCanvas;
|
|
58
77
|
renderImageDataToOffScreenCanvas: (image: ImageData, canvasName: keyof InternalCanvases) => OffscreenCanvas;
|
|
59
78
|
drawAndBlurImageOnOffScreenCanvas: ({ image, blurAmount, offscreenCanvasName, preserveOldDrawing, }: {
|
|
60
|
-
image: CanvasImageSource;
|
|
79
|
+
image: CanvasImageSource | OffscreenCanvas;
|
|
61
80
|
blurAmount: number;
|
|
62
81
|
offscreenCanvasName: keyof InternalCanvases;
|
|
63
82
|
preserveOldDrawing?: boolean;
|
|
@@ -1,4 +1,46 @@
|
|
|
1
1
|
import { resize } from './utils';
|
|
2
|
+
/**
|
|
3
|
+
* Create a reusable converter that transforms a single-channel mask into
|
|
4
|
+
* RGBA ImageData with values written into the alpha channel.
|
|
5
|
+
* The returned function reuses the same ImageData buffer across calls.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const toAlphaMask = createAlphaMaskConverter();
|
|
10
|
+
* const imageData = toAlphaMask(mask);
|
|
11
|
+
* ctx.putImageData(imageData, 0, 0);
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export const createAlphaMaskConverter = () => {
|
|
15
|
+
let imageData;
|
|
16
|
+
return (mask) => {
|
|
17
|
+
const maskData = mask.getAsUint8Array();
|
|
18
|
+
if (!imageData ||
|
|
19
|
+
imageData.width !== mask.width ||
|
|
20
|
+
imageData.height !== mask.height) {
|
|
21
|
+
imageData = new ImageData(mask.width, mask.height);
|
|
22
|
+
}
|
|
23
|
+
const pixels = imageData.data;
|
|
24
|
+
for (let i = 0; i < maskData.length; i++) {
|
|
25
|
+
pixels[i * 4 + 3] = maskData[i];
|
|
26
|
+
}
|
|
27
|
+
return imageData;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
export const createCpuMaskRenderer = (canvas) => {
|
|
31
|
+
let ctx = getCanvasRenderingContext2D(canvas);
|
|
32
|
+
return {
|
|
33
|
+
render: (imageData) => {
|
|
34
|
+
if (canvas.height !== imageData.height ||
|
|
35
|
+
canvas.width !== imageData.width) {
|
|
36
|
+
canvas.width = imageData.width;
|
|
37
|
+
canvas.height = imageData.height;
|
|
38
|
+
ctx = getCanvasRenderingContext2D(canvas);
|
|
39
|
+
}
|
|
40
|
+
ctx.putImageData(imageData, 0, 0);
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
};
|
|
2
44
|
const INTERNAL_CANVAS_KEYS = {
|
|
3
45
|
drawImageDataCanvas: 'drawImageDataCanvas',
|
|
4
46
|
maskCanvas: 'maskCanvas',
|
|
@@ -197,10 +239,8 @@ export const createCanvasRenderUtils = (processingWidth, processingHeight) => {
|
|
|
197
239
|
}
|
|
198
240
|
return canvas;
|
|
199
241
|
};
|
|
200
|
-
const createPersonMask = (
|
|
201
|
-
const backgroundMaskImage =
|
|
202
|
-
? segmentation[0]?.canvas
|
|
203
|
-
: segmentation.canvas;
|
|
242
|
+
const createPersonMask = (mask, _foregroundThreshold, edgeBlurAmount) => {
|
|
243
|
+
const backgroundMaskImage = mask;
|
|
204
244
|
if (!backgroundMaskImage) {
|
|
205
245
|
return getCanvas('maskCanvas');
|
|
206
246
|
}
|
|
@@ -219,17 +259,14 @@ export const createCanvasRenderUtils = (processingWidth, processingHeight) => {
|
|
|
219
259
|
preserveOldDrawing: false,
|
|
220
260
|
});
|
|
221
261
|
};
|
|
222
|
-
const drawBokehEffect = (canvas, inputImage, backgroundImage,
|
|
262
|
+
const drawBokehEffect = (canvas, inputImage, backgroundImage, mask, foregroundThreshold = 0.5, backgroundBlurAmount = 3, edgeBlurAmount = 3, flipHorizontal = false) => {
|
|
223
263
|
const blurredImage = drawAndBlurImageOnOffScreenCanvas({
|
|
224
264
|
image: backgroundImage,
|
|
225
265
|
blurAmount: backgroundBlurAmount,
|
|
226
266
|
offscreenCanvasName: 'blurredCanvas',
|
|
227
267
|
});
|
|
228
268
|
const ctx = getCanvasRenderingContext2D(canvas);
|
|
229
|
-
|
|
230
|
-
return drawImage(ctx, blurredImage, 0, 0);
|
|
231
|
-
}
|
|
232
|
-
const personMask = createPersonMask(segmentations, foregroundThreshold, edgeBlurAmount);
|
|
269
|
+
const personMask = createPersonMask(mask, foregroundThreshold, edgeBlurAmount);
|
|
233
270
|
ctx.save();
|
|
234
271
|
if (flipHorizontal) {
|
|
235
272
|
flipCanvasHorizontal(canvas);
|
|
@@ -250,8 +287,8 @@ export const createCanvasRenderUtils = (processingWidth, processingHeight) => {
|
|
|
250
287
|
drawWithCompositing(ctx, blurredImage, 'destination-over');
|
|
251
288
|
ctx.restore();
|
|
252
289
|
};
|
|
253
|
-
const drawBlurEffect = (canvas, inputImage,
|
|
254
|
-
const drawOverlayEffect = (canvas, inputImage, backgroundImage,
|
|
290
|
+
const drawBlurEffect = (canvas, inputImage, mask, foregroundThreshold = 0.5, backgroundBlurAmount = 3, edgeBlurAmount = 3, flipHorizontal = false) => drawBokehEffect(canvas, inputImage, inputImage, mask, foregroundThreshold, backgroundBlurAmount, edgeBlurAmount, flipHorizontal);
|
|
291
|
+
const drawOverlayEffect = (canvas, inputImage, backgroundImage, mask, foregroundThreshold = 0.5, backgroundBlurAmount = 0, edgeBlurAmount = 3, flipHorizontal = false) => drawBokehEffect(canvas, inputImage, backgroundImage, mask, foregroundThreshold, backgroundBlurAmount, edgeBlurAmount, flipHorizontal);
|
|
255
292
|
const evaluateInput = (inputImage) => {
|
|
256
293
|
const image = renderImageToOffScreenCanvas(inputImage, 'inputCanvas');
|
|
257
294
|
return image;
|
|
@@ -45,6 +45,7 @@ export declare const RENDER_EFFECT: {
|
|
|
45
45
|
export declare const RENDER_BACKEND: {
|
|
46
46
|
readonly WebGL: "webgl";
|
|
47
47
|
readonly WebGPU: "webgpu";
|
|
48
|
+
readonly Canvas2D: "canvas2d";
|
|
48
49
|
};
|
|
49
50
|
export type RenderBackend = ExtractValueTypes<typeof RENDER_BACKEND>;
|
|
50
51
|
export type RenderEffects = ExtractValueTypes<typeof RENDER_EFFECT>;
|
package/dist/common/constants.js
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Environment, type CompileOptions, type EnvironmentOptions, type LoadLiteRtOptions, type Tensor } from '@litertjs/core';
|
|
2
|
+
export { Tensor, type CompiledModel, type TensorDetails } from '@litertjs/core';
|
|
3
|
+
export interface RuntimeOptions extends LoadLiteRtOptions {
|
|
4
|
+
}
|
|
5
|
+
export interface ModelCompileOptions extends Omit<CompileOptions, 'environment'> {
|
|
6
|
+
}
|
|
7
|
+
export declare const createInferencer: () => {
|
|
8
|
+
init: (path: string, runtimeOptions?: RuntimeOptions) => Promise<void>;
|
|
9
|
+
prepareEnv: (options: EnvironmentOptions) => Promise<void>;
|
|
10
|
+
loadModel: (modelRef: string | URL | Uint8Array | ReadableStreamDefaultReader<Uint8Array>, options?: ModelCompileOptions) => Promise<void>;
|
|
11
|
+
release: () => void;
|
|
12
|
+
run(input: Record<string, Tensor>): Promise<Record<string, Tensor>>;
|
|
13
|
+
readonly initialized: boolean;
|
|
14
|
+
readonly environment: Environment | undefined;
|
|
15
|
+
readonly signatures: Record<string, import("@litertjs/core").SignatureRunner> | undefined;
|
|
16
|
+
readonly inputs: readonly import("@litertjs/core").TensorDetails[] | undefined;
|
|
17
|
+
readonly outputs: readonly import("@litertjs/core").TensorDetails[] | undefined;
|
|
18
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Environment, loadLiteRt, unloadLiteRt, } from '@litertjs/core';
|
|
2
|
+
export { Tensor } from '@litertjs/core';
|
|
3
|
+
import { assert } from '@pexip/utils';
|
|
4
|
+
export const createInferencer = () => {
|
|
5
|
+
let runtime;
|
|
6
|
+
let env;
|
|
7
|
+
let model;
|
|
8
|
+
let initialized = false;
|
|
9
|
+
const init = async (path, runtimeOptions) => {
|
|
10
|
+
runtime?.delete();
|
|
11
|
+
runtime = await loadLiteRt(path, runtimeOptions);
|
|
12
|
+
initialized = true;
|
|
13
|
+
};
|
|
14
|
+
const prepareEnv = async (options) => {
|
|
15
|
+
env?.delete();
|
|
16
|
+
env = await Environment.create(options);
|
|
17
|
+
};
|
|
18
|
+
const loadModel = async (modelRef, options) => {
|
|
19
|
+
assert(runtime, 'Runtime has not been populated!');
|
|
20
|
+
model?.delete();
|
|
21
|
+
model = await runtime.loadAndCompile(modelRef, {
|
|
22
|
+
environment: env,
|
|
23
|
+
...options,
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
const release = () => {
|
|
27
|
+
runtime?.delete();
|
|
28
|
+
model?.delete();
|
|
29
|
+
initialized = false;
|
|
30
|
+
runtime = undefined;
|
|
31
|
+
model = undefined;
|
|
32
|
+
env = undefined;
|
|
33
|
+
unloadLiteRt();
|
|
34
|
+
};
|
|
35
|
+
return {
|
|
36
|
+
init,
|
|
37
|
+
prepareEnv,
|
|
38
|
+
loadModel,
|
|
39
|
+
release,
|
|
40
|
+
run(input) {
|
|
41
|
+
assert(model, 'Model has not been compiled!');
|
|
42
|
+
return model?.run(input);
|
|
43
|
+
},
|
|
44
|
+
get initialized() {
|
|
45
|
+
return initialized;
|
|
46
|
+
},
|
|
47
|
+
get environment() {
|
|
48
|
+
return env;
|
|
49
|
+
},
|
|
50
|
+
get signatures() {
|
|
51
|
+
return model?.signatures;
|
|
52
|
+
},
|
|
53
|
+
get inputs() {
|
|
54
|
+
return model?.getInputDetails();
|
|
55
|
+
},
|
|
56
|
+
get outputs() {
|
|
57
|
+
return model?.getOutputDetails();
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
};
|