@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,192 +1,194 @@
1
1
  import { assert } from '@pexip/utils';
2
2
  import { RENDERING_EVENTS } from '../../constants';
3
- import blurExample from './shaders/dualBlurShader.wgsl';
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 = (processor, device, presentationFormat, isContextLost) => {
8
- const context = processor.getContext('webgpu');
9
- if (!context) {
10
- throw Error('WebGPU not supported on the canvas.');
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 shaderModule = device.createShaderModule({ code: blurExample });
41
- const pipeline = device.createRenderPipeline({
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
- entryPoint: 'vert_main',
47
- buffers: [],
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
- entryPoint: 'frag_main',
52
- targets: [{ format: presentationFormat }],
27
+ targets: [{ format: textureFormat }],
28
+ entryPoint: 'upsample_fs',
53
29
  },
54
- primitive: { topology: 'triangle-list' },
55
30
  });
56
- // --- State for persistent textures and buffers ---
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
- // Helper to (re)generate textures if needed
61
- function ensureBlurTextures(pass, width, height) {
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 !== width ||
66
- lastHeight !== height) {
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(width / 2 ** i), 1);
74
- const h = Math.max(Math.trunc(height / 2 ** i), 1);
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: createTexture(w, h),
78
+ texture,
79
+ view: texture.createView(),
77
80
  width: w,
78
81
  height: h,
79
82
  });
80
83
  }
81
- lastWidth = width;
82
- lastHeight = height;
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
- const bindGroup = device.createBindGroup({
105
- layout: pipeline.getBindGroupLayout(0),
90
+ downsampleBindGroups.push(device.createBindGroup({
91
+ layout: downsamplePipeline.getBindGroupLayout(0),
106
92
  entries: [
107
93
  { binding: 0, resource: sampler },
108
- { binding: 1, resource: src.texture.createView() },
109
- { binding: 2, resource: { buffer: flipDownBuffer } },
94
+ { binding: 1, resource: src.view },
110
95
  ],
111
- });
112
- const encoder = device.createCommandEncoder();
113
- const passEncoder = encoder.beginRenderPass({
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
- // Upsampling passes
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
- const bindGroup = device.createBindGroup({
136
- layout: pipeline.getBindGroupLayout(0),
111
+ upsampleBindGroups.push(device.createBindGroup({
112
+ layout: upsamplePipeline.getBindGroupLayout(0),
137
113
  entries: [
138
114
  { binding: 0, resource: sampler },
139
- { binding: 1, resource: src.texture.createView() },
140
- { binding: 2, resource: { buffer: flipUpBuffer } },
115
+ { binding: 1, resource: src.view },
141
116
  ],
142
- });
143
- const encoder = device.createCommandEncoder();
144
- const passEncoder = encoder.beginRenderPass({
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
- // Final pass: draw to canvas
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
- flipDownBuffer.destroy();
198
- flipUpBuffer.destroy();
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
+ };