@pexip/media-processor 20.3.5 → 21.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/api-docs/README.mdx +33 -6
- package/api-docs/functions/createBenchmark.mdx +17 -5
- package/api-docs/functions/createWindowedStats.mdx +23 -0
- package/api-docs/functions/iou.mdx +19 -0
- package/api-docs/functions/labelComponents.mdx +20 -0
- package/api-docs/functions/nearestPowerOfTwo.mdx +21 -0
- package/api-docs/functions/playVideo.mdx +16 -0
- package/api-docs/functions/scoreComponent.mdx +22 -0
- package/api-docs/functions/setVideoElementSrc.mdx +14 -0
- package/api-docs/interfaces/Benchmark.mdx +12 -0
- package/api-docs/interfaces/RendererOptions.mdx +26 -9
- package/api-docs/interfaces/SegmentationParams.mdx +29 -12
- package/api-docs/interfaces/SelectionOptions.mdx +15 -0
- package/api-docs/interfaces/Stats.mdx +11 -0
- package/api-docs/interfaces/Weights.mdx +12 -0
- package/api-docs/type-aliases/OptionalKeys.mdx +10 -0
- package/api-docs/variables/BACKGROUND_THRESHOLD.mdx +3 -0
- package/api-docs/variables/DOWN_SAMPLE_FACTOR.mdx +3 -0
- package/api-docs/variables/EDGE_BLUR_AMOUNT.mdx +1 -1
- package/api-docs/variables/EXCLUDE_BYSTANDERS.mdx +3 -0
- package/api-docs/variables/FOREGROUND_THRESHOLD.mdx +1 -1
- package/api-docs/variables/LIGHT_WRAP_BLUR_AMOUNT.mdx +3 -0
- package/api-docs/variables/LIGHT_WRAP_EDGE_BAND.mdx +3 -0
- package/api-docs/variables/LIGHT_WRAP_INTENSITY.mdx +3 -0
- package/api-docs/variables/LIGHT_WRAP_TIGHTNESS.mdx +3 -0
- package/api-docs/variables/MASK_COMBINE_RATIO.mdx +1 -1
- package/api-docs/variables/MORPH_DILATE_RADIUS_PX.mdx +3 -0
- package/api-docs/variables/MORPH_ERODE_RADIUS_PX.mdx +3 -0
- package/api-docs/variables/MORPH_PASS.mdx +3 -0
- package/api-docs/variables/PERSON_CENTER.mdx +3 -0
- package/api-docs/variables/SIGMA_RANGE.mdx +3 -0
- package/api-docs/variables/SIGMA_SPACE.mdx +3 -0
- package/api-docs/variables/STRONG_EDGE_BLUR_AMOUNT.mdx +3 -0
- package/api-docs/variables/getCanvasRenderingContext2D.mdx +14 -0
- package/api-docs/variables/getImageSize.mdx +13 -0
- package/dist/common/backends/webgl/binaryMask.d.ts +8 -0
- package/dist/common/backends/webgl/binaryMask.js +95 -0
- package/dist/common/backends/webgl/blender.d.ts +2 -1
- package/dist/common/backends/webgl/blender.js +18 -15
- package/dist/common/backends/webgl/blur.d.ts +1 -1
- package/dist/common/backends/webgl/blur.js +23 -16
- package/dist/common/backends/webgl/canvas.js +9 -4
- package/dist/common/backends/webgl/diffAbsMask.d.ts +10 -0
- package/dist/common/backends/webgl/diffAbsMask.js +103 -0
- package/dist/common/backends/webgl/geodesicReconstruction.d.ts +13 -0
- package/dist/common/backends/webgl/geodesicReconstruction.js +176 -0
- package/dist/common/backends/webgl/jointBilateralFilter.d.ts +12 -0
- package/dist/common/backends/webgl/jointBilateralFilter.js +108 -0
- package/dist/common/backends/webgl/morphErode.d.ts +13 -0
- package/dist/common/backends/webgl/morphErode.js +121 -0
- package/dist/common/backends/webgl/renderer.js +194 -34
- package/dist/common/backends/webgl/replace.d.ts +17 -0
- package/dist/common/backends/webgl/replace.js +126 -0
- package/dist/common/backends/webgl/selectComponent.d.ts +15 -0
- package/dist/common/backends/webgl/selectComponent.js +102 -0
- package/dist/common/backends/webgl/smoothingMask.d.ts +3 -3
- package/dist/common/backends/webgl/smoothingMask.js +22 -21
- package/dist/common/backends/webgl/tentBlur.d.ts +9 -0
- package/dist/common/backends/webgl/tentBlur.js +118 -0
- package/dist/common/backends/webgl/texture.d.ts +1 -1
- package/dist/common/backends/webgl/texture.js +19 -17
- package/dist/common/backends/webgl/textureToTexture.d.ts +1 -1
- package/dist/common/backends/webgl/textureToTexture.js +19 -16
- package/dist/common/backends/webgl/webglUtils.d.ts +7 -1
- package/dist/common/backends/webgl/webglUtils.js +64 -6
- package/dist/common/backends/webgpu/blender.d.ts +6 -1
- package/dist/common/backends/webgpu/blender.js +96 -51
- package/dist/common/backends/webgpu/constants.js +1 -1
- package/dist/common/backends/webgpu/dualFilterBlur.d.ts +2 -3
- package/dist/common/backends/webgpu/dualFilterBlur.js +146 -140
- package/dist/common/backends/webgpu/jointBilateralFilter.d.ts +7 -0
- package/dist/common/backends/webgpu/jointBilateralFilter.js +162 -0
- package/dist/common/backends/webgpu/renderer.js +211 -39
- package/dist/common/backends/webgpu/replace.d.ts +11 -0
- package/dist/common/backends/webgpu/replace.js +175 -0
- package/dist/common/backends/webgpu/resize.d.ts +4 -0
- package/dist/common/backends/webgpu/resize.js +76 -0
- package/dist/common/backends/webgpu/smoothingMask.d.ts +2 -2
- package/dist/common/backends/webgpu/smoothingMask.js +74 -40
- package/dist/common/backends/webgpu/tentBlur.d.ts +5 -0
- package/dist/common/backends/webgpu/tentBlur.js +131 -0
- package/dist/common/backends/webgpu/texture.d.ts +2 -2
- package/dist/common/backends/webgpu/texture.js +24 -19
- package/dist/common/backends/webgpu/textureToTexture.d.ts +1 -1
- package/dist/common/backends/webgpu/textureToTexture.js +28 -15
- package/dist/common/backends/webgpu/types.d.ts +2 -0
- package/dist/common/backends/webgpu/webgpuUtils.d.ts +24 -4
- package/dist/common/backends/webgpu/webgpuUtils.js +60 -11
- package/dist/common/constants.d.ts +19 -5
- package/dist/common/constants.js +19 -5
- package/dist/common/index.d.ts +1 -0
- package/dist/common/index.js +1 -0
- package/dist/common/tsconfig.tsbuildinfo +1 -1
- package/dist/common/types/render.d.ts +142 -13
- package/dist/common/types/utils.d.ts +21 -0
- package/dist/common/utils.d.ts +16 -0
- package/dist/common/utils.js +152 -0
- package/dist/main/benchUtils.d.ts +33 -1
- package/dist/main/benchUtils.js +145 -11
- package/dist/main/tsconfig.tsbuildinfo +1 -1
- package/dist/main/video/canvasTransform.d.ts +1 -1
- package/dist/main/video/canvasTransform.js +135 -47
- package/dist/main/video/index.d.ts +1 -1
- package/dist/main/video/index.js +1 -1
- package/dist/workers/mediaWorker.js +35 -162
- package/dist/workers/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -7
- package/dist/common/backends/webgl/shaders/blendFragmentShader.d.ts +0 -1
- package/dist/common/backends/webgl/shaders/blendFragmentShader.js +0 -21
- package/dist/common/backends/webgl/shaders/dualFilterBlurFragmentShader.d.ts +0 -1
- package/dist/common/backends/webgl/shaders/dualFilterBlurFragmentShader.js +0 -45
- package/dist/common/backends/webgl/shaders/smoothingMaskFragmentShader.d.ts +0 -1
- package/dist/common/backends/webgl/shaders/smoothingMaskFragmentShader.js +0 -57
- package/dist/common/backends/webgl/shaders/textureFragmentShader.d.ts +0 -1
- package/dist/common/backends/webgl/shaders/textureFragmentShader.js +0 -16
- package/dist/common/backends/webgl/shaders/vertexShader.d.ts +0 -1
- package/dist/common/backends/webgl/shaders/vertexShader.js +0 -19
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { RendererOptions } from '../../types/render';
|
|
2
|
+
import type { TextureInfo } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Based on Connected Component Labelling algorithm,
|
|
5
|
+
* find the center-most connected component in the mask and
|
|
6
|
+
* return a binary mask of just that component.
|
|
7
|
+
*
|
|
8
|
+
* @param processor - OffscreenCanvas for WebGL context
|
|
9
|
+
* @param width - target width of the downsampled mask to process (e.g. 32)
|
|
10
|
+
* @param height - target height of the downsampled mask to process (e.g. 18)
|
|
11
|
+
*/
|
|
12
|
+
export declare function createComponentSelector(processor: OffscreenCanvas, width: number, height: number): {
|
|
13
|
+
render: (softMask: TextureInfo, options: RendererOptions) => TextureInfo;
|
|
14
|
+
release: () => void;
|
|
15
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { assert } from '@pexip/utils';
|
|
2
|
+
import { RENDERING_EVENTS } from '../../constants';
|
|
3
|
+
import { createTextureToTextureRenderer } from './textureToTexture';
|
|
4
|
+
import { scoreComponent, labelComponents, iou } from '../../utils';
|
|
5
|
+
/**
|
|
6
|
+
* Based on Connected Component Labelling algorithm,
|
|
7
|
+
* find the center-most connected component in the mask and
|
|
8
|
+
* return a binary mask of just that component.
|
|
9
|
+
*
|
|
10
|
+
* @param processor - OffscreenCanvas for WebGL context
|
|
11
|
+
* @param width - target width of the downsampled mask to process (e.g. 32)
|
|
12
|
+
* @param height - target height of the downsampled mask to process (e.g. 18)
|
|
13
|
+
*/
|
|
14
|
+
export function createComponentSelector(processor, width, height) {
|
|
15
|
+
const gl = processor.getContext('webgl2', {
|
|
16
|
+
premultipliedAlpha: false,
|
|
17
|
+
powerPreference: 'high-performance',
|
|
18
|
+
});
|
|
19
|
+
const downSampler = createTextureToTextureRenderer(processor, true, false, width, height, false);
|
|
20
|
+
assert(gl, RENDERING_EVENTS.ContextCreationError);
|
|
21
|
+
const labels = new Int32Array(width * height);
|
|
22
|
+
const data = new Uint8Array(width * height);
|
|
23
|
+
const bin = new Uint8Array(width * height);
|
|
24
|
+
const bestMask = new Uint8Array(width * height);
|
|
25
|
+
const labelledMask = new Uint8Array(width * height);
|
|
26
|
+
let prevPrimaryScore = 0;
|
|
27
|
+
const outputTexture = gl.createTexture();
|
|
28
|
+
gl.bindTexture(gl.TEXTURE_2D, outputTexture);
|
|
29
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
30
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
31
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
32
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
33
|
+
const fbo = gl.createFramebuffer();
|
|
34
|
+
const config = {
|
|
35
|
+
// Frames persisted = -Math.log(1 + switchThreshold) / Math.log(decay)
|
|
36
|
+
// Width 0.3 switchThreshold and 0.95 decay, it persists about 6 frames
|
|
37
|
+
switchThreshold: 0.3,
|
|
38
|
+
decay: 0.95,
|
|
39
|
+
// Weights
|
|
40
|
+
area: 0.2,
|
|
41
|
+
height: 0.4,
|
|
42
|
+
center: 0.2,
|
|
43
|
+
iou: 0.2,
|
|
44
|
+
};
|
|
45
|
+
return {
|
|
46
|
+
render: (softMask, options) => {
|
|
47
|
+
const smallMask = downSampler.render(softMask);
|
|
48
|
+
// Read back single-channel 8-bit for simplicity here
|
|
49
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
|
|
50
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, smallMask.texture, 0);
|
|
51
|
+
gl.readPixels(0, 0, width, height, gl.RED, gl.UNSIGNED_BYTE, data);
|
|
52
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
53
|
+
// Binarize using foregroundThreshold in 0..255 domain
|
|
54
|
+
const thresh = Math.max(0, Math.min(255, Math.floor(255 * options.foregroundThreshold)));
|
|
55
|
+
for (let i = 0; i < bin.length; i++) {
|
|
56
|
+
//@ts-expect-error will not be undefined
|
|
57
|
+
bin[i] = data[i] >= thresh ? 255 : 0;
|
|
58
|
+
}
|
|
59
|
+
let bestScore = -Infinity;
|
|
60
|
+
let bestLabel = 0;
|
|
61
|
+
const stats = labelComponents(bin, labels, width, height);
|
|
62
|
+
for (const [l, s] of stats.entries()) {
|
|
63
|
+
if (l === 0) {
|
|
64
|
+
continue; // background
|
|
65
|
+
}
|
|
66
|
+
for (let i = 0; i < labels.length; i++) {
|
|
67
|
+
labelledMask[i] = labels[i] === l ? 255 : 0;
|
|
68
|
+
}
|
|
69
|
+
const score = scoreComponent(s, iou(bestMask, labelledMask), {
|
|
70
|
+
...config,
|
|
71
|
+
centerX: options.personCenter[0] * width,
|
|
72
|
+
centerY: options.personCenter[1] * height,
|
|
73
|
+
}, width, height);
|
|
74
|
+
if (score > bestScore) {
|
|
75
|
+
bestScore = score;
|
|
76
|
+
bestLabel = l;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Hysteresis: switch only if significantly better
|
|
80
|
+
const shouldSwitch = bestScore > prevPrimaryScore * (1.0 + config.switchThreshold);
|
|
81
|
+
if (shouldSwitch || prevPrimaryScore === 0) {
|
|
82
|
+
for (let i = 0; i < labels.length; i++) {
|
|
83
|
+
bestMask[i] = labels[i] === bestLabel ? 255 : 0;
|
|
84
|
+
}
|
|
85
|
+
prevPrimaryScore = bestScore;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
prevPrimaryScore *= config.decay;
|
|
89
|
+
}
|
|
90
|
+
// Upload small filtered mask to GPU and return as TextureInfo (caller will upsample & combine)
|
|
91
|
+
gl.bindTexture(gl.TEXTURE_2D, outputTexture);
|
|
92
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8, width, height, 0, gl.RED, gl.UNSIGNED_BYTE, bestMask);
|
|
93
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
94
|
+
return { texture: outputTexture, width, height };
|
|
95
|
+
},
|
|
96
|
+
release: () => {
|
|
97
|
+
gl.deleteTexture(outputTexture);
|
|
98
|
+
gl.deleteFramebuffer(fbo);
|
|
99
|
+
downSampler.release();
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -3,9 +3,9 @@ import type { TextureInfo } from './types';
|
|
|
3
3
|
/**
|
|
4
4
|
* Smooth the mask edge
|
|
5
5
|
*/
|
|
6
|
-
export declare const createSmoothingMaskRenderer: (processor: OffscreenCanvas) => {
|
|
7
|
-
render: ({ mask, prevMask
|
|
8
|
-
prevMask
|
|
6
|
+
export declare const createSmoothingMaskRenderer: (processor: OffscreenCanvas, width: number, height: number) => {
|
|
7
|
+
render: ({ mask, prevMask }: {
|
|
8
|
+
prevMask: TextureInfo;
|
|
9
9
|
mask: TextureInfo;
|
|
10
10
|
}, options: RendererOptions) => TextureInfo;
|
|
11
11
|
release: () => void;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { assert } from '@pexip/utils';
|
|
2
2
|
import { m3 } from '../../m3';
|
|
3
3
|
import { RENDERING_EVENTS } from '../../constants';
|
|
4
|
-
import { createGLProgram,
|
|
5
|
-
import
|
|
6
|
-
import
|
|
4
|
+
import { createGLProgram, createFrameBuffer, createMaskTexture, } from './webglUtils';
|
|
5
|
+
import vertexSrc from './shaders/matrix.vert';
|
|
6
|
+
import fragmentSrc from './shaders/smoothingMask.frag';
|
|
7
7
|
/**
|
|
8
8
|
* Smooth the mask edge
|
|
9
9
|
*/
|
|
10
|
-
export const createSmoothingMaskRenderer = (processor) => {
|
|
11
|
-
const { gl, program, attribLocations, uniformLocations, release } = createGLProgram(processor, {
|
|
10
|
+
export const createSmoothingMaskRenderer = (processor, width, height) => {
|
|
11
|
+
const { gl, program, attribLocations, uniformLocations, floatFullChannelSupported, floatSingleChannelSupported, release, } = createGLProgram(processor, {
|
|
12
12
|
attribs: {
|
|
13
13
|
position: 'a_position',
|
|
14
14
|
texCoord: 'a_texCoord',
|
|
@@ -16,12 +16,13 @@ export const createSmoothingMaskRenderer = (processor) => {
|
|
|
16
16
|
uniforms: {
|
|
17
17
|
matrix: 'u_matrix',
|
|
18
18
|
mask: 'u_mask',
|
|
19
|
-
maskCombineRatio: 'u_mask_combine_ratio',
|
|
20
|
-
foregroundThreshold: 'u_foreground_threshold',
|
|
21
19
|
prevMask: 'u_mask_prev',
|
|
20
|
+
frame: 'u_frame',
|
|
21
|
+
prevFrame: 'u_frame_prev',
|
|
22
|
+
maskCombineRatio: 'u_mask_combine_ratio',
|
|
22
23
|
},
|
|
23
|
-
vertexSrc
|
|
24
|
-
fragmentSrc
|
|
24
|
+
vertexSrc,
|
|
25
|
+
fragmentSrc,
|
|
25
26
|
});
|
|
26
27
|
const vao = gl.createVertexArray();
|
|
27
28
|
gl.bindVertexArray(vao);
|
|
@@ -55,16 +56,13 @@ export const createSmoothingMaskRenderer = (processor) => {
|
|
|
55
56
|
]), gl.STATIC_DRAW);
|
|
56
57
|
gl.enableVertexAttribArray(attribLocations.texCoord);
|
|
57
58
|
gl.vertexAttribPointer(attribLocations.texCoord, 2, gl.FLOAT, false, 0, 0);
|
|
58
|
-
const outputTexture =
|
|
59
|
-
|
|
60
|
-
const frameBuffer = gl.createFramebuffer();
|
|
61
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
|
|
62
|
-
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, outputTexture, 0);
|
|
59
|
+
const outputTexture = createMaskTexture(gl, width, height, false, floatSingleChannelSupported, floatFullChannelSupported);
|
|
60
|
+
const frameBuffer = createFrameBuffer(gl, outputTexture);
|
|
63
61
|
return {
|
|
64
|
-
render: ({ mask, prevMask
|
|
62
|
+
render: ({ mask, prevMask }, options) => {
|
|
65
63
|
assert(!gl.isContextLost(), RENDERING_EVENTS.ContextLost);
|
|
66
64
|
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
|
|
67
|
-
gl.viewport(0, 0,
|
|
65
|
+
gl.viewport(0, 0, mask.width, mask.height);
|
|
68
66
|
gl.clearColor(0, 0, 0, 0);
|
|
69
67
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
70
68
|
gl.useProgram(program);
|
|
@@ -75,19 +73,22 @@ export const createSmoothingMaskRenderer = (processor) => {
|
|
|
75
73
|
gl.bindTexture(gl.TEXTURE_2D, mask.texture);
|
|
76
74
|
gl.activeTexture(gl.TEXTURE1);
|
|
77
75
|
gl.uniform1i(uniformLocations.prevMask, 1);
|
|
78
|
-
gl.bindTexture(gl.TEXTURE_2D, prevMask
|
|
76
|
+
gl.bindTexture(gl.TEXTURE_2D, prevMask.texture);
|
|
79
77
|
// No Flip Y Coordinate for drawing on the frame buffer
|
|
80
78
|
// Project, position and scale to full-size of the canvas
|
|
81
79
|
const matrix = m3.projection(1, 1, 1);
|
|
82
80
|
gl.uniformMatrix3fv(uniformLocations.matrix, false, matrix);
|
|
83
81
|
gl.uniform1f(uniformLocations.maskCombineRatio, options.maskCombineRatio);
|
|
84
|
-
gl.uniform1f(uniformLocations.foregroundThreshold, options.foregroundThreshold);
|
|
85
82
|
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
86
|
-
|
|
83
|
+
// Cleanup
|
|
84
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
|
85
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
86
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
87
|
+
gl.bindVertexArray(null);
|
|
87
88
|
return {
|
|
88
89
|
texture: outputTexture,
|
|
89
|
-
width
|
|
90
|
-
height
|
|
90
|
+
width,
|
|
91
|
+
height,
|
|
91
92
|
};
|
|
92
93
|
},
|
|
93
94
|
release: () => {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TextureInfo } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Create a tent blur renderer for small radius blurring of a mask.
|
|
4
|
+
* The blur is implemented as a separable filter.
|
|
5
|
+
*/
|
|
6
|
+
export declare const createTentBlurRenderer: (processor: OffscreenCanvas, width: number, height: number) => {
|
|
7
|
+
render: (mask: TextureInfo, radiusPx: number) => TextureInfo;
|
|
8
|
+
release: () => void;
|
|
9
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { assert } from '@pexip/utils';
|
|
2
|
+
import { m3 } from '../../m3';
|
|
3
|
+
import { RENDERING_EVENTS } from '../../constants';
|
|
4
|
+
import { clamping } from '../../utils';
|
|
5
|
+
import { createGLProgram, createMaskTexture, createFrameBuffer, } from './webglUtils';
|
|
6
|
+
import vertexSrc from './shaders/matrix.vert';
|
|
7
|
+
import fragmentSrc from './shaders/tentBlur.frag';
|
|
8
|
+
const MAX_RADIUS_PX = 16;
|
|
9
|
+
const clampRadiusPx = clamping(0, MAX_RADIUS_PX);
|
|
10
|
+
/**
|
|
11
|
+
* Create a tent blur renderer for small radius blurring of a mask.
|
|
12
|
+
* The blur is implemented as a separable filter.
|
|
13
|
+
*/
|
|
14
|
+
export const createTentBlurRenderer = (processor, width, height) => {
|
|
15
|
+
const { gl, program, attribLocations, uniformLocations, floatSingleChannelSupported, floatFullChannelSupported, release, } = createGLProgram(processor, {
|
|
16
|
+
attribs: {
|
|
17
|
+
position: 'a_position',
|
|
18
|
+
texCoord: 'a_texCoord',
|
|
19
|
+
},
|
|
20
|
+
uniforms: {
|
|
21
|
+
matrix: 'u_matrix',
|
|
22
|
+
mask: 'u_mask',
|
|
23
|
+
texelSize: 'u_texel_size',
|
|
24
|
+
direction: 'u_direction',
|
|
25
|
+
radiusPx: 'u_radius_px',
|
|
26
|
+
},
|
|
27
|
+
vertexSrc,
|
|
28
|
+
fragmentSrc,
|
|
29
|
+
});
|
|
30
|
+
const vao = gl.createVertexArray();
|
|
31
|
+
gl.bindVertexArray(vao);
|
|
32
|
+
// Position
|
|
33
|
+
const positionBuffer = gl.createBuffer();
|
|
34
|
+
gl.enableVertexAttribArray(attribLocations.position);
|
|
35
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
|
36
|
+
gl.vertexAttribPointer(attribLocations.position, 2, gl.FLOAT, false, 0, 0);
|
|
37
|
+
gl.bufferData(gl.ARRAY_BUFFER,
|
|
38
|
+
// prettier-ignore
|
|
39
|
+
new Float32Array([
|
|
40
|
+
0, 0,
|
|
41
|
+
0, 1,
|
|
42
|
+
1, 0,
|
|
43
|
+
1, 0,
|
|
44
|
+
0, 1,
|
|
45
|
+
1, 1,
|
|
46
|
+
]), gl.STATIC_DRAW);
|
|
47
|
+
// Texture coordinates for the rectangle
|
|
48
|
+
const texCoordBuffer = gl.createBuffer();
|
|
49
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
|
|
50
|
+
gl.bufferData(gl.ARRAY_BUFFER,
|
|
51
|
+
// prettier-ignore
|
|
52
|
+
new Float32Array([
|
|
53
|
+
0, 0,
|
|
54
|
+
0, 1,
|
|
55
|
+
1, 0,
|
|
56
|
+
1, 0,
|
|
57
|
+
0, 1,
|
|
58
|
+
1, 1,
|
|
59
|
+
]), gl.STATIC_DRAW);
|
|
60
|
+
gl.enableVertexAttribArray(attribLocations.texCoord);
|
|
61
|
+
gl.vertexAttribPointer(attribLocations.texCoord, 2, gl.FLOAT, false, 0, 0);
|
|
62
|
+
const outputTexturePing = createMaskTexture(gl, width, height, false, floatSingleChannelSupported, floatFullChannelSupported);
|
|
63
|
+
const frameBufferPing = createFrameBuffer(gl, outputTexturePing);
|
|
64
|
+
const outputTexturePong = createMaskTexture(gl, width, height, false, floatSingleChannelSupported, floatFullChannelSupported);
|
|
65
|
+
const frameBufferPong = createFrameBuffer(gl, outputTexturePong);
|
|
66
|
+
const scaleFactor = processor.width / width;
|
|
67
|
+
return {
|
|
68
|
+
render: (mask, radiusPx) => {
|
|
69
|
+
assert(!gl.isContextLost(), RENDERING_EVENTS.ContextLost);
|
|
70
|
+
gl.clearColor(0, 0, 0, 0);
|
|
71
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
72
|
+
gl.useProgram(program);
|
|
73
|
+
gl.bindVertexArray(vao);
|
|
74
|
+
gl.uniform2f(uniformLocations.texelSize, 1 / mask.width, 1 / mask.height);
|
|
75
|
+
gl.uniform1i(uniformLocations.radiusPx, clampRadiusPx(Math.floor(radiusPx / scaleFactor)));
|
|
76
|
+
const matrix = m3.projection(1, 1, 1);
|
|
77
|
+
// No Flip Y Coordinate for drawing on the frame buffer
|
|
78
|
+
// Project, position and scale to full-size of the canvas
|
|
79
|
+
gl.uniformMatrix3fv(uniformLocations.matrix, false, matrix);
|
|
80
|
+
let write = frameBufferPing;
|
|
81
|
+
let read = mask.texture;
|
|
82
|
+
// Ping-pong framebuffers for multiple passes
|
|
83
|
+
// Separable
|
|
84
|
+
for (let j = 0; j < 2; j++) {
|
|
85
|
+
// Textures
|
|
86
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
87
|
+
gl.uniform1i(uniformLocations.mask, 0);
|
|
88
|
+
gl.bindTexture(gl.TEXTURE_2D, read);
|
|
89
|
+
// Horizontal then Vertical
|
|
90
|
+
gl.uniform2f(uniformLocations.direction, 1.0 - j, 0.0 + j);
|
|
91
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, write);
|
|
92
|
+
gl.viewport(0, 0, mask.width, mask.height);
|
|
93
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
94
|
+
// Swap
|
|
95
|
+
[read, write] =
|
|
96
|
+
write === frameBufferPing
|
|
97
|
+
? [outputTexturePing, frameBufferPong]
|
|
98
|
+
: [outputTexturePong, frameBufferPing];
|
|
99
|
+
}
|
|
100
|
+
// Cleanup
|
|
101
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
|
102
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
103
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
104
|
+
gl.bindVertexArray(null);
|
|
105
|
+
return { texture: read, width, height };
|
|
106
|
+
},
|
|
107
|
+
release: () => {
|
|
108
|
+
gl.deleteBuffer(positionBuffer);
|
|
109
|
+
gl.deleteBuffer(texCoordBuffer);
|
|
110
|
+
gl.deleteTexture(outputTexturePing);
|
|
111
|
+
gl.deleteTexture(outputTexturePong);
|
|
112
|
+
gl.deleteFramebuffer(frameBufferPing);
|
|
113
|
+
gl.deleteFramebuffer(frameBufferPong);
|
|
114
|
+
gl.deleteVertexArray(vao);
|
|
115
|
+
release();
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
};
|
|
@@ -3,7 +3,7 @@ import type { TextureInfo } from './types';
|
|
|
3
3
|
/**
|
|
4
4
|
* Render Video frame into texture
|
|
5
5
|
*/
|
|
6
|
-
export declare const createTextureRenderer: (processor: OffscreenCanvas) => {
|
|
6
|
+
export declare const createTextureRenderer: (processor: OffscreenCanvas, redOnly: boolean) => {
|
|
7
7
|
render: (video: VideoFrameLike) => TextureInfo;
|
|
8
8
|
release: () => void;
|
|
9
9
|
};
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { assert } from '@pexip/utils';
|
|
2
2
|
import { RENDERING_EVENTS } from '../../constants';
|
|
3
3
|
import { m3 } from '../../m3';
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import
|
|
4
|
+
import { createFrameBuffer, createGLProgram, createMaskTexture, createRGBAFrameTexture, } from './webglUtils';
|
|
5
|
+
import vertexSrc from './shaders/matrix.vert';
|
|
6
|
+
import fragmentSrc from './shaders/texture.frag';
|
|
7
7
|
/**
|
|
8
8
|
* Render Video frame into texture
|
|
9
9
|
*/
|
|
10
|
-
export const createTextureRenderer = (processor) => {
|
|
11
|
-
const { gl, program, attribLocations, uniformLocations, release } = createGLProgram(processor, {
|
|
10
|
+
export const createTextureRenderer = (processor, redOnly) => {
|
|
11
|
+
const { gl, program, attribLocations, uniformLocations, floatFullChannelSupported, floatSingleChannelSupported, release, } = createGLProgram(processor, {
|
|
12
12
|
attribs: {
|
|
13
13
|
position: 'a_position',
|
|
14
14
|
texCoord: 'a_texCoord',
|
|
@@ -17,8 +17,8 @@ export const createTextureRenderer = (processor) => {
|
|
|
17
17
|
matrix: 'u_matrix',
|
|
18
18
|
texture: 'u_texture',
|
|
19
19
|
},
|
|
20
|
-
vertexSrc
|
|
21
|
-
fragmentSrc
|
|
20
|
+
vertexSrc,
|
|
21
|
+
fragmentSrc,
|
|
22
22
|
});
|
|
23
23
|
const vao = gl.createVertexArray();
|
|
24
24
|
gl.bindVertexArray(vao);
|
|
@@ -53,12 +53,11 @@ export const createTextureRenderer = (processor) => {
|
|
|
53
53
|
]), gl.STATIC_DRAW);
|
|
54
54
|
gl.enableVertexAttribArray(attribLocations.texCoord);
|
|
55
55
|
gl.vertexAttribPointer(attribLocations.texCoord, 2, gl.FLOAT, false, 0, 0);
|
|
56
|
-
const frameTexture =
|
|
57
|
-
const outputTexture =
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, outputTexture, 0);
|
|
56
|
+
const frameTexture = createRGBAFrameTexture(gl, processor.width, processor.height, true, floatFullChannelSupported);
|
|
57
|
+
const outputTexture = redOnly
|
|
58
|
+
? createMaskTexture(gl, processor.width, processor.height, false, floatSingleChannelSupported, floatFullChannelSupported)
|
|
59
|
+
: createRGBAFrameTexture(gl, processor.width, processor.height, true, floatFullChannelSupported);
|
|
60
|
+
const frameBuffer = createFrameBuffer(gl, outputTexture);
|
|
62
61
|
return {
|
|
63
62
|
render: (video) => {
|
|
64
63
|
assert(!gl.isContextLost(), RENDERING_EVENTS.ContextLost);
|
|
@@ -71,16 +70,19 @@ export const createTextureRenderer = (processor) => {
|
|
|
71
70
|
gl.bindVertexArray(vao);
|
|
72
71
|
// Texture Unit setup
|
|
73
72
|
gl.uniform1i(uniformLocations.texture, 0);
|
|
74
|
-
// Project, position and scale to full-size of the canvas
|
|
73
|
+
// Project, position, and scale to full-size of the canvas
|
|
75
74
|
const matrix = m3.projection(1, 1, 1);
|
|
76
75
|
gl.uniformMatrix3fv(uniformLocations.matrix, false, matrix);
|
|
77
|
-
//
|
|
76
|
+
// Upload the frame to texture
|
|
78
77
|
gl.activeTexture(gl.TEXTURE0);
|
|
79
78
|
gl.bindTexture(gl.TEXTURE_2D, frameTexture);
|
|
80
79
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video.frame);
|
|
81
|
-
gl.generateMipmap(gl.TEXTURE_2D);
|
|
82
80
|
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
83
|
-
|
|
81
|
+
// Cleanup
|
|
82
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
|
83
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
84
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
85
|
+
gl.bindVertexArray(null);
|
|
84
86
|
return {
|
|
85
87
|
texture: outputTexture,
|
|
86
88
|
width: processor.width,
|
|
@@ -2,7 +2,7 @@ import type { TextureInfo } from './types';
|
|
|
2
2
|
/**
|
|
3
3
|
* Render texture into another texture, i.e. clone
|
|
4
4
|
*/
|
|
5
|
-
export declare const createTextureToTextureRenderer: (processor: OffscreenCanvas) => {
|
|
5
|
+
export declare const createTextureToTextureRenderer: (processor: OffscreenCanvas, redOnly: boolean, linearSampling?: boolean, targetWidth?: number, targetHeight?: number, float?: boolean) => {
|
|
6
6
|
render: (inputTexture: TextureInfo) => TextureInfo;
|
|
7
7
|
release: () => void;
|
|
8
8
|
};
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { assert } from '@pexip/utils';
|
|
2
2
|
import { RENDERING_EVENTS } from '../../constants';
|
|
3
3
|
import { m3 } from '../../m3';
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import
|
|
4
|
+
import { createFrameBuffer, createGLProgram, createMaskTexture, createRGBAFrameTexture, } from './webglUtils';
|
|
5
|
+
import vertexSrc from './shaders/matrix.vert';
|
|
6
|
+
import fragmentSrc from './shaders/texture.frag';
|
|
7
7
|
/**
|
|
8
8
|
* Render texture into another texture, i.e. clone
|
|
9
9
|
*/
|
|
10
|
-
export const createTextureToTextureRenderer = (processor) => {
|
|
11
|
-
const { gl, program, attribLocations, uniformLocations, release } = createGLProgram(processor, {
|
|
10
|
+
export const createTextureToTextureRenderer = (processor, redOnly, linearSampling = false, targetWidth = processor.width, targetHeight = processor.height, float = true) => {
|
|
11
|
+
const { gl, program, attribLocations, uniformLocations, floatFullChannelSupported, floatSingleChannelSupported, release, } = createGLProgram(processor, {
|
|
12
12
|
attribs: {
|
|
13
13
|
position: 'a_position',
|
|
14
14
|
texCoord: 'a_texCoord',
|
|
@@ -17,8 +17,8 @@ export const createTextureToTextureRenderer = (processor) => {
|
|
|
17
17
|
matrix: 'u_matrix',
|
|
18
18
|
texture: 'u_texture',
|
|
19
19
|
},
|
|
20
|
-
vertexSrc
|
|
21
|
-
fragmentSrc
|
|
20
|
+
vertexSrc,
|
|
21
|
+
fragmentSrc,
|
|
22
22
|
});
|
|
23
23
|
const vao = gl.createVertexArray();
|
|
24
24
|
gl.bindVertexArray(vao);
|
|
@@ -53,17 +53,16 @@ export const createTextureToTextureRenderer = (processor) => {
|
|
|
53
53
|
]), gl.STATIC_DRAW);
|
|
54
54
|
gl.enableVertexAttribArray(attribLocations.texCoord);
|
|
55
55
|
gl.vertexAttribPointer(attribLocations.texCoord, 2, gl.FLOAT, false, 0, 0);
|
|
56
|
-
const outputTexture =
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, outputTexture, 0);
|
|
56
|
+
const outputTexture = redOnly
|
|
57
|
+
? createMaskTexture(gl, targetWidth, targetHeight, linearSampling, float && floatSingleChannelSupported, float && floatFullChannelSupported)
|
|
58
|
+
: createRGBAFrameTexture(gl, targetWidth, targetHeight, linearSampling, float && floatFullChannelSupported);
|
|
59
|
+
const frameBuffer = createFrameBuffer(gl, outputTexture);
|
|
61
60
|
return {
|
|
62
61
|
render: (inputTexture) => {
|
|
63
62
|
assert(!gl.isContextLost(), RENDERING_EVENTS.ContextLost);
|
|
64
63
|
// Draw on the frame buffer
|
|
65
64
|
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
|
|
66
|
-
gl.viewport(0, 0,
|
|
65
|
+
gl.viewport(0, 0, targetWidth, targetHeight);
|
|
67
66
|
gl.clearColor(0, 0, 0, 1);
|
|
68
67
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
69
68
|
gl.useProgram(program);
|
|
@@ -76,11 +75,15 @@ export const createTextureToTextureRenderer = (processor) => {
|
|
|
76
75
|
const matrix = m3.projection(1, 1, 1);
|
|
77
76
|
gl.uniformMatrix3fv(uniformLocations.matrix, false, matrix);
|
|
78
77
|
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
79
|
-
|
|
78
|
+
// Cleanup
|
|
79
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
|
80
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
81
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
82
|
+
gl.bindVertexArray(null);
|
|
80
83
|
return {
|
|
81
84
|
texture: outputTexture,
|
|
82
|
-
width:
|
|
83
|
-
height:
|
|
85
|
+
width: targetWidth,
|
|
86
|
+
height: targetHeight,
|
|
84
87
|
};
|
|
85
88
|
},
|
|
86
89
|
release: () => {
|
|
@@ -61,7 +61,11 @@ export declare const createProgram: (gl: WebGLRenderingContext, shaders: [WebGLS
|
|
|
61
61
|
* A parallel array to opt_attribs letting you assign locations.
|
|
62
62
|
*/
|
|
63
63
|
export declare const createProgramFromSources: (gl: WebGLRenderingContext, [vs, fs]: [GLShaderSource, GLShaderSource], attribs?: string[], locations?: number[]) => [WebGLProgram, Release];
|
|
64
|
-
export declare const createAndSetupTexture: (gl: WebGL2RenderingContext) => WebGLTexture;
|
|
64
|
+
export declare const createAndSetupTexture: (gl: WebGL2RenderingContext, linear: boolean) => WebGLTexture;
|
|
65
|
+
export declare const createRGBAFrameTexture: (gl: WebGL2RenderingContext, width: number, height: number, linear?: boolean, withFloat?: boolean) => WebGLTexture;
|
|
66
|
+
export declare const createMaskTexture: (gl: WebGL2RenderingContext, width: number, height: number, linear?: boolean, withFloatSingle?: boolean, withFloatFull?: boolean) => WebGLTexture;
|
|
67
|
+
export declare const createFrameBuffer: (gl: WebGL2RenderingContext, texture: WebGLTexture) => WebGLFramebuffer;
|
|
68
|
+
export declare const support: (gl: WebGL2RenderingContext, internalFormat: GLint, format: GLenum, width?: number, height?: number) => boolean;
|
|
65
69
|
type Location<S extends string> = S extends `u_${string}` ? ReturnType<WebGL2RenderingContext['getUniformLocation']> : S extends `a_${string}` ? ReturnType<WebGL2RenderingContext['getAttribLocation']> : never;
|
|
66
70
|
export declare const getLocations: <T extends Record<string, string>>(gl: WebGL2RenderingContext, program: WebGLProgram, locations: T) => { [Key in keyof T]: Location<T[Key]>; };
|
|
67
71
|
export declare const createGLProgram: <A extends Record<string, string>, U extends Record<string, string>>(processCanvas: OffscreenCanvas, { attribs, uniforms, vertexSrc, fragmentSrc, }: {
|
|
@@ -75,6 +79,8 @@ export declare const createGLProgram: <A extends Record<string, string>, U exten
|
|
|
75
79
|
attribLocations: { [Key in keyof A]: Location<A[Key]>; };
|
|
76
80
|
uniformLocations: { [Key_1 in keyof U]: Location<U[Key_1]>; };
|
|
77
81
|
release: Release;
|
|
82
|
+
floatSingleChannelSupported: boolean;
|
|
83
|
+
floatFullChannelSupported: boolean;
|
|
78
84
|
};
|
|
79
85
|
export declare const toTextureInfo: (mask: MPMask) => TextureInfo;
|
|
80
86
|
export {};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { assert } from '@pexip/utils';
|
|
2
|
+
import { RENDERING_EVENTS } from '../../constants';
|
|
1
3
|
/**
|
|
2
4
|
* WebGL Shader type enum
|
|
3
5
|
*/
|
|
@@ -139,16 +141,69 @@ export const createProgramFromSources = (gl, [vs, fs], attribs, locations) => cr
|
|
|
139
141
|
loadShader(gl, GL_SHADER_TYPE.VertexShader, vs),
|
|
140
142
|
loadShader(gl, GL_SHADER_TYPE.FragmentShader, fs),
|
|
141
143
|
], attribs, locations);
|
|
142
|
-
export const createAndSetupTexture = (gl) => {
|
|
144
|
+
export const createAndSetupTexture = (gl, linear) => {
|
|
143
145
|
const texture = gl.createTexture();
|
|
144
146
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
145
|
-
// Set the parameters so we don't need
|
|
147
|
+
// Set the parameters so we don't need MIPS and so we're not filtering
|
|
146
148
|
// and we don't repeat at the edges.
|
|
147
149
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
148
150
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
151
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, linear ? gl.LINEAR : gl.NEAREST);
|
|
152
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, linear ? gl.LINEAR : gl.NEAREST);
|
|
153
|
+
return texture;
|
|
154
|
+
};
|
|
155
|
+
export const createRGBAFrameTexture = (gl, width, height, linear = false, withFloat = false) => {
|
|
156
|
+
const texture = createAndSetupTexture(gl, linear);
|
|
157
|
+
if (withFloat) {
|
|
158
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, width, height, 0, gl.RGBA, gl.HALF_FLOAT, null);
|
|
159
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
160
|
+
return texture;
|
|
161
|
+
}
|
|
162
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
|
163
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
164
|
+
return texture;
|
|
165
|
+
};
|
|
166
|
+
export const createMaskTexture = (gl, width, height, linear = false, withFloatSingle = false, withFloatFull = false) => {
|
|
167
|
+
const texture = createAndSetupTexture(gl, linear);
|
|
168
|
+
if (withFloatSingle) {
|
|
169
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.R16F, width, height, 0, gl.RED, gl.HALF_FLOAT, null);
|
|
170
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
171
|
+
return texture;
|
|
172
|
+
}
|
|
173
|
+
if (withFloatFull) {
|
|
174
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, width, height, 0, gl.RGBA, gl.HALF_FLOAT, null);
|
|
175
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
176
|
+
return texture;
|
|
177
|
+
}
|
|
178
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8, width, height, 0, gl.RED, gl.UNSIGNED_BYTE, null);
|
|
179
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
180
|
+
return texture;
|
|
181
|
+
};
|
|
182
|
+
export const createFrameBuffer = (gl, texture) => {
|
|
183
|
+
const frameBuffer = gl.createFramebuffer();
|
|
184
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
|
|
185
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
|
|
186
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
187
|
+
return frameBuffer;
|
|
188
|
+
};
|
|
189
|
+
export const support = (gl, internalFormat, format, width = 2, height = 2) => {
|
|
190
|
+
// Create single-channel float16 texture
|
|
191
|
+
const tex = gl.createTexture();
|
|
192
|
+
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
193
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, width, height, 0, format, gl.HALF_FLOAT, null);
|
|
149
194
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
150
195
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
151
|
-
|
|
196
|
+
// Attach texture
|
|
197
|
+
const fb = createFrameBuffer(gl, tex);
|
|
198
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
|
|
199
|
+
// Check completeness
|
|
200
|
+
const complete = gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE;
|
|
201
|
+
// Clean up
|
|
202
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
203
|
+
gl.deleteFramebuffer(fb);
|
|
204
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
205
|
+
gl.deleteTexture(tex);
|
|
206
|
+
return complete;
|
|
152
207
|
};
|
|
153
208
|
export const getLocations = (gl, program, locations) => Object.keys(locations).reduce((acc, key) => {
|
|
154
209
|
const k = key;
|
|
@@ -167,9 +222,7 @@ export const createGLProgram = (processCanvas, { attribs, uniforms, vertexSrc, f
|
|
|
167
222
|
premultipliedAlpha: false,
|
|
168
223
|
powerPreference: 'high-performance',
|
|
169
224
|
});
|
|
170
|
-
|
|
171
|
-
throw new Error('WebGL2 is not supported');
|
|
172
|
-
}
|
|
225
|
+
assert(gl, RENDERING_EVENTS.ContextCreationError);
|
|
173
226
|
gl.enable(gl.BLEND);
|
|
174
227
|
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
175
228
|
assertShaderSource(vertexSrc);
|
|
@@ -178,12 +231,17 @@ export const createGLProgram = (processCanvas, { attribs, uniforms, vertexSrc, f
|
|
|
178
231
|
vertexSrc,
|
|
179
232
|
fragmentSrc,
|
|
180
233
|
]);
|
|
234
|
+
const hasFloat = !!gl.getExtension('EXT_color_buffer_float');
|
|
235
|
+
const floatSingleChannelSupported = hasFloat && support(gl, gl.R16F, gl.RED);
|
|
236
|
+
const floatFullChannelSupported = hasFloat && support(gl, gl.RGBA16F, gl.RGBA);
|
|
181
237
|
return {
|
|
182
238
|
gl,
|
|
183
239
|
program,
|
|
184
240
|
attribLocations: getLocations(gl, program, attribs),
|
|
185
241
|
uniformLocations: getLocations(gl, program, uniforms),
|
|
186
242
|
release,
|
|
243
|
+
floatSingleChannelSupported,
|
|
244
|
+
floatFullChannelSupported,
|
|
187
245
|
};
|
|
188
246
|
};
|
|
189
247
|
export const toTextureInfo = (mask) => ({
|