@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.
Files changed (201) hide show
  1. package/CHANGELOG.md +109 -0
  2. package/README.md +17 -10
  3. package/api-docs/README.mdx +69 -23
  4. package/api-docs/functions/assertMediaStreamVideoTrack.mdx +13 -0
  5. package/api-docs/functions/createBenchmark.mdx +17 -5
  6. package/api-docs/functions/createFrameCallbackRequest.mdx +8 -2
  7. package/api-docs/functions/createMediaStreamTrackInputProcessAdaptor.mdx +10 -0
  8. package/api-docs/functions/createReadWritableStreamFallbackInputProcessAdaptor.mdx +17 -0
  9. package/api-docs/functions/createReadWritableStreamInputProcessAdaptor.mdx +10 -0
  10. package/api-docs/functions/createVideoProcessor.mdx +3 -3
  11. package/api-docs/functions/createWindowedStats.mdx +23 -0
  12. package/api-docs/functions/getDefaultInputProcessAdaptor.mdx +17 -0
  13. package/api-docs/functions/iou.mdx +19 -0
  14. package/api-docs/functions/isMediaStreamVideoTrack.mdx +13 -0
  15. package/api-docs/functions/labelComponents.mdx +20 -0
  16. package/api-docs/functions/nearestPowerOfTwo.mdx +21 -0
  17. package/api-docs/functions/playVideo.mdx +16 -0
  18. package/api-docs/functions/scoreComponent.mdx +22 -0
  19. package/api-docs/functions/setVideoElementSrc.mdx +14 -0
  20. package/api-docs/interfaces/Analyzer.mdx +4 -0
  21. package/api-docs/interfaces/Benchmark.mdx +26 -2
  22. package/api-docs/interfaces/FrameTransformStats.mdx +9 -0
  23. package/api-docs/interfaces/Gain.mdx +4 -0
  24. package/api-docs/interfaces/ImageSegmenterOptions.mdx +0 -2
  25. package/api-docs/interfaces/InputProcessAdaptor.mdx +28 -0
  26. package/api-docs/interfaces/Process.mdx +8 -3
  27. package/api-docs/interfaces/ProcessEventMute.mdx +6 -0
  28. package/api-docs/interfaces/ProcessWorkerEventOpened.mdx +7 -0
  29. package/api-docs/interfaces/ProcessWorkerEventStats.mdx +6 -0
  30. package/api-docs/interfaces/ProcessWorkerEventUpdated.mdx +7 -0
  31. package/api-docs/interfaces/ProcessorEvent.mdx +1 -1
  32. package/api-docs/interfaces/ProcessorOptions.mdx +0 -1
  33. package/api-docs/interfaces/ProcessorProcessOptions.mdx +3 -1
  34. package/api-docs/interfaces/ProcessorUpdateOptions.mdx +15 -0
  35. package/api-docs/interfaces/ReadWritableStream.mdx +12 -0
  36. package/api-docs/interfaces/Renderer.mdx +2 -2
  37. package/api-docs/interfaces/RendererOptions.mdx +25 -12
  38. package/api-docs/interfaces/Segmenter.mdx +89 -10
  39. package/api-docs/interfaces/SegmenterOptions.mdx +0 -1
  40. package/api-docs/interfaces/SelectionOptions.mdx +15 -0
  41. package/api-docs/interfaces/Stats.mdx +11 -0
  42. package/api-docs/interfaces/VideoProcessor.mdx +49 -5
  43. package/api-docs/interfaces/Weights.mdx +12 -0
  44. package/api-docs/type-aliases/OptionalKeys.mdx +10 -0
  45. package/api-docs/type-aliases/ProcessEvents.mdx +1 -0
  46. package/api-docs/type-aliases/ProcessInputType.mdx +3 -1
  47. package/api-docs/type-aliases/ProcessWorkerEvents.mdx +6 -4
  48. package/api-docs/type-aliases/ProcessorWorkerEvents.mdx +1 -1
  49. package/api-docs/type-aliases/Struct.mdx +9 -0
  50. package/api-docs/variables/BACKGROUND_THRESHOLD.mdx +3 -0
  51. package/api-docs/variables/DOWN_SAMPLE_FACTOR.mdx +3 -0
  52. package/api-docs/variables/EDGE_BLUR_AMOUNT.mdx +1 -1
  53. package/api-docs/variables/EXCLUDE_BYSTANDERS.mdx +3 -0
  54. package/api-docs/variables/FOREGROUND_THRESHOLD.mdx +1 -1
  55. package/api-docs/variables/FRAME_RATE.mdx +1 -1
  56. package/api-docs/variables/ImageFetchError.mdx +3 -0
  57. package/api-docs/variables/LIGHT_WRAP_BLUR_AMOUNT.mdx +3 -0
  58. package/api-docs/variables/LIGHT_WRAP_EDGE_BAND.mdx +3 -0
  59. package/api-docs/variables/LIGHT_WRAP_INTENSITY.mdx +3 -0
  60. package/api-docs/variables/LIGHT_WRAP_TIGHTNESS.mdx +3 -0
  61. package/api-docs/variables/MASK_COMBINE_RATIO.mdx +1 -1
  62. package/api-docs/variables/MORPH_DILATE_RADIUS_PX.mdx +3 -0
  63. package/api-docs/variables/MORPH_ERODE_RADIUS_PX.mdx +3 -0
  64. package/api-docs/variables/MORPH_PASS.mdx +3 -0
  65. package/api-docs/variables/MediaStreamTrackTransferable.mdx +5 -0
  66. package/api-docs/variables/PERSON_CENTER.mdx +3 -0
  67. package/api-docs/variables/RENDERER_OPTIONS_STRUCT.mdx +3 -0
  68. package/api-docs/variables/SIGMA_RANGE.mdx +3 -0
  69. package/api-docs/variables/SIGMA_SPACE.mdx +3 -0
  70. package/api-docs/variables/STRONG_EDGE_BLUR_AMOUNT.mdx +3 -0
  71. package/api-docs/variables/cloneImageRecord.mdx +19 -0
  72. package/api-docs/variables/compareImageRecords.mdx +18 -0
  73. package/api-docs/variables/createRemoteImageBitmap.mdx +1 -1
  74. package/api-docs/variables/getCanvasRenderingContext2D.mdx +14 -0
  75. package/api-docs/variables/getImageSize.mdx +13 -0
  76. package/api-docs/variables/isRenderOptionsKey.mdx +13 -0
  77. package/api-docs/variables/isValidGpuAPI.mdx +13 -0
  78. package/dist/common/backends/webgl/binaryMask.d.ts +8 -0
  79. package/dist/common/backends/webgl/binaryMask.js +95 -0
  80. package/dist/common/backends/webgl/blender.d.ts +2 -1
  81. package/dist/common/backends/webgl/blender.js +18 -15
  82. package/dist/common/backends/webgl/blur.d.ts +1 -1
  83. package/dist/common/backends/webgl/blur.js +23 -16
  84. package/dist/common/backends/webgl/canvas.js +9 -4
  85. package/dist/common/backends/webgl/diffAbsMask.d.ts +10 -0
  86. package/dist/common/backends/webgl/diffAbsMask.js +103 -0
  87. package/dist/common/backends/webgl/geodesicReconstruction.d.ts +13 -0
  88. package/dist/common/backends/webgl/geodesicReconstruction.js +176 -0
  89. package/dist/common/backends/webgl/jointBilateralFilter.d.ts +12 -0
  90. package/dist/common/backends/webgl/jointBilateralFilter.js +108 -0
  91. package/dist/common/backends/webgl/morphErode.d.ts +13 -0
  92. package/dist/common/backends/webgl/morphErode.js +121 -0
  93. package/dist/common/backends/webgl/renderer.js +194 -44
  94. package/dist/common/backends/webgl/replace.d.ts +17 -0
  95. package/dist/common/backends/webgl/replace.js +126 -0
  96. package/dist/common/backends/webgl/selectComponent.d.ts +15 -0
  97. package/dist/common/backends/webgl/selectComponent.js +102 -0
  98. package/dist/common/backends/webgl/smoothingMask.d.ts +3 -3
  99. package/dist/common/backends/webgl/smoothingMask.js +22 -21
  100. package/dist/common/backends/webgl/tentBlur.d.ts +9 -0
  101. package/dist/common/backends/webgl/tentBlur.js +118 -0
  102. package/dist/common/backends/webgl/texture.d.ts +2 -3
  103. package/dist/common/backends/webgl/texture.js +21 -19
  104. package/dist/common/backends/webgl/textureToTexture.d.ts +1 -1
  105. package/dist/common/backends/webgl/textureToTexture.js +19 -16
  106. package/dist/common/backends/webgl/webglUtils.d.ts +7 -1
  107. package/dist/common/backends/webgl/webglUtils.js +72 -10
  108. package/dist/common/backends/webgpu/blender.d.ts +6 -1
  109. package/dist/common/backends/webgpu/blender.js +96 -51
  110. package/dist/common/backends/webgpu/constants.js +1 -1
  111. package/dist/common/backends/webgpu/dualFilterBlur.d.ts +2 -3
  112. package/dist/common/backends/webgpu/dualFilterBlur.js +146 -140
  113. package/dist/common/backends/webgpu/jointBilateralFilter.d.ts +7 -0
  114. package/dist/common/backends/webgpu/jointBilateralFilter.js +162 -0
  115. package/dist/common/backends/webgpu/renderer.js +205 -43
  116. package/dist/common/backends/webgpu/replace.d.ts +11 -0
  117. package/dist/common/backends/webgpu/replace.js +175 -0
  118. package/dist/common/backends/webgpu/resize.d.ts +4 -0
  119. package/dist/common/backends/webgpu/resize.js +76 -0
  120. package/dist/common/backends/webgpu/smoothingMask.d.ts +2 -2
  121. package/dist/common/backends/webgpu/smoothingMask.js +74 -40
  122. package/dist/common/backends/webgpu/tentBlur.d.ts +5 -0
  123. package/dist/common/backends/webgpu/tentBlur.js +131 -0
  124. package/dist/common/backends/webgpu/texture.d.ts +2 -3
  125. package/dist/common/backends/webgpu/texture.js +29 -20
  126. package/dist/common/backends/webgpu/textureToTexture.d.ts +1 -1
  127. package/dist/common/backends/webgpu/textureToTexture.js +28 -15
  128. package/dist/common/backends/webgpu/types.d.ts +2 -0
  129. package/dist/common/backends/webgpu/webgpuUtils.d.ts +24 -4
  130. package/dist/common/backends/webgpu/webgpuUtils.js +60 -11
  131. package/dist/common/constants.d.ts +22 -5
  132. package/dist/common/constants.js +23 -5
  133. package/dist/common/index.d.ts +2 -0
  134. package/dist/common/index.js +2 -0
  135. package/dist/common/tsconfig.tsbuildinfo +1 -1
  136. package/dist/common/typeGuards.d.ts +2 -0
  137. package/dist/common/typeGuards.js +7 -0
  138. package/dist/common/types/media.d.ts +5 -1
  139. package/dist/common/types/messageEvents.d.ts +31 -4
  140. package/dist/common/types/processor.d.ts +6 -4
  141. package/dist/common/types/render.d.ts +147 -15
  142. package/dist/common/types/render.js +23 -1
  143. package/dist/common/types/segmentation.d.ts +0 -12
  144. package/dist/common/types/utils.d.ts +21 -0
  145. package/dist/common/utils.d.ts +29 -2
  146. package/dist/common/utils.js +206 -10
  147. package/dist/main/audio.js +3 -3
  148. package/dist/main/benchUtils.d.ts +35 -3
  149. package/dist/main/benchUtils.js +149 -15
  150. package/dist/main/index.d.ts +1 -1
  151. package/dist/main/index.js +1 -1
  152. package/dist/main/tsconfig.tsbuildinfo +1 -1
  153. package/dist/main/types.d.ts +0 -6
  154. package/dist/main/utils.d.ts +4 -0
  155. package/dist/main/utils.js +31 -0
  156. package/dist/main/video/constants.d.ts +1 -4
  157. package/dist/main/video/constants.js +1 -5
  158. package/dist/main/video/index.d.ts +3 -4
  159. package/dist/main/video/index.js +3 -4
  160. package/dist/main/video/inputProcessAdaptor.d.ts +30 -0
  161. package/dist/main/video/inputProcessAdaptor.js +198 -0
  162. package/dist/main/video/segmenter.d.ts +35 -0
  163. package/dist/main/video/{segmenters/mediapipe.js → segmenter.js} +93 -53
  164. package/dist/main/video/typeGuards.d.ts +2 -0
  165. package/dist/main/video/typeGuards.js +8 -1
  166. package/dist/main/video/types.d.ts +36 -16
  167. package/dist/main/video/utils.d.ts +5 -4
  168. package/dist/main/video/utils.js +49 -44
  169. package/dist/main/video/video.d.ts +29 -7
  170. package/dist/main/video/video.js +323 -28
  171. package/dist/workers/mediaWorker.js +35 -162
  172. package/dist/workers/tsconfig.tsbuildinfo +1 -1
  173. package/dist/worklets/tsconfig.tsbuildinfo +1 -1
  174. package/package.json +13 -12
  175. package/api-docs/functions/createCanvasTransform.mdx +0 -14
  176. package/api-docs/functions/createVideoTrackProcessor.mdx +0 -9
  177. package/api-docs/functions/createVideoTrackProcessorWithFallback.mdx +0 -15
  178. package/api-docs/interfaces/Detector.mdx +0 -81
  179. package/api-docs/interfaces/SegmentationParams.mdx +0 -18
  180. package/api-docs/interfaces/Transform.mdx +0 -62
  181. package/api-docs/type-aliases/ProcessVideoTrack.mdx +0 -15
  182. package/api-docs/type-aliases/SegmentationTransform.mdx +0 -3
  183. package/dist/common/backends/webgl/shaders/blendFragmentShader.d.ts +0 -1
  184. package/dist/common/backends/webgl/shaders/blendFragmentShader.js +0 -21
  185. package/dist/common/backends/webgl/shaders/dualFilterBlurFragmentShader.d.ts +0 -1
  186. package/dist/common/backends/webgl/shaders/dualFilterBlurFragmentShader.js +0 -45
  187. package/dist/common/backends/webgl/shaders/smoothingMaskFragmentShader.d.ts +0 -1
  188. package/dist/common/backends/webgl/shaders/smoothingMaskFragmentShader.js +0 -57
  189. package/dist/common/backends/webgl/shaders/textureFragmentShader.d.ts +0 -1
  190. package/dist/common/backends/webgl/shaders/textureFragmentShader.js +0 -16
  191. package/dist/common/backends/webgl/shaders/vertexShader.d.ts +0 -1
  192. package/dist/common/backends/webgl/shaders/vertexShader.js +0 -19
  193. package/dist/main/transformer.d.ts +0 -1
  194. package/dist/main/transformer.js +0 -4
  195. package/dist/main/video/canvasTransform.d.ts +0 -17
  196. package/dist/main/video/canvasTransform.js +0 -253
  197. package/dist/main/video/segmenters/mediapipe.d.ts +0 -22
  198. package/dist/main/video/transformer.d.ts +0 -9
  199. package/dist/main/video/transformer.js +0 -68
  200. package/dist/main/video/videoStreamTrackProcessor.d.ts +0 -20
  201. package/dist/main/video/videoStreamTrackProcessor.js +0 -86
@@ -1,43 +1,95 @@
1
- import { PROCESSING_WIDTH, PROCESSING_HEIGHT, } from '../../constants';
1
+ import { assert } from '@pexip/utils';
2
+ import { PROCESSING_WIDTH, PROCESSING_HEIGHT, STRONG_EDGE_BLUR_AMOUNT, } from '../../constants';
2
3
  import { createObjectUpdater, createLazyProps } from '../../utils';
3
4
  import { createTextureRenderer } from './texture';
4
5
  import { createBlendRenderer } from './blender';
5
- import { getGPUDevice, hasBGRA8unormStorage } from './webgpuUtils';
6
+ import { getGPUDevice, hasR16FloatTextureStorage, supportsTextureFormat, hasF16, } from './webgpuUtils';
6
7
  import { createBlurRenderer } from './dualFilterBlur';
7
8
  import { createSmoothingMaskRenderer } from './smoothingMask';
8
9
  import { createTextureToTextureRenderer } from './textureToTexture';
10
+ import { createJointBilateralFilterRenderer } from './jointBilateralFilter';
11
+ import { createTextureResizeRenderer } from './resize';
12
+ import { createTentBlurRenderer } from './tentBlur';
13
+ import { createReplaceRenderer } from './replace';
9
14
  export const createRenderer = (adapter, renderEventHandlers, canvas = new OffscreenCanvas(PROCESSING_WIDTH, PROCESSING_HEIGHT)) => {
15
+ const halfWidth = Math.trunc(canvas.width / 2);
16
+ const halfHeight = Math.trunc(canvas.height / 2);
17
+ const quarterWidth = Math.trunc(canvas.width / 4);
18
+ const quarterHeight = Math.trunc(canvas.height / 4);
19
+ const fullChannelTextureFormat = 'rgba8unorm';
10
20
  const rendererCreator = {
21
+ edgeBlurRenderer: () => {
22
+ assert(props.device);
23
+ return createTentBlurRenderer(props.device, isContextLost, canvas.width, canvas.height, props.maskFormat, props.supportF16);
24
+ },
11
25
  videoFrameRenderer: () => {
12
- if (!props.device) {
13
- throw new Error('No device.');
14
- }
15
- return createTextureRenderer(canvas, props.device, isContextLost);
26
+ assert(props.device);
27
+ return createTextureRenderer(props.device, isContextLost, fullChannelTextureFormat, canvas.width, canvas.height, 'linear');
28
+ },
29
+ maskRenderer: () => {
30
+ assert(props.device);
31
+ return createTextureRenderer(props.device, isContextLost, props.maskFormat, canvas.width, canvas.height, 'nearest');
32
+ },
33
+ maskDownsampler2: () => {
34
+ assert(props.device);
35
+ return createTextureResizeRenderer(props.device, isContextLost, halfWidth, halfHeight, props.maskFormat, 'nearest');
36
+ },
37
+ maskDownsampler4: () => {
38
+ assert(props.device);
39
+ return createTextureResizeRenderer(props.device, isContextLost, quarterWidth, quarterHeight, props.maskFormat, 'nearest');
40
+ },
41
+ maskUpsampler: () => {
42
+ assert(props.device);
43
+ return createTextureResizeRenderer(props.device, isContextLost, canvas.width, canvas.height, props.maskFormat, 'nearest');
44
+ },
45
+ frameDownsampler2: () => {
46
+ assert(props.device);
47
+ return createTextureResizeRenderer(props.device, isContextLost, halfWidth, halfHeight, fullChannelTextureFormat, 'linear');
48
+ },
49
+ frameDownsampler4: () => {
50
+ assert(props.device);
51
+ return createTextureResizeRenderer(props.device, isContextLost, quarterWidth, quarterHeight, fullChannelTextureFormat, 'linear');
16
52
  },
17
53
  backgroundImageRenderer: () => {
18
- if (!props.device) {
19
- throw new Error('No device.');
20
- }
54
+ assert(props.device);
55
+ return createTextureRenderer(props.device, isContextLost, fullChannelTextureFormat, canvas.width, canvas.height, 'linear');
56
+ },
57
+ blendRenderer: () => {
58
+ assert(props.device);
21
59
  return createBlendRenderer(canvas, props.device, props.presentationFormat, isContextLost);
22
60
  },
23
61
  blurRenderer: () => {
24
- if (!props.device) {
25
- throw new Error('No device.');
26
- }
27
- return createBlurRenderer(canvas, props.device, props.presentationFormat, isContextLost);
62
+ assert(props.device);
63
+ return createBlurRenderer(props.device, canvas.width, canvas.height, fullChannelTextureFormat, isContextLost);
28
64
  },
29
- maskSmoothingRenderer: () => {
30
- if (!props.device) {
31
- throw new Error('No device.');
32
- }
33
- return createSmoothingMaskRenderer(canvas, props.device, isContextLost);
65
+ jointBilateralFilterRenderer2: () => {
66
+ assert(props.device);
67
+ return createJointBilateralFilterRenderer(canvas, props.device, isContextLost, halfWidth, halfHeight, props.maskFormat);
68
+ },
69
+ jointBilateralFilterRenderer4: () => {
70
+ assert(props.device);
71
+ return createJointBilateralFilterRenderer(canvas, props.device, isContextLost, quarterWidth, quarterHeight, props.maskFormat);
72
+ },
73
+ maskSmoothingRenderer2: () => {
74
+ assert(props.device);
75
+ return createSmoothingMaskRenderer(props.device, isContextLost, props.maskFormat, halfWidth, halfHeight, props.supportF16);
76
+ },
77
+ maskSmoothingRenderer4: () => {
78
+ assert(props.device);
79
+ return createSmoothingMaskRenderer(props.device, isContextLost, props.maskFormat, quarterWidth, quarterHeight, props.supportF16);
34
80
  },
35
81
  prevMaskRenderer: () => {
36
- if (!props.device) {
37
- throw new Error('No device.');
38
- }
82
+ assert(props.device);
39
83
  return createTextureToTextureRenderer(props.device, isContextLost);
40
84
  },
85
+ strongMaskBlurRenderer: () => {
86
+ assert(props.device);
87
+ return createBlurRenderer(props.device, canvas.width, canvas.height, props.maskFormat, isContextLost);
88
+ },
89
+ replaceRenderer: () => {
90
+ assert(props.device);
91
+ return createReplaceRenderer(canvas, props.device, props.presentationFormat, isContextLost);
92
+ },
41
93
  release: (states) => () => {
42
94
  for (const key of Object.keys(states)) {
43
95
  key !== 'release' && states[key]?.release();
@@ -48,33 +100,80 @@ export const createRenderer = (adapter, renderEventHandlers, canvas = new Offscr
48
100
  const props = {
49
101
  canvas,
50
102
  presentationFormat: 'rgba8unorm',
103
+ supportR16FloatTextureStorage: false,
104
+ maskFormat: 'rgba16float',
105
+ supportF16: false,
51
106
  backend: 'webgpu',
52
107
  deviceLost: false,
53
108
  get videoFrameRenderer() {
54
109
  return lazyProps.videoFrameRenderer;
55
110
  },
111
+ get edgeBlurRenderer() {
112
+ return lazyProps.edgeBlurRenderer;
113
+ },
114
+ get maskRenderer() {
115
+ return lazyProps.maskRenderer;
116
+ },
117
+ get maskDownsampler2() {
118
+ return lazyProps.maskDownsampler2;
119
+ },
120
+ get maskDownsampler4() {
121
+ return lazyProps.maskDownsampler4;
122
+ },
123
+ get maskUpsampler() {
124
+ return lazyProps.maskUpsampler;
125
+ },
126
+ get frameDownsampler2() {
127
+ return lazyProps.frameDownsampler2;
128
+ },
129
+ get frameDownsampler4() {
130
+ return lazyProps.frameDownsampler4;
131
+ },
56
132
  get blendRenderer() {
57
- return lazyProps.backgroundImageRenderer;
133
+ return lazyProps.blendRenderer;
58
134
  },
59
135
  get blurRenderer() {
60
136
  return lazyProps.blurRenderer;
61
137
  },
62
- get maskSmoothingRenderer() {
63
- return lazyProps.maskSmoothingRenderer;
138
+ get backgroundImageRenderer() {
139
+ return lazyProps.backgroundImageRenderer;
140
+ },
141
+ get maskSmoothingRenderer2() {
142
+ return lazyProps.maskSmoothingRenderer2;
143
+ },
144
+ get maskSmoothingRenderer4() {
145
+ return lazyProps.maskSmoothingRenderer4;
64
146
  },
65
147
  get prevMaskRenderer() {
66
148
  return lazyProps.prevMaskRenderer;
67
149
  },
150
+ get jointBilateralFilterRenderer2() {
151
+ return lazyProps.jointBilateralFilterRenderer2;
152
+ },
153
+ get jointBilateralFilterRenderer4() {
154
+ return lazyProps.jointBilateralFilterRenderer4;
155
+ },
156
+ get strongMaskBlurRenderer() {
157
+ return lazyProps.strongMaskBlurRenderer;
158
+ },
159
+ get replaceRenderer() {
160
+ return lazyProps.replaceRenderer;
161
+ },
68
162
  };
69
163
  const updateProps = createObjectUpdater(props);
70
164
  const init = async () => {
71
165
  if (!navigator.gpu) {
72
166
  throw Error('WebGPU not supported.');
73
167
  }
74
- props.presentationFormat = hasBGRA8unormStorage(adapter)
75
- ? navigator.gpu.getPreferredCanvasFormat()
76
- : 'rgba8unorm';
168
+ props.presentationFormat = navigator.gpu.getPreferredCanvasFormat();
77
169
  props.device = await getGPUDevice(adapter);
170
+ props.supportR16FloatTextureStorage =
171
+ hasR16FloatTextureStorage(adapter) &&
172
+ supportsTextureFormat(props.device, 'r16float');
173
+ if (props.supportR16FloatTextureStorage) {
174
+ props.maskFormat = 'r16float';
175
+ }
176
+ props.supportF16 = hasF16(adapter);
78
177
  props.device.lost.then(info => {
79
178
  if (info.reason === 'destroyed') {
80
179
  return;
@@ -88,34 +187,97 @@ export const createRenderer = (adapter, renderEventHandlers, canvas = new Offscr
88
187
  if (options) {
89
188
  updateProps(options);
90
189
  }
91
- const frameTexture = props.videoFrameRenderer.render(frame);
92
- const maskTexture = props.videoFrameRenderer.render({
93
- frame: mask,
94
- width: frame.width,
95
- height: frame.height,
96
- timestamp: frame.timestamp,
190
+ assert(props.device);
191
+ const encoder = props.device.createCommandEncoder({
192
+ label: 'Renderer Encoder',
97
193
  });
98
- const smoothedMaskTexture = props.maskSmoothingRenderer.render(maskTexture, props.prevMask, options);
194
+ const frameUploadPass = encoder.beginComputePass({
195
+ label: 'frame upload pass',
196
+ });
197
+ const frameTexture = props.videoFrameRenderer.render(frame, frameUploadPass);
198
+ const maskTexture = props.maskRenderer.render(mask, frameUploadPass);
199
+ frameUploadPass.end();
200
+ let smoothedMask;
201
+ let compositeInputMask;
202
+ switch (options.downSampleFactor) {
203
+ case 4: {
204
+ const downsamplePass = encoder.beginComputePass({
205
+ label: 'downsample pass',
206
+ });
207
+ const maskLowRes = props.maskDownsampler4.render(downsamplePass, maskTexture);
208
+ const frameLowRes = props.frameDownsampler4.render(downsamplePass, frameTexture);
209
+ downsamplePass.end();
210
+ const filteredMask = props.jointBilateralFilterRenderer4.render(encoder, maskLowRes, frameLowRes, options);
211
+ smoothedMask = props.maskSmoothingRenderer4.render(encoder, filteredMask, props.prevMask4 ?? filteredMask, options);
212
+ const upsamplePass = encoder.beginComputePass({
213
+ label: 'upsample pass',
214
+ });
215
+ const upsampledMask = props.maskUpsampler.render(upsamplePass, smoothedMask);
216
+ upsamplePass.end();
217
+ compositeInputMask = props.strongMaskBlurRenderer.render(encoder, upsampledMask, STRONG_EDGE_BLUR_AMOUNT);
218
+ break;
219
+ }
220
+ default: {
221
+ const downsamplePass = encoder.beginComputePass({
222
+ label: 'downsample pass',
223
+ });
224
+ const maskLowRes = props.maskDownsampler2.render(downsamplePass, maskTexture);
225
+ const frameLowRes = props.frameDownsampler2.render(downsamplePass, frameTexture);
226
+ downsamplePass.end();
227
+ const filteredMask = props.jointBilateralFilterRenderer2.render(encoder, maskLowRes, frameLowRes, options);
228
+ smoothedMask = props.maskSmoothingRenderer2.render(encoder, filteredMask, props.prevMask2 ?? filteredMask, options);
229
+ const upsamplePass = encoder.beginComputePass({
230
+ label: 'upsample pass',
231
+ });
232
+ const upsampledMask = props.maskUpsampler.render(upsamplePass, smoothedMask);
233
+ upsamplePass.end();
234
+ compositeInputMask = props.edgeBlurRenderer.render(encoder, upsampledMask, options);
235
+ break;
236
+ }
237
+ }
99
238
  if (options.effects === 'blur') {
100
- const blurredTexture = props.blurRenderer.render(frame, options.backgroundBlurAmount);
101
- props.blendRenderer.render(frameTexture, blurredTexture, smoothedMaskTexture);
239
+ const blurredTexture = props.blurRenderer.render(encoder, frameTexture, options.backgroundBlurAmount);
240
+ props.blendRenderer.render(encoder, {
241
+ foreground: frameTexture,
242
+ background: blurredTexture,
243
+ mask: compositeInputMask,
244
+ }, options);
102
245
  }
103
246
  else if (options.effects === 'overlay') {
104
247
  if (options.backgroundImage) {
105
- const bgTexture = props.videoFrameRenderer.render({
106
- frame: options.backgroundImage,
107
- width: options.backgroundImage.width,
108
- height: options.backgroundImage.height,
109
- timestamp: frame.timestamp,
248
+ const bgImgUploadPass = encoder.beginComputePass({
249
+ label: 'background image upload pass',
110
250
  });
111
- props.blendRenderer.render(frameTexture, bgTexture, smoothedMaskTexture);
251
+ const bgTexture = props.backgroundImageRenderer.render(options.backgroundImage, bgImgUploadPass);
252
+ bgImgUploadPass.end();
253
+ const blurredBackgroundImageTexture = props.blurRenderer.render(encoder, bgTexture, options.backgroundBlurAmount);
254
+ props.replaceRenderer.render(encoder, {
255
+ foreground: frameTexture,
256
+ background: bgTexture,
257
+ mask: compositeInputMask,
258
+ blurBackground: blurredBackgroundImageTexture,
259
+ timestamp: frame.timestamp,
260
+ }, options);
112
261
  }
113
262
  }
114
- props.prevMask = props.prevMaskRenderer.render(smoothedMaskTexture);
263
+ switch (options.downSampleFactor) {
264
+ case 4:
265
+ props.prevMask4 = props.prevMaskRenderer.render(encoder, smoothedMask);
266
+ break;
267
+ default:
268
+ props.prevMask2 = props.prevMaskRenderer.render(encoder, smoothedMask);
269
+ break;
270
+ }
271
+ props.device.queue.submit([encoder.finish()]);
115
272
  };
116
273
  const release = () => {
117
274
  lazyProps.release();
118
275
  props.deviceLost = false;
276
+ props.prevMask2 = undefined;
277
+ props.prevMask4 = undefined;
278
+ props.presentationFormat = 'rgba8unorm';
279
+ props.supportR16FloatTextureStorage = false;
280
+ props.maskFormat = 'rgba16float';
119
281
  lazyProps = createLazyProps(rendererCreator);
120
282
  };
121
283
  const isContextLost = () => props.deviceLost;
@@ -0,0 +1,11 @@
1
+ import type { RendererOptions } from '../../types/render';
2
+ export declare const createReplaceRenderer: (processor: OffscreenCanvas, device: GPUDevice, presentationFormat: GPUTextureFormat, isContextLost: () => boolean) => {
3
+ render: (encoder: GPUCommandEncoder, { foreground, background, blurBackground, mask, timestamp, }: {
4
+ foreground: GPUTexture;
5
+ background: GPUTexture;
6
+ blurBackground: GPUTexture;
7
+ mask: GPUTexture;
8
+ timestamp: number;
9
+ }, options: RendererOptions) => void;
10
+ release: () => void;
11
+ };
@@ -0,0 +1,175 @@
1
+ import { makeShaderDataDefinitions, makeStructuredView } from 'webgpu-utils';
2
+ import { assert } from '@pexip/utils';
3
+ import { RENDERING_EVENTS } from '../../constants';
4
+ import shaderCode from './shaders/replace.wgsl';
5
+ export const createReplaceRenderer = (processor, device, presentationFormat, isContextLost) => {
6
+ const context = processor.getContext('webgpu');
7
+ if (!context) {
8
+ throw Error('WebGPU not supported on the canvas.');
9
+ }
10
+ context.configure({
11
+ device,
12
+ format: presentationFormat,
13
+ alphaMode: 'premultiplied',
14
+ });
15
+ const defs = makeShaderDataDefinitions(shaderCode);
16
+ // @ts-expect-error This is what we get from the API
17
+ const uniformsSparams = makeStructuredView(defs.uniforms.sparams);
18
+ // @ts-expect-error This is what we get from the API
19
+ const uniformsDparams = makeStructuredView(defs.uniforms.dparams);
20
+ // Cache shader module and pipeline at init time
21
+ const shaderModule = device.createShaderModule({
22
+ label: 'replace shader',
23
+ code: shaderCode,
24
+ });
25
+ const pipeline = device.createRenderPipeline({
26
+ label: 'replace pipeline',
27
+ layout: 'auto',
28
+ vertex: {
29
+ module: shaderModule,
30
+ },
31
+ fragment: {
32
+ module: shaderModule,
33
+ targets: [{ format: presentationFormat }],
34
+ },
35
+ });
36
+ // Cache sampler at init time
37
+ const linearSampler = device.createSampler({
38
+ addressModeU: 'clamp-to-edge',
39
+ addressModeV: 'clamp-to-edge',
40
+ magFilter: 'linear',
41
+ minFilter: 'linear',
42
+ });
43
+ const nearestSampler = device.createSampler({
44
+ addressModeU: 'clamp-to-edge',
45
+ addressModeV: 'clamp-to-edge',
46
+ magFilter: 'nearest',
47
+ minFilter: 'nearest',
48
+ });
49
+ const sparamsBuffer = device.createBuffer({
50
+ size: uniformsSparams.arrayBuffer.byteLength,
51
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
52
+ });
53
+ const dparamsBuffer = device.createBuffer({
54
+ size: uniformsDparams.arrayBuffer.byteLength,
55
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
56
+ });
57
+ const updateSparams = (options) => {
58
+ uniformsSparams.set({
59
+ background: options.backgroundThreshold,
60
+ foreground: options.foregroundThreshold,
61
+ lightWrap: options.lightWrapIntensity,
62
+ lightWrapTightness: options.lightWrapTightness,
63
+ lightWrapEdgeBand: options.lightWrapEdgeBand,
64
+ width: options.width,
65
+ height: options.height,
66
+ });
67
+ device.queue.writeBuffer(sparamsBuffer, 0, uniformsSparams.arrayBuffer);
68
+ };
69
+ const updateDparams = ({ time, parity }) => {
70
+ uniformsDparams.set({ time, parity });
71
+ device.queue.writeBuffer(dparamsBuffer, 0, uniformsDparams.arrayBuffer);
72
+ };
73
+ const colorAttachment0 = {
74
+ loadOp: 'load',
75
+ storeOp: 'store',
76
+ };
77
+ const renderPassDescriptor = {
78
+ label: 'replace renderPass',
79
+ colorAttachments: [colorAttachment0],
80
+ };
81
+ const bindGroup0 = device.createBindGroup({
82
+ layout: pipeline.getBindGroupLayout(0),
83
+ entries: [
84
+ { binding: 0, resource: linearSampler },
85
+ { binding: 1, resource: nearestSampler },
86
+ { binding: 2, resource: { buffer: sparamsBuffer } },
87
+ { binding: 3, resource: { buffer: dparamsBuffer } },
88
+ ],
89
+ });
90
+ let cachedBindGroup1 = null;
91
+ let cachedMask = null;
92
+ let cachedBackground = null;
93
+ let cachedForeground = null;
94
+ let cachedBlurBackground = null;
95
+ let cachedBackgroundThreshold = -1;
96
+ let cachedForegroundThreshold = -1;
97
+ let cachedLightWrapIntensity = -1;
98
+ let cachedLightWrapTightness = -1;
99
+ let cachedLightWrapEdgeBand = -1;
100
+ let cachedWidth = -1;
101
+ let cachedHeight = -1;
102
+ let parity = -1;
103
+ return {
104
+ render: (encoder, { foreground, background, blurBackground, mask, timestamp, }, options) => {
105
+ assert(!isContextLost(), RENDERING_EVENTS.ContextLost);
106
+ colorAttachment0.view = context.getCurrentTexture().createView();
107
+ if (!(cachedBackground === background &&
108
+ cachedForeground === foreground &&
109
+ cachedMask === mask &&
110
+ cachedBlurBackground === blurBackground)) {
111
+ cachedBindGroup1 = device.createBindGroup({
112
+ layout: pipeline.getBindGroupLayout(1),
113
+ entries: [
114
+ { binding: 0, resource: foreground.createView() },
115
+ { binding: 1, resource: background.createView() },
116
+ { binding: 2, resource: mask.createView() },
117
+ { binding: 3, resource: blurBackground.createView() },
118
+ ],
119
+ });
120
+ cachedForeground = foreground;
121
+ cachedBackground = background;
122
+ cachedMask = mask;
123
+ cachedBlurBackground = blurBackground;
124
+ }
125
+ if (!(cachedBackgroundThreshold === options.backgroundThreshold &&
126
+ cachedForegroundThreshold === options.foregroundThreshold &&
127
+ cachedLightWrapIntensity === options.lightWrapIntensity &&
128
+ cachedLightWrapTightness === options.lightWrapTightness &&
129
+ cachedLightWrapEdgeBand === options.lightWrapEdgeBand &&
130
+ cachedWidth === background.width &&
131
+ cachedHeight === background.height)) {
132
+ updateSparams({
133
+ backgroundThreshold: options.backgroundThreshold,
134
+ foregroundThreshold: options.foregroundThreshold,
135
+ lightWrapIntensity: options.lightWrapIntensity,
136
+ lightWrapTightness: options.lightWrapTightness,
137
+ lightWrapEdgeBand: options.lightWrapEdgeBand,
138
+ width: background.width,
139
+ height: background.height,
140
+ });
141
+ cachedBackgroundThreshold = options.backgroundThreshold;
142
+ cachedForegroundThreshold = options.foregroundThreshold;
143
+ cachedLightWrapIntensity = options.lightWrapIntensity;
144
+ cachedLightWrapTightness = options.lightWrapTightness;
145
+ cachedLightWrapEdgeBand = options.lightWrapEdgeBand;
146
+ cachedWidth = background.width;
147
+ cachedHeight = background.height;
148
+ }
149
+ parity = -parity;
150
+ updateDparams({ time: (timestamp * 1e-6) % 1024.0, parity });
151
+ const pass = encoder.beginRenderPass(renderPassDescriptor);
152
+ pass.setPipeline(pipeline);
153
+ pass.setBindGroup(0, bindGroup0);
154
+ pass.setBindGroup(1, cachedBindGroup1);
155
+ pass.draw(6); // call our vertex shader 6 times
156
+ pass.end();
157
+ },
158
+ release: () => {
159
+ sparamsBuffer.destroy();
160
+ dparamsBuffer.destroy();
161
+ cachedBackgroundThreshold = -1;
162
+ cachedForegroundThreshold = -1;
163
+ cachedLightWrapIntensity = -1;
164
+ cachedLightWrapTightness = -1;
165
+ cachedLightWrapEdgeBand = -1;
166
+ cachedWidth = -1;
167
+ cachedHeight = -1;
168
+ cachedBlurBackground = null;
169
+ cachedBackground = null;
170
+ cachedForeground = null;
171
+ cachedMask = null;
172
+ cachedBindGroup1 = null;
173
+ },
174
+ };
175
+ };
@@ -0,0 +1,4 @@
1
+ export declare const createTextureResizeRenderer: (device: GPUDevice, isContextLost: () => boolean, width: number, height: number, textureFormat: GPUTextureFormat, filterMode: GPUFilterMode) => {
2
+ render(pass: GPUComputePassEncoder, texture: GPUTexture): GPUTexture;
3
+ release(): void;
4
+ };
@@ -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: (processor: OffscreenCanvas, device: GPUDevice, isContextLost: () => boolean) => {
3
- render: (currMask: GPUTexture, prevMask: GPUTexture | undefined, options: RendererOptions) => 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
  };