@pexip/media-processor 17.4.0 → 18.1.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 +37 -0
- package/dist/common/backends/webgl/blender.d.ts +11 -0
- package/dist/common/backends/webgl/blender.js +104 -0
- package/dist/common/backends/webgl/blur.d.ts +5 -0
- package/dist/common/backends/webgl/blur.js +153 -0
- package/dist/common/backends/webgl/canvas.d.ts +5 -0
- package/dist/common/backends/webgl/canvas.js +77 -0
- package/dist/common/backends/webgl/renderer.d.ts +8 -0
- package/dist/common/backends/webgl/renderer.js +126 -0
- package/dist/common/backends/webgl/shaders/blendFragmentShader.d.ts +1 -0
- package/dist/common/backends/webgl/shaders/blendFragmentShader.js +63 -0
- package/dist/common/backends/webgl/shaders/dualFilterBlurFragmentShader.d.ts +1 -0
- package/dist/common/backends/webgl/shaders/dualFilterBlurFragmentShader.js +45 -0
- package/dist/common/backends/webgl/shaders/textureFragmentShader.d.ts +1 -0
- package/dist/common/backends/webgl/shaders/textureFragmentShader.js +16 -0
- package/dist/common/backends/webgl/shaders/vertexShader.d.ts +1 -0
- package/dist/common/backends/webgl/shaders/vertexShader.js +19 -0
- package/dist/common/backends/webgl/texture.d.ts +6 -0
- package/dist/common/backends/webgl/texture.js +94 -0
- package/dist/common/backends/webgl/types.d.ts +5 -0
- package/dist/common/backends/webgl/types.js +1 -0
- package/dist/common/backends/webgl/webgl.mock.js +8 -0
- package/dist/common/backends/webgl/webglUtils.d.ts +80 -0
- package/dist/common/backends/webgl/webglUtils.js +194 -0
- package/dist/common/canvasRenderUtils.d.ts +62 -0
- package/dist/{main/video → common}/canvasRenderUtils.js +100 -61
- package/dist/common/constants.d.ts +27 -0
- package/dist/common/constants.js +23 -0
- package/dist/common/index.d.ts +9 -0
- package/dist/common/index.js +9 -0
- package/dist/common/m3.d.ts +17 -0
- package/dist/common/m3.js +69 -0
- package/dist/common/tsconfig.tsbuildinfo +1 -0
- package/dist/common/types/media.d.ts +22 -0
- package/dist/common/types/media.js +1 -0
- package/dist/common/types/messageEvents.d.ts +31 -0
- package/dist/common/types/messageEvents.js +1 -0
- package/dist/common/types/processor.d.ts +15 -0
- package/dist/common/types/processor.js +1 -0
- package/dist/common/types/render.d.ts +38 -0
- package/dist/common/types/render.js +1 -0
- package/dist/common/types/segmentation.d.ts +99 -0
- package/dist/common/types/segmentation.js +4 -0
- package/dist/common/types/utils.d.ts +19 -0
- package/dist/common/types/utils.js +1 -0
- package/dist/common/utils.d.ts +35 -0
- package/dist/common/utils.js +129 -0
- package/dist/main/benchUtils.d.ts +3 -2
- package/dist/main/benchUtils.js +7 -6
- package/dist/main/index.d.ts +2 -0
- package/dist/main/index.js +2 -0
- package/dist/main/process.d.ts +1 -11
- package/dist/main/process.js +0 -20
- package/dist/main/tsconfig.tsbuildinfo +1 -1
- package/dist/main/types.d.ts +2 -2
- package/dist/main/video/canvasTransform.d.ts +14 -5
- package/dist/main/video/canvasTransform.js +101 -86
- package/dist/main/video/constants.d.ts +0 -6
- package/dist/main/video/constants.js +0 -6
- package/dist/main/video/index.d.ts +2 -2
- package/dist/main/video/index.js +2 -2
- package/dist/main/video/load.d.ts +0 -14
- package/dist/main/video/load.js +0 -8
- package/dist/main/video/segmenters/mediapipe.d.ts +9 -11
- package/dist/main/video/segmenters/mediapipe.js +127 -77
- package/dist/main/video/typeGuards.d.ts +0 -1
- package/dist/main/video/typeGuards.js +2 -8
- package/dist/main/video/types.d.ts +19 -61
- package/dist/main/video/types.js +1 -20
- package/dist/main/video/utils.d.ts +5 -12
- package/dist/main/video/utils.js +5 -152
- package/dist/main/video/video.js +1 -0
- package/dist/main/video/videoStreamTrackProcessor.js +7 -4
- package/dist/workers/global.d.js +0 -0
- package/dist/workers/mediaWorker.d.ts +1 -0
- package/dist/workers/mediaWorker.js +188 -0
- package/dist/workers/tsconfig.tsbuildinfo +1 -1
- package/package.json +21 -13
- package/dist/main/video/canvasRenderUtils.d.ts +0 -27
- package/dist/main/video/segmenters/index.d.ts +0 -1
- package/dist/main/video/segmenters/index.js +0 -1
- package/dist/workers/index.js +0 -1
- /package/dist/{workers/index.d.ts → common/backends/webgl/webgl.mock.d.ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 18.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- b818713: Bump @mediapipe/tasks-vision to v0.10.14 and remove workaround for
|
|
8
|
+
WebWorker issue
|
|
9
|
+
|
|
10
|
+
## 18.0.0
|
|
11
|
+
|
|
12
|
+
### Major Changes
|
|
13
|
+
|
|
14
|
+
- bfb8058: Replace @mediapipe/selfie_segmentation with @mediapipe/tasks-vision
|
|
15
|
+
|
|
16
|
+
- Replace @mediapipe/selfie_segmentation with @mediapipe/tasks-vision
|
|
17
|
+
- Move video processing into WebWorker
|
|
18
|
+
- Replace Canvas 2D with WebGL implementation
|
|
19
|
+
- Replace Gaussian Blur with Dual filtering blur
|
|
20
|
+
- Add Segmentation Mask Smoothing
|
|
21
|
+
|
|
22
|
+
### Minor Changes
|
|
23
|
+
|
|
24
|
+
- 0a5a136: Fix getting the correct size of the MediaStream from a video element
|
|
25
|
+
- 6b75621: Return the processing result when no output provided
|
|
26
|
+
- 218a2c0: Refactor lazy props
|
|
27
|
+
- 9bcfc68: Avoid processing a frame which is disposed
|
|
28
|
+
- 1543a66: Allow passing Background Image Binary
|
|
29
|
+
|
|
30
|
+
- Add `update()` API to allow passing Background Image for background
|
|
31
|
+
replacement
|
|
32
|
+
|
|
33
|
+
- 68e8ec1: Standardize input size and ratio
|
|
34
|
+
|
|
35
|
+
### Patch Changes
|
|
36
|
+
|
|
37
|
+
- Updated dependencies [bfb8058]
|
|
38
|
+
- @pexip/utils@16.11.0
|
|
39
|
+
|
|
3
40
|
## 17.4.0
|
|
4
41
|
|
|
5
42
|
### Minor Changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RendererOptions } from '../../types/render';
|
|
2
|
+
import type { TextureInfo } from './types';
|
|
3
|
+
export declare const createBlendRenderer: (processor: OffscreenCanvas) => {
|
|
4
|
+
render: ({ mask, prevMask, foreground, background, }: {
|
|
5
|
+
prevMask?: TextureInfo | undefined;
|
|
6
|
+
mask: TextureInfo;
|
|
7
|
+
foreground: TextureInfo;
|
|
8
|
+
background: TextureInfo;
|
|
9
|
+
}, options: RendererOptions) => TextureInfo;
|
|
10
|
+
release: () => void;
|
|
11
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { assert } from '../../utils';
|
|
2
|
+
import { m3 } from '../../m3';
|
|
3
|
+
import { createGLProgram, createAndSetupTexture } from './webglUtils';
|
|
4
|
+
import { vertexShaderSource } from './shaders/vertexShader';
|
|
5
|
+
import { blendFragmentShaderSource } from './shaders/blendFragmentShader';
|
|
6
|
+
export const createBlendRenderer = (processor) => {
|
|
7
|
+
const { gl, program, attribLocations, uniformLocations, release } = createGLProgram(processor, {
|
|
8
|
+
attribs: {
|
|
9
|
+
position: 'a_position',
|
|
10
|
+
texCoord: 'a_texCoord',
|
|
11
|
+
},
|
|
12
|
+
uniforms: {
|
|
13
|
+
matrix: 'u_matrix',
|
|
14
|
+
frameBackground: 'u_frame_background',
|
|
15
|
+
frameForeground: 'u_frame_foreground',
|
|
16
|
+
mask: 'u_mask',
|
|
17
|
+
maskCombineRatio: 'u_mask_combine_ratio',
|
|
18
|
+
foregroundThreshold: 'u_foreground_threshold',
|
|
19
|
+
prevMask: 'u_mask_prev',
|
|
20
|
+
},
|
|
21
|
+
vertexSrc: vertexShaderSource,
|
|
22
|
+
fragmentSrc: blendFragmentShaderSource,
|
|
23
|
+
});
|
|
24
|
+
gl.enable(gl.BLEND);
|
|
25
|
+
const vao = gl.createVertexArray();
|
|
26
|
+
gl.bindVertexArray(vao);
|
|
27
|
+
// Position
|
|
28
|
+
const positionBuffer = gl.createBuffer();
|
|
29
|
+
gl.enableVertexAttribArray(attribLocations.position);
|
|
30
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
|
31
|
+
gl.vertexAttribPointer(attribLocations.position, 2, gl.FLOAT, false, 0, 0);
|
|
32
|
+
gl.bufferData(gl.ARRAY_BUFFER,
|
|
33
|
+
// prettier-ignore
|
|
34
|
+
new Float32Array([
|
|
35
|
+
0, 0,
|
|
36
|
+
0, 1,
|
|
37
|
+
1, 0,
|
|
38
|
+
1, 0,
|
|
39
|
+
0, 1,
|
|
40
|
+
1, 1,
|
|
41
|
+
]), gl.STATIC_DRAW);
|
|
42
|
+
// Texture coordinates for the rectangle
|
|
43
|
+
const texCoordBuffer = gl.createBuffer();
|
|
44
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
|
|
45
|
+
gl.bufferData(gl.ARRAY_BUFFER,
|
|
46
|
+
// prettier-ignore
|
|
47
|
+
new Float32Array([
|
|
48
|
+
0, 0,
|
|
49
|
+
0, 1,
|
|
50
|
+
1, 0,
|
|
51
|
+
1, 0,
|
|
52
|
+
0, 1,
|
|
53
|
+
1, 1,
|
|
54
|
+
]), gl.STATIC_DRAW);
|
|
55
|
+
gl.enableVertexAttribArray(attribLocations.texCoord);
|
|
56
|
+
gl.vertexAttribPointer(attribLocations.texCoord, 2, gl.FLOAT, false, 0, 0);
|
|
57
|
+
const outputTexture = createAndSetupTexture(gl);
|
|
58
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, processor.width, processor.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
|
59
|
+
const frameBuffer = gl.createFramebuffer();
|
|
60
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
|
|
61
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, outputTexture, 0);
|
|
62
|
+
return {
|
|
63
|
+
render: ({ mask, prevMask, foreground, background, }, options) => {
|
|
64
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
|
|
65
|
+
gl.viewport(0, 0, processor.width, processor.height);
|
|
66
|
+
gl.useProgram(program);
|
|
67
|
+
gl.bindVertexArray(vao);
|
|
68
|
+
// Textures
|
|
69
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
70
|
+
gl.uniform1i(uniformLocations.frameForeground, 0);
|
|
71
|
+
gl.bindTexture(gl.TEXTURE_2D, foreground.texture);
|
|
72
|
+
gl.activeTexture(gl.TEXTURE1);
|
|
73
|
+
gl.uniform1i(uniformLocations.frameBackground, 1);
|
|
74
|
+
gl.bindTexture(gl.TEXTURE_2D, background.texture);
|
|
75
|
+
gl.activeTexture(gl.TEXTURE2);
|
|
76
|
+
gl.uniform1i(uniformLocations.mask, 2);
|
|
77
|
+
gl.bindTexture(gl.TEXTURE_2D, mask.texture);
|
|
78
|
+
gl.activeTexture(gl.TEXTURE3);
|
|
79
|
+
gl.uniform1i(uniformLocations.prevMask, 3);
|
|
80
|
+
gl.bindTexture(gl.TEXTURE_2D, prevMask?.texture ?? mask.texture);
|
|
81
|
+
// No Flip Y Coordinate for drawing on the frame buffer
|
|
82
|
+
// Project, position and scale to full-size of the canvas
|
|
83
|
+
const matrix = m3.projection(1, 1, 1);
|
|
84
|
+
gl.uniformMatrix3fv(uniformLocations.matrix, false, matrix);
|
|
85
|
+
gl.uniform1f(uniformLocations.maskCombineRatio, options.maskCombineRatio);
|
|
86
|
+
gl.uniform1f(uniformLocations.foregroundThreshold, options.foregroundThreshold);
|
|
87
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
88
|
+
assert(outputTexture);
|
|
89
|
+
return {
|
|
90
|
+
texture: outputTexture,
|
|
91
|
+
width: processor.width,
|
|
92
|
+
height: processor.height,
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
release: () => {
|
|
96
|
+
gl.deleteBuffer(positionBuffer);
|
|
97
|
+
gl.deleteBuffer(texCoordBuffer);
|
|
98
|
+
gl.deleteTexture(outputTexture);
|
|
99
|
+
gl.deleteFramebuffer(frameBuffer);
|
|
100
|
+
gl.deleteVertexArray(vao);
|
|
101
|
+
release();
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { assert, clamping } from '../../utils';
|
|
2
|
+
import { m3 } from '../../m3';
|
|
3
|
+
import { createAndSetupTexture, createGLProgram } from './webglUtils';
|
|
4
|
+
import { vertexShaderSource } from './shaders/vertexShader';
|
|
5
|
+
import { dualFilterBlurFragmentShaderSource } from './shaders/dualFilterBlurFragmentShader';
|
|
6
|
+
const clampBlurAmount = clamping(1, 9);
|
|
7
|
+
export const createBlurRenderer = (processor, pass) => {
|
|
8
|
+
const { gl, program, attribLocations, uniformLocations, release } = createGLProgram(processor, {
|
|
9
|
+
attribs: {
|
|
10
|
+
position: 'a_position',
|
|
11
|
+
texCoord: 'a_texCoord',
|
|
12
|
+
},
|
|
13
|
+
uniforms: {
|
|
14
|
+
matrix: 'u_matrix',
|
|
15
|
+
downsampling: 'u_downsampling',
|
|
16
|
+
frame: 'u_frame',
|
|
17
|
+
},
|
|
18
|
+
vertexSrc: vertexShaderSource,
|
|
19
|
+
fragmentSrc: dualFilterBlurFragmentShaderSource,
|
|
20
|
+
});
|
|
21
|
+
const vao = gl.createVertexArray();
|
|
22
|
+
gl.bindVertexArray(vao);
|
|
23
|
+
// Position
|
|
24
|
+
const positionBuffer = gl.createBuffer();
|
|
25
|
+
gl.enableVertexAttribArray(attribLocations.position);
|
|
26
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
|
27
|
+
gl.vertexAttribPointer(attribLocations.position, 2, gl.FLOAT, false, 0, 0);
|
|
28
|
+
// Put a unit quad in the buffer
|
|
29
|
+
gl.bufferData(gl.ARRAY_BUFFER,
|
|
30
|
+
// prettier-ignore
|
|
31
|
+
new Float32Array([
|
|
32
|
+
0, 0,
|
|
33
|
+
0, 1,
|
|
34
|
+
1, 0,
|
|
35
|
+
1, 0,
|
|
36
|
+
0, 1,
|
|
37
|
+
1, 1,
|
|
38
|
+
]), gl.STATIC_DRAW);
|
|
39
|
+
// Texture coordinates for the rectangle
|
|
40
|
+
const texCoordBuffer = gl.createBuffer();
|
|
41
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
|
|
42
|
+
gl.bufferData(gl.ARRAY_BUFFER,
|
|
43
|
+
// prettier-ignore
|
|
44
|
+
new Float32Array([
|
|
45
|
+
0, 0,
|
|
46
|
+
0, 1,
|
|
47
|
+
1, 0,
|
|
48
|
+
1, 0,
|
|
49
|
+
0, 1,
|
|
50
|
+
1, 1,
|
|
51
|
+
]), gl.STATIC_DRAW);
|
|
52
|
+
gl.enableVertexAttribArray(attribLocations.texCoord);
|
|
53
|
+
gl.vertexAttribPointer(attribLocations.texCoord, 2, gl.FLOAT, false, 0, 0);
|
|
54
|
+
const setFrameBuffer = (frameBuffer, width, height) => {
|
|
55
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
|
|
56
|
+
gl.viewport(0, 0, width, height);
|
|
57
|
+
};
|
|
58
|
+
const createFramebufferAndTexture = (idx) => {
|
|
59
|
+
const texture = createAndSetupTexture(gl);
|
|
60
|
+
assert(texture);
|
|
61
|
+
// Avoid fractional value
|
|
62
|
+
const width = Math.trunc(processor.width / Math.pow(2, idx));
|
|
63
|
+
const height = Math.trunc(processor.height / Math.pow(2, idx));
|
|
64
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
|
65
|
+
const frameBuffer = gl.createFramebuffer();
|
|
66
|
+
assert(frameBuffer);
|
|
67
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
|
|
68
|
+
// Attach a texture to it.
|
|
69
|
+
const attachmentPoint = gl.COLOR_ATTACHMENT0;
|
|
70
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, attachmentPoint, gl.TEXTURE_2D, texture, 0);
|
|
71
|
+
return { texture, frameBuffer, width, height };
|
|
72
|
+
};
|
|
73
|
+
const generateBlurBuffers = (pass, offset = 0) => {
|
|
74
|
+
const length = Math.max(clampBlurAmount(offset + pass) - offset, 0);
|
|
75
|
+
return Array.from({
|
|
76
|
+
length,
|
|
77
|
+
}).map((_, currentIdx) => createFramebufferAndTexture(offset + currentIdx));
|
|
78
|
+
};
|
|
79
|
+
// Generate multiple FrameBuffers, textures and the widths and heights are of size
|
|
80
|
+
// of half of the previous one. E.g. 640x480 -> 320x240 -> 160x120 -> 80x60
|
|
81
|
+
const blurBuffers = generateBlurBuffers(pass + 1);
|
|
82
|
+
return {
|
|
83
|
+
render: (frame, pass) => {
|
|
84
|
+
gl.useProgram(program);
|
|
85
|
+
gl.bindVertexArray(vao);
|
|
86
|
+
// Frame texture
|
|
87
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
88
|
+
gl.uniform1i(uniformLocations.frame, 0);
|
|
89
|
+
gl.bindTexture(gl.TEXTURE_2D, frame.texture);
|
|
90
|
+
// Dual filter blur
|
|
91
|
+
// Downsampling
|
|
92
|
+
gl.uniform1i(uniformLocations.downsampling, 1);
|
|
93
|
+
// Adjust pass number accordingly
|
|
94
|
+
const currentPass = pass + 1;
|
|
95
|
+
if (currentPass > blurBuffers.length) {
|
|
96
|
+
generateBlurBuffers(currentPass - blurBuffers.length, blurBuffers.length).forEach(buffer => blurBuffers.push(buffer));
|
|
97
|
+
}
|
|
98
|
+
while (currentPass < blurBuffers.length) {
|
|
99
|
+
const buffer = blurBuffers.pop();
|
|
100
|
+
gl.deleteTexture(buffer?.texture ?? null);
|
|
101
|
+
gl.deleteFramebuffer(buffer?.frameBuffer ?? null);
|
|
102
|
+
}
|
|
103
|
+
// Skip the first pass
|
|
104
|
+
for (let i = 1; i < currentPass; i++) {
|
|
105
|
+
const { frameBuffer = null, texture = null, width = processor.width, height = processor.height, } = blurBuffers[i] ?? {};
|
|
106
|
+
// Setup to draw to the frame buffer
|
|
107
|
+
setFrameBuffer(frameBuffer, width, height);
|
|
108
|
+
const matrix = m3.projection(1, 1, 1);
|
|
109
|
+
gl.uniformMatrix3fv(uniformLocations.matrix, false, matrix);
|
|
110
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
111
|
+
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
|
|
112
|
+
if (status !== gl.FRAMEBUFFER_COMPLETE) {
|
|
113
|
+
throw new Error(`downsampling FrameBuffer Status: ${status}`);
|
|
114
|
+
}
|
|
115
|
+
// for the next draw, use the texture we just rendered to.
|
|
116
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
117
|
+
}
|
|
118
|
+
// Upsampling
|
|
119
|
+
gl.uniform1i(uniformLocations.downsampling, 0);
|
|
120
|
+
// Skip the first pass
|
|
121
|
+
for (let i = pass - 1; i >= 0; i--) {
|
|
122
|
+
const { frameBuffer = null, texture = null, width = processor.width, height = processor.height, } = blurBuffers[i] ?? {};
|
|
123
|
+
// console.log(`Drawing upsampling pass ${i}, ${width}x${height}`);
|
|
124
|
+
// Setup to draw to the frame buffer
|
|
125
|
+
setFrameBuffer(frameBuffer, width, height);
|
|
126
|
+
const matrix = m3.projection(1, 1, 1);
|
|
127
|
+
gl.uniformMatrix3fv(uniformLocations.matrix, false, matrix);
|
|
128
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
129
|
+
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
|
|
130
|
+
if (status !== gl.FRAMEBUFFER_COMPLETE) {
|
|
131
|
+
throw new Error(`Upsampling FrameBuffer Status: ${status}`);
|
|
132
|
+
}
|
|
133
|
+
// for the next draw, use the texture we just rendered to.
|
|
134
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
texture: blurBuffers[0]?.texture ?? frame.texture,
|
|
138
|
+
width: blurBuffers[0]?.width ?? frame.width,
|
|
139
|
+
height: blurBuffers[0]?.height ?? frame.height,
|
|
140
|
+
};
|
|
141
|
+
},
|
|
142
|
+
release: () => {
|
|
143
|
+
blurBuffers.forEach(({ frameBuffer, texture }) => {
|
|
144
|
+
gl.deleteTexture(texture);
|
|
145
|
+
gl.deleteFramebuffer(frameBuffer);
|
|
146
|
+
});
|
|
147
|
+
gl.deleteBuffer(positionBuffer);
|
|
148
|
+
gl.deleteBuffer(texCoordBuffer);
|
|
149
|
+
gl.deleteVertexArray(vao);
|
|
150
|
+
release();
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { m3 } from '../../m3';
|
|
2
|
+
import { createGLProgram } from './webglUtils';
|
|
3
|
+
import { vertexShaderSource } from './shaders/vertexShader';
|
|
4
|
+
import { textureFragmentShaderSource } from './shaders/textureFragmentShader';
|
|
5
|
+
export const createCanvasRenderer = (processor) => {
|
|
6
|
+
const { gl, program, attribLocations, uniformLocations, release } = createGLProgram(processor, {
|
|
7
|
+
attribs: {
|
|
8
|
+
position: 'a_position',
|
|
9
|
+
texCoord: 'a_texCoord',
|
|
10
|
+
},
|
|
11
|
+
uniforms: {
|
|
12
|
+
matrix: 'u_matrix',
|
|
13
|
+
texture: 'u_texture',
|
|
14
|
+
},
|
|
15
|
+
vertexSrc: vertexShaderSource,
|
|
16
|
+
fragmentSrc: textureFragmentShaderSource,
|
|
17
|
+
});
|
|
18
|
+
const vao = gl.createVertexArray();
|
|
19
|
+
gl.bindVertexArray(vao);
|
|
20
|
+
// Position
|
|
21
|
+
const positionBuffer = gl.createBuffer();
|
|
22
|
+
gl.enableVertexAttribArray(attribLocations.position);
|
|
23
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
|
24
|
+
gl.vertexAttribPointer(attribLocations.position, 2, gl.FLOAT, false, 0, 0);
|
|
25
|
+
// Put a unit quad in the buffer
|
|
26
|
+
gl.bufferData(gl.ARRAY_BUFFER,
|
|
27
|
+
// prettier-ignore
|
|
28
|
+
new Float32Array([
|
|
29
|
+
0, 0,
|
|
30
|
+
0, 1,
|
|
31
|
+
1, 0,
|
|
32
|
+
1, 0,
|
|
33
|
+
0, 1,
|
|
34
|
+
1, 1,
|
|
35
|
+
]), gl.STATIC_DRAW);
|
|
36
|
+
// Texture coordinates for the rectangle
|
|
37
|
+
const texCoordBuffer = gl.createBuffer();
|
|
38
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
|
|
39
|
+
gl.bufferData(gl.ARRAY_BUFFER,
|
|
40
|
+
// prettier-ignore
|
|
41
|
+
new Float32Array([
|
|
42
|
+
0, 0,
|
|
43
|
+
0, 1,
|
|
44
|
+
1, 0,
|
|
45
|
+
1, 0,
|
|
46
|
+
0, 1,
|
|
47
|
+
1, 1,
|
|
48
|
+
]), gl.STATIC_DRAW);
|
|
49
|
+
gl.enableVertexAttribArray(attribLocations.texCoord);
|
|
50
|
+
gl.vertexAttribPointer(attribLocations.texCoord, 2, gl.FLOAT, false, 0, 0);
|
|
51
|
+
return {
|
|
52
|
+
render: (texture) => {
|
|
53
|
+
// Draw on the Canvas
|
|
54
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
55
|
+
gl.viewport(0, 0, processor.width, processor.height);
|
|
56
|
+
gl.clearColor(0, 0, 0, 1);
|
|
57
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
58
|
+
gl.useProgram(program);
|
|
59
|
+
gl.bindVertexArray(vao);
|
|
60
|
+
// Texture Unit setup
|
|
61
|
+
gl.uniform1i(uniformLocations.texture, 0);
|
|
62
|
+
// Project, position and scale to full-size of the canvas
|
|
63
|
+
const matrix = m3.projection(1, 1);
|
|
64
|
+
gl.uniformMatrix3fv(uniformLocations.matrix, false, matrix);
|
|
65
|
+
// Frame texture
|
|
66
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
67
|
+
gl.bindTexture(gl.TEXTURE_2D, texture.texture);
|
|
68
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
69
|
+
},
|
|
70
|
+
release: () => {
|
|
71
|
+
gl.deleteBuffer(positionBuffer);
|
|
72
|
+
gl.deleteBuffer(texCoordBuffer);
|
|
73
|
+
gl.deleteVertexArray(vao);
|
|
74
|
+
release();
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Renderer, RendererOptions } from '../../types/render';
|
|
2
|
+
import type { ImageMapIterable } from '../../types/media';
|
|
3
|
+
interface Options extends Partial<RendererOptions> {
|
|
4
|
+
backgroundImageInits?: ImageMapIterable;
|
|
5
|
+
onError?: (error: Error) => void;
|
|
6
|
+
}
|
|
7
|
+
export declare const createRenderer: (canvas?: OffscreenCanvas, { backgroundBlurAmount, backgroundImageUrl, edgeBlurAmount, effects, foregroundThreshold, maskCombineRatio, onError, backgroundImageInits, }?: Options) => Renderer;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { PROCESSING_WIDTH, PROCESSING_HEIGHT, EDGE_BLUR_AMOUNT, BACKGROUND_BLUR_AMOUNT, FOREGROUND_THRESHOLD, MASK_COMBINE_RATIO, RENDER_EFFECT, } from '../../constants';
|
|
2
|
+
import { createObjectUpdater, createRemoteImageBitmap } from '../../utils';
|
|
3
|
+
import { toTextureInfo } from './webglUtils';
|
|
4
|
+
import { createTextureRenderer } from './texture';
|
|
5
|
+
import { createBlurRenderer } from './blur';
|
|
6
|
+
import { createBlendRenderer } from './blender';
|
|
7
|
+
import { createCanvasRenderer } from './canvas';
|
|
8
|
+
const createLazyProps = (stateBuilder) => {
|
|
9
|
+
const props = {};
|
|
10
|
+
return new Proxy(props, {
|
|
11
|
+
get(target, p, receiver) {
|
|
12
|
+
const prop = target[p];
|
|
13
|
+
if (!prop) {
|
|
14
|
+
target[p] = stateBuilder[p]?.(props);
|
|
15
|
+
}
|
|
16
|
+
return Reflect.get(target, p, receiver);
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
export const createRenderer = (canvas = new OffscreenCanvas(PROCESSING_WIDTH, PROCESSING_HEIGHT), { backgroundBlurAmount = BACKGROUND_BLUR_AMOUNT, backgroundImageUrl = '', edgeBlurAmount = EDGE_BLUR_AMOUNT, effects = RENDER_EFFECT.None, foregroundThreshold = FOREGROUND_THRESHOLD, maskCombineRatio = MASK_COMBINE_RATIO, onError, backgroundImageInits, } = {}) => {
|
|
21
|
+
const rendererCreator = {
|
|
22
|
+
videoFrameRenderer: () => createTextureRenderer(canvas),
|
|
23
|
+
canvasRenderer: () => createCanvasRenderer(canvas),
|
|
24
|
+
backgroundImageRenderer: () => createTextureRenderer(canvas),
|
|
25
|
+
blendRenderer: () => createBlendRenderer(canvas),
|
|
26
|
+
blurRenderer: () => createBlurRenderer(canvas, props.backgroundBlurAmount),
|
|
27
|
+
maskBlurRenderer: () => createBlurRenderer(canvas, props.edgeBlurAmount),
|
|
28
|
+
release: (states) => () => Object.keys(states).forEach(key => key !== 'release' &&
|
|
29
|
+
states[key]?.release()),
|
|
30
|
+
};
|
|
31
|
+
let lazyProps = createLazyProps(rendererCreator);
|
|
32
|
+
const props = {
|
|
33
|
+
backgroundBlurAmount,
|
|
34
|
+
backgroundImageUrl,
|
|
35
|
+
edgeBlurAmount,
|
|
36
|
+
effects,
|
|
37
|
+
foregroundThreshold,
|
|
38
|
+
maskCombineRatio,
|
|
39
|
+
canvas,
|
|
40
|
+
backgroundImages: createRemoteImageBitmap(backgroundImageInits),
|
|
41
|
+
// TODO: refactor to avoid repeated codes
|
|
42
|
+
get videoFrameRenderer() {
|
|
43
|
+
return lazyProps.videoFrameRenderer;
|
|
44
|
+
},
|
|
45
|
+
get canvasRenderer() {
|
|
46
|
+
return lazyProps.canvasRenderer;
|
|
47
|
+
},
|
|
48
|
+
get backgroundImageRenderer() {
|
|
49
|
+
return lazyProps.backgroundImageRenderer;
|
|
50
|
+
},
|
|
51
|
+
get blendRenderer() {
|
|
52
|
+
return lazyProps.blendRenderer;
|
|
53
|
+
},
|
|
54
|
+
get blurRenderer() {
|
|
55
|
+
return lazyProps.blurRenderer;
|
|
56
|
+
},
|
|
57
|
+
// TODO: Impl edge blur
|
|
58
|
+
get maskBlurRenderer() {
|
|
59
|
+
return lazyProps.maskBlurRenderer;
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
const updateProps = createObjectUpdater(props);
|
|
63
|
+
const update = options => {
|
|
64
|
+
if (options.backgroundImage) {
|
|
65
|
+
props.backgroundImages.setImage(options.backgroundImage.key, options.backgroundImage.image);
|
|
66
|
+
}
|
|
67
|
+
updateProps(options);
|
|
68
|
+
};
|
|
69
|
+
const handleError = (error) => {
|
|
70
|
+
if (error instanceof Error) {
|
|
71
|
+
return onError?.(error);
|
|
72
|
+
}
|
|
73
|
+
throw error;
|
|
74
|
+
};
|
|
75
|
+
const init = options => {
|
|
76
|
+
if (options) {
|
|
77
|
+
update(options);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const render = (mask, frame, options) => {
|
|
81
|
+
if (options) {
|
|
82
|
+
updateProps(options);
|
|
83
|
+
}
|
|
84
|
+
const frameTexture = props.videoFrameRenderer.render(frame);
|
|
85
|
+
let background = frameTexture;
|
|
86
|
+
if (props.effects === 'blur' && props.backgroundBlurAmount) {
|
|
87
|
+
background = props.blurRenderer.render(frameTexture, props.backgroundBlurAmount);
|
|
88
|
+
}
|
|
89
|
+
else if (props?.effects === 'overlay' && props.backgroundImageUrl) {
|
|
90
|
+
// Keep rendering with video frame until we get the background image
|
|
91
|
+
const backgroundImage = props.backgroundImages.getImage(props.backgroundImageUrl);
|
|
92
|
+
if (backgroundImage) {
|
|
93
|
+
background = props.backgroundImageRenderer.render({
|
|
94
|
+
frame: backgroundImage,
|
|
95
|
+
timestamp: performance.now(),
|
|
96
|
+
width: backgroundImage.width,
|
|
97
|
+
height: backgroundImage.height,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
props.backgroundImages
|
|
102
|
+
.tryFetchingImage(props.backgroundImageUrl)
|
|
103
|
+
.catch(error => {
|
|
104
|
+
handleError(new Error(`FailedToLoadBackgroundImage: ${props.backgroundImageUrl}`, { cause: error }));
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const blendedFrameTexture = props.blendRenderer.render({
|
|
109
|
+
prevMask: props.prevMask && toTextureInfo(props.prevMask),
|
|
110
|
+
mask: toTextureInfo(mask),
|
|
111
|
+
foreground: frameTexture,
|
|
112
|
+
background,
|
|
113
|
+
}, props);
|
|
114
|
+
props.canvasRenderer.render(blendedFrameTexture);
|
|
115
|
+
props.prevMask?.close();
|
|
116
|
+
props.prevMask = mask.clone();
|
|
117
|
+
};
|
|
118
|
+
const release = () => {
|
|
119
|
+
lazyProps.release();
|
|
120
|
+
lazyProps = createLazyProps(rendererCreator);
|
|
121
|
+
props.backgroundImages.clear();
|
|
122
|
+
props.prevMask?.close();
|
|
123
|
+
props.prevMask = undefined;
|
|
124
|
+
};
|
|
125
|
+
return { init, render, release, update };
|
|
126
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const blendFragmentShaderSource = "#version 300 es\nprecision mediump float;\n\n// our textures\nuniform sampler2D u_mask;\nuniform sampler2D u_mask_prev;\nuniform sampler2D u_frame_foreground;\nuniform sampler2D u_frame_background;\n\n// control flags\nuniform float u_mask_combine_ratio;\nuniform float u_foreground_threshold;\n\nin vec2 v_texCoord;\n\n// we need to declare an output for the fragment shader\nout vec4 outColor;\n\n// Source: https://github.com/google/mediapipe/blob/8750c3dca1bc00fbe47acf501b7c9c61f3f3bd3f/mediapipe/calculators/image/segmentation_smoothing_calculator.cc#L377\nfloat SmoothingMask(sampler2D prevMask, sampler2D currMask, vec2 uv, float ratio) {\n float prevMaskValue = texture(prevMask, uv).r;\n float newMaskValue = texture(currMask, uv).r;\n\n /*\n * Assume p := newMaskValue\n * H(p) := 1 + (p * log(p) + (1-p) * log(1-p)) / log(2)\n * uncertainty alpha(p) =\n * Clamp(1 - (1 - H(p)) * (1 - H(p)), 0, 1) [squaring the\n * uncertainty]\n *\n * The following polynomial approximates uncertainty alpha as a\n * function of (p + 0.5):\n */\n const float c1 = 5.68842;\n const float c2 = -0.748699;\n const float c3 = -57.8051;\n const float c4 = 291.309;\n const float c5 = -624.717;\n float t = newMaskValue - 0.5;\n float x = t * t;\n\n float uncertainty =\n 1.0 - min(1.0, x * (c1 + x * (c2 + x * (c3 + x * (c4 + x * c5)))));\n\n float outputValue = newMaskValue + (prevMaskValue - newMaskValue) *\n (uncertainty * ratio);\n return outputValue;\n}\n\nvoid main() {\n vec2 halfpixel = 0.5 / vec2(textureSize(u_frame_background, 0));\n\n vec4 background = texture(u_frame_background, v_texCoord);\n vec4 foreground = texture(u_frame_foreground, v_texCoord);\n float maskAlpha = SmoothingMask(u_mask_prev, u_mask, v_texCoord, u_mask_combine_ratio);\n float filteredMask = maskAlpha;\n if (maskAlpha < u_foreground_threshold) {\n filteredMask = 0.0;\n }\n\n outColor = mix(background, foreground, filteredMask);\n}\n";
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export const blendFragmentShaderSource = `#version 300 es
|
|
2
|
+
precision mediump float;
|
|
3
|
+
|
|
4
|
+
// our textures
|
|
5
|
+
uniform sampler2D u_mask;
|
|
6
|
+
uniform sampler2D u_mask_prev;
|
|
7
|
+
uniform sampler2D u_frame_foreground;
|
|
8
|
+
uniform sampler2D u_frame_background;
|
|
9
|
+
|
|
10
|
+
// control flags
|
|
11
|
+
uniform float u_mask_combine_ratio;
|
|
12
|
+
uniform float u_foreground_threshold;
|
|
13
|
+
|
|
14
|
+
in vec2 v_texCoord;
|
|
15
|
+
|
|
16
|
+
// we need to declare an output for the fragment shader
|
|
17
|
+
out vec4 outColor;
|
|
18
|
+
|
|
19
|
+
// Source: https://github.com/google/mediapipe/blob/8750c3dca1bc00fbe47acf501b7c9c61f3f3bd3f/mediapipe/calculators/image/segmentation_smoothing_calculator.cc#L377
|
|
20
|
+
float SmoothingMask(sampler2D prevMask, sampler2D currMask, vec2 uv, float ratio) {
|
|
21
|
+
float prevMaskValue = texture(prevMask, uv).r;
|
|
22
|
+
float newMaskValue = texture(currMask, uv).r;
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
* Assume p := newMaskValue
|
|
26
|
+
* H(p) := 1 + (p * log(p) + (1-p) * log(1-p)) / log(2)
|
|
27
|
+
* uncertainty alpha(p) =
|
|
28
|
+
* Clamp(1 - (1 - H(p)) * (1 - H(p)), 0, 1) [squaring the
|
|
29
|
+
* uncertainty]
|
|
30
|
+
*
|
|
31
|
+
* The following polynomial approximates uncertainty alpha as a
|
|
32
|
+
* function of (p + 0.5):
|
|
33
|
+
*/
|
|
34
|
+
const float c1 = 5.68842;
|
|
35
|
+
const float c2 = -0.748699;
|
|
36
|
+
const float c3 = -57.8051;
|
|
37
|
+
const float c4 = 291.309;
|
|
38
|
+
const float c5 = -624.717;
|
|
39
|
+
float t = newMaskValue - 0.5;
|
|
40
|
+
float x = t * t;
|
|
41
|
+
|
|
42
|
+
float uncertainty =
|
|
43
|
+
1.0 - min(1.0, x * (c1 + x * (c2 + x * (c3 + x * (c4 + x * c5)))));
|
|
44
|
+
|
|
45
|
+
float outputValue = newMaskValue + (prevMaskValue - newMaskValue) *
|
|
46
|
+
(uncertainty * ratio);
|
|
47
|
+
return outputValue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
void main() {
|
|
51
|
+
vec2 halfpixel = 0.5 / vec2(textureSize(u_frame_background, 0));
|
|
52
|
+
|
|
53
|
+
vec4 background = texture(u_frame_background, v_texCoord);
|
|
54
|
+
vec4 foreground = texture(u_frame_foreground, v_texCoord);
|
|
55
|
+
float maskAlpha = SmoothingMask(u_mask_prev, u_mask, v_texCoord, u_mask_combine_ratio);
|
|
56
|
+
float filteredMask = maskAlpha;
|
|
57
|
+
if (maskAlpha < u_foreground_threshold) {
|
|
58
|
+
filteredMask = 0.0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
outColor = mix(background, foreground, filteredMask);
|
|
62
|
+
}
|
|
63
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const dualFilterBlurFragmentShaderSource = "#version 300 es\nprecision mediump float;\n\n// our textures\nuniform sampler2D u_frame;\n\n// control flags\nuniform int u_downsampling;\n\nin vec2 v_texCoord;\n\n// we need to declare an output for the fragment shader\nout vec4 outColor;\n\nvec4 Downsample(sampler2D image, vec2 uv, vec2 halfpixel) {\n vec4 sum = texture(image, uv) * 4.0;\n sum += texture(image, uv - halfpixel.xy);\n sum += texture(image, uv + halfpixel.xy);\n sum += texture(image, uv + vec2(halfpixel.x, -halfpixel.y));\n sum += texture(image, uv - vec2(halfpixel.x, -halfpixel.y));\n return sum / 8.0;\n}\n\nvec4 Upsample(sampler2D image, vec2 uv, vec2 halfpixel) {\n vec4 sum = texture(image, uv + vec2(-halfpixel.x * 2.0, 0.0));\n sum += texture(image, uv + vec2(-halfpixel.x, halfpixel.y)) * 2.0;\n sum += texture(image, uv + vec2(0.0, halfpixel.y * 2.0));\n sum += texture(image, uv + vec2(halfpixel.x, halfpixel.y)) * 2.0;\n sum += texture(image, uv + vec2(halfpixel.x * 2.0, 0.0));\n sum += texture(image, uv + vec2(halfpixel.x, -halfpixel.y)) * 2.0;\n sum += texture(image, uv + vec2(0.0, -halfpixel.y * 2.0));\n sum += texture(image, uv + vec2(-halfpixel.x, -halfpixel.y)) * 2.0;\n return sum / 12.0;\n}\n\nvoid main() {\n vec2 halfpixel = 0.5 / vec2(textureSize(u_frame, 0));\n\n if (u_downsampling == 1) {\n outColor = Downsample(u_frame, v_texCoord, halfpixel);\n return;\n }\n outColor = Upsample(u_frame, v_texCoord, halfpixel);\n}\n";
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export const dualFilterBlurFragmentShaderSource = `#version 300 es
|
|
2
|
+
precision mediump float;
|
|
3
|
+
|
|
4
|
+
// our textures
|
|
5
|
+
uniform sampler2D u_frame;
|
|
6
|
+
|
|
7
|
+
// control flags
|
|
8
|
+
uniform int u_downsampling;
|
|
9
|
+
|
|
10
|
+
in vec2 v_texCoord;
|
|
11
|
+
|
|
12
|
+
// we need to declare an output for the fragment shader
|
|
13
|
+
out vec4 outColor;
|
|
14
|
+
|
|
15
|
+
vec4 Downsample(sampler2D image, vec2 uv, vec2 halfpixel) {
|
|
16
|
+
vec4 sum = texture(image, uv) * 4.0;
|
|
17
|
+
sum += texture(image, uv - halfpixel.xy);
|
|
18
|
+
sum += texture(image, uv + halfpixel.xy);
|
|
19
|
+
sum += texture(image, uv + vec2(halfpixel.x, -halfpixel.y));
|
|
20
|
+
sum += texture(image, uv - vec2(halfpixel.x, -halfpixel.y));
|
|
21
|
+
return sum / 8.0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
vec4 Upsample(sampler2D image, vec2 uv, vec2 halfpixel) {
|
|
25
|
+
vec4 sum = texture(image, uv + vec2(-halfpixel.x * 2.0, 0.0));
|
|
26
|
+
sum += texture(image, uv + vec2(-halfpixel.x, halfpixel.y)) * 2.0;
|
|
27
|
+
sum += texture(image, uv + vec2(0.0, halfpixel.y * 2.0));
|
|
28
|
+
sum += texture(image, uv + vec2(halfpixel.x, halfpixel.y)) * 2.0;
|
|
29
|
+
sum += texture(image, uv + vec2(halfpixel.x * 2.0, 0.0));
|
|
30
|
+
sum += texture(image, uv + vec2(halfpixel.x, -halfpixel.y)) * 2.0;
|
|
31
|
+
sum += texture(image, uv + vec2(0.0, -halfpixel.y * 2.0));
|
|
32
|
+
sum += texture(image, uv + vec2(-halfpixel.x, -halfpixel.y)) * 2.0;
|
|
33
|
+
return sum / 12.0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
void main() {
|
|
37
|
+
vec2 halfpixel = 0.5 / vec2(textureSize(u_frame, 0));
|
|
38
|
+
|
|
39
|
+
if (u_downsampling == 1) {
|
|
40
|
+
outColor = Downsample(u_frame, v_texCoord, halfpixel);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
outColor = Upsample(u_frame, v_texCoord, halfpixel);
|
|
44
|
+
}
|
|
45
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const textureFragmentShaderSource = "#version 300 es\nprecision mediump float;\n\n// Passed in from the vertex shader.\nin vec2 v_texCoord;\n\n// The texture.\nuniform sampler2D u_texture;\n\n// we need to declare an output for the fragment shader\nout vec4 outColor;\n\nvoid main() {\n outColor = texture(u_texture, v_texCoord);\n}\n";
|