webgl-render-vap.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. /*
  2. * Tencent is pleased to support the open source community by making vap available.
  3. *
  4. * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
  5. *
  6. * Licensed under the MIT License (the "License"); you may not use this file except in
  7. * compliance with the License. You may obtain a copy of the License at
  8. *
  9. * http://opensource.org/licenses/MIT
  10. *
  11. * Unless required by applicable law or agreed to in writing, software distributed under the License is
  12. * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
  13. * either express or implied. See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. import {VapConfig} from "./type";
  17. import VapFrameParser from './vap-frame-parser';
  18. import * as glUtil from './gl-util';
  19. import VapVideo from './video';
  20. let clearTimer = null;
  21. let instances = {};
  22. const PER_SIZE = 9;
  23. function computeCoord(x:number, y:number, w:number, h:number, vw:number, vh:number) {
  24. // leftX rightX bottomY topY
  25. return [x / vw, (x + w) / vw, (vh - y - h) / vh, (vh - y) / vh]
  26. }
  27. export default class WebglRenderVap extends VapVideo {
  28. constructor(options:VapConfig) {
  29. super(options);
  30. this.insType = this.options.type;
  31. if (instances[this.insType]) {
  32. this.instance = instances[this.insType]
  33. } else {
  34. this.instance = instances[this.insType] = {}
  35. }
  36. this.textures = [];
  37. this.buffers = [];
  38. this.shaders = [];
  39. this.init();
  40. }
  41. private insType;
  42. private textures;
  43. private buffers;
  44. private shaders;
  45. private vapFrameParser;
  46. private resources;
  47. private instance;
  48. private program;
  49. private videoTexture;
  50. private aPosition;
  51. private aTexCoord;
  52. private aAlphaTexCoord;
  53. private _imagePos;
  54. async init() {
  55. this.setCanvas();
  56. if (this.options.config) {
  57. try {
  58. this.vapFrameParser = await new VapFrameParser(this.options.config, this.options).init();
  59. this.resources = this.vapFrameParser.srcData;
  60. } catch (e) {
  61. console.error('[Alpha video] parse vap frame error.', e);
  62. }
  63. }
  64. this.resources = this.resources || {};
  65. this.initWebGL();
  66. this.play();
  67. }
  68. setCanvas() {
  69. let canvas = this.instance.canvas;
  70. const { width, height } = this.options;
  71. if (!canvas) {
  72. canvas = this.instance.canvas = document.createElement('canvas');
  73. }
  74. canvas.width = width;
  75. canvas.height = height;
  76. this.container.appendChild(canvas);
  77. }
  78. initWebGL() {
  79. const { canvas } = this.instance;
  80. let { gl, vertexShader, fragmentShader, program } = this.instance;
  81. if (!canvas) {
  82. return
  83. }
  84. if (!gl) {
  85. this.instance.gl = gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
  86. gl.disable(gl.BLEND);
  87. gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
  88. gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
  89. }
  90. // 清除界面,解决同类型type切换MP4时,第一帧是上一个mp4最后一帧的问题
  91. gl.clear(gl.COLOR_BUFFER_BIT);
  92. if (gl) {
  93. gl.viewport(0, 0, canvas.width, canvas.height);
  94. if (!vertexShader) {
  95. vertexShader = this.instance.vertexShader = this.initVertexShader();
  96. }
  97. if (!fragmentShader) {
  98. fragmentShader = this.instance.fragmentShader = this.initFragmentShader();
  99. }
  100. if (!program) {
  101. program = this.instance.program = glUtil.createProgram(gl, vertexShader, fragmentShader);
  102. }
  103. this.program = program;
  104. this.initTexture();
  105. this.initVideoTexture();
  106. return gl;
  107. }
  108. }
  109. /**
  110. * 顶点着色器
  111. */
  112. initVertexShader() {
  113. const { gl } = this.instance;
  114. return glUtil.createShader(
  115. gl,
  116. gl.VERTEX_SHADER,
  117. `attribute vec2 a_position; // 接受顶点坐标
  118. attribute vec2 a_texCoord; // 接受纹理坐标
  119. attribute vec2 a_alpha_texCoord; // 接受纹理坐标
  120. varying vec2 v_alpha_texCoord; // 接受纹理坐标
  121. varying vec2 v_texcoord; // 传递纹理坐标给片元着色器
  122. void main(void){
  123. gl_Position = vec4(a_position, 0.0, 1.0); // 设置坐标
  124. v_texcoord = a_texCoord; // 设置纹理坐标
  125. v_alpha_texCoord = a_alpha_texCoord; // 设置纹理坐标
  126. }`
  127. );
  128. }
  129. /**
  130. * 片元着色器
  131. */
  132. initFragmentShader() {
  133. const { gl } = this.instance;
  134. const bgColor = `vec4(texture2D(u_image_video, v_texcoord).rgb, texture2D(u_image_video,v_alpha_texCoord).r);`;
  135. const textureSize = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) - 1;
  136. // const textureSize =0
  137. let sourceTexure = '';
  138. let sourceUniform = '';
  139. if (textureSize > 0) {
  140. const imgColor = [];
  141. const samplers = [];
  142. for (let i = 0; i < textureSize; i++) {
  143. imgColor.push(
  144. `if(ndx == ${i + 1}){
  145. color = texture2D(u_image${i + 1},uv);
  146. }`
  147. )
  148. samplers.push(`uniform sampler2D u_image${i + 1};`)
  149. }
  150. sourceUniform = `
  151. ${samplers.join('\n')}
  152. uniform float image_pos[${textureSize * PER_SIZE}];
  153. vec4 getSampleFromArray(int ndx, vec2 uv) {
  154. vec4 color;
  155. ${imgColor.join(' else ')}
  156. return color;
  157. }
  158. `;
  159. sourceTexure = `
  160. vec4 srcColor,maskColor;
  161. vec2 srcTexcoord,maskTexcoord;
  162. int srcIndex;
  163. float x1,x2,y1,y2,mx1,mx2,my1,my2; //显示的区域
  164. for(int i=0;i<${textureSize * PER_SIZE};i+= ${PER_SIZE}){
  165. if ((int(image_pos[i]) > 0)) {
  166. srcIndex = int(image_pos[i]);
  167. x1 = image_pos[i+1];
  168. x2 = image_pos[i+2];
  169. y1 = image_pos[i+3];
  170. y2 = image_pos[i+4];
  171. mx1 = image_pos[i+5];
  172. mx2 = image_pos[i+6];
  173. my1 = image_pos[i+7];
  174. my2 = image_pos[i+8];
  175. if (v_texcoord.s>x1 && v_texcoord.s<x2 && v_texcoord.t>y1 && v_texcoord.t<y2) {
  176. srcTexcoord = vec2((v_texcoord.s-x1)/(x2-x1),(v_texcoord.t-y1)/(y2-y1));
  177. maskTexcoord = vec2(mx1+srcTexcoord.s*(mx2-mx1),my1+srcTexcoord.t*(my2-my1));
  178. srcColor = getSampleFromArray(srcIndex,srcTexcoord);
  179. maskColor = texture2D(u_image_video, maskTexcoord);
  180. srcColor.a = srcColor.a*(maskColor.r);
  181. bgColor = vec4(srcColor.rgb*srcColor.a,srcColor.a) + (1.0-srcColor.a)*bgColor;
  182. }
  183. }
  184. }
  185. `;
  186. }
  187. const fragmentSharder = `
  188. precision lowp float;
  189. varying vec2 v_texcoord;
  190. varying vec2 v_alpha_texCoord;
  191. uniform sampler2D u_image_video;
  192. ${sourceUniform}
  193. void main(void) {
  194. vec4 bgColor = ${bgColor}
  195. ${sourceTexure}
  196. // bgColor = texture2D(u_image[0], v_texcoord);
  197. gl_FragColor = bgColor;
  198. }
  199. `;
  200. return glUtil.createShader(gl, gl.FRAGMENT_SHADER, fragmentSharder)
  201. }
  202. initTexture() {
  203. const { gl } = this.instance;
  204. let i = 1;
  205. if (!this.vapFrameParser || !this.vapFrameParser.srcData) {
  206. return
  207. }
  208. const resources = this.vapFrameParser.srcData;
  209. for (const key in resources) {
  210. const resource = resources[key];
  211. this.textures.push(glUtil.createTexture(gl, i, resource.img));
  212. const sampler = gl.getUniformLocation(this.program, `u_image${i}`);
  213. gl.uniform1i(sampler, i);
  214. this.vapFrameParser.textureMap[resource.srcId] = i++;
  215. }
  216. const dumpTexture = gl.createTexture();
  217. gl.activeTexture(gl.TEXTURE0);
  218. gl.bindTexture(gl.TEXTURE_2D, dumpTexture);
  219. gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
  220. this.videoTexture = glUtil.createTexture(gl, i);
  221. const sampler = gl.getUniformLocation(this.program, `u_image_video`);
  222. gl.uniform1i(sampler, i);
  223. }
  224. initVideoTexture() {
  225. const { gl } = this.instance;
  226. const vertexBuffer = gl.createBuffer();
  227. this.buffers.push(vertexBuffer);
  228. if (!this.vapFrameParser || !this.vapFrameParser.config || !this.vapFrameParser.config.info) {
  229. return
  230. }
  231. const info = this.vapFrameParser.config.info;
  232. const ver = [];
  233. const { videoW: vW, videoH: vH } = info;
  234. const [rgbX, rgbY, rgbW, rgbH] = info.rgbFrame;
  235. const [aX, aY, aW, aH] = info.aFrame;
  236. const rgbCoord = computeCoord(rgbX, rgbY, rgbW, rgbH, vW, vH);
  237. const aCoord = computeCoord(aX, aY, aW, aH, vW, vH);
  238. ver.push(...[-1, 1, rgbCoord[0], rgbCoord[3], aCoord[0], aCoord[3]]);
  239. ver.push(...[1, 1, rgbCoord[1], rgbCoord[3], aCoord[1], aCoord[3]]);
  240. ver.push(...[-1, -1, rgbCoord[0], rgbCoord[2], aCoord[0], aCoord[2]]);
  241. ver.push(...[1, -1, rgbCoord[1], rgbCoord[2], aCoord[1], aCoord[2]]);
  242. const view = new Float32Array(ver);
  243. gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
  244. gl.bufferData(gl.ARRAY_BUFFER, view, gl.STATIC_DRAW);
  245. this.aPosition = gl.getAttribLocation(this.program, 'a_position');
  246. gl.enableVertexAttribArray(this.aPosition);
  247. this.aTexCoord = gl.getAttribLocation(this.program, 'a_texCoord');
  248. gl.enableVertexAttribArray(this.aTexCoord);
  249. this.aAlphaTexCoord = gl.getAttribLocation(this.program, 'a_alpha_texCoord');
  250. gl.enableVertexAttribArray(this.aAlphaTexCoord);
  251. // 将缓冲区对象分配给a_position变量、a_texCoord变量
  252. const size = view.BYTES_PER_ELEMENT;
  253. gl.vertexAttribPointer(this.aPosition, 2, gl.FLOAT, false, size * 6, 0); // 顶点着色器位置
  254. gl.vertexAttribPointer(this.aTexCoord, 2, gl.FLOAT, false, size * 6, size * 2); // rgb像素位置
  255. gl.vertexAttribPointer(this.aAlphaTexCoord, 2, gl.FLOAT, false, size * 6, size * 4); // rgb像素位置
  256. }
  257. drawFrame(_, info) {
  258. const gl = this.instance.gl;
  259. if (!gl) {
  260. super.drawFrame(_, info);
  261. return
  262. }
  263. gl.clear(gl.COLOR_BUFFER_BIT);
  264. if (this.vapFrameParser) {
  265. const timePoint = (info && info.mediaTime >= 0) ? info.mediaTime : this.video.currentTime
  266. const frame = Math.floor(timePoint * this.options.fps);
  267. const frameData = this.vapFrameParser.getFrame(frame);
  268. let posArr = [];
  269. if (frameData && frameData.obj) {
  270. frameData.obj.forEach((frame, index) => {
  271. posArr[posArr.length] = +this.vapFrameParser.textureMap[frame.srcId];
  272. const info = this.vapFrameParser.config.info;
  273. const { videoW: vW, videoH: vH } = info;
  274. const [x, y, w, h] = frame.frame;
  275. const [mX, mY, mW, mH] = frame.mFrame;
  276. const coord = computeCoord(x, y, w, h, vW, vH);
  277. const mCoord = computeCoord(mX, mY, mW, mH, vW, vH);
  278. posArr = posArr.concat(coord).concat(mCoord);
  279. })
  280. }
  281. //
  282. const size = (gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) - 1) * PER_SIZE;
  283. posArr = posArr.concat(new Array(size - posArr.length).fill(0));
  284. this._imagePos = this._imagePos || gl.getUniformLocation(this.program, 'image_pos');
  285. gl.uniform1fv(this._imagePos, new Float32Array(posArr));
  286. }
  287. gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, this.video); // 指定二维纹理方式
  288. gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
  289. super.drawFrame(_, info);
  290. }
  291. destroy() {
  292. const { canvas, gl } = this.instance;
  293. if (this.textures && this.textures.length) {
  294. for (let i = 0; i < this.textures.length; i++) {
  295. gl.deleteTexture(this.textures[i]);
  296. }
  297. }
  298. if (canvas) {
  299. canvas.parentNode && canvas.parentNode.removeChild(canvas);
  300. }
  301. // glUtil.cleanWebGL(gl, this.shaders, this.program, this.textures, this.buffers)
  302. super.destroy();
  303. this.clearMemoryCache();
  304. }
  305. clearMemoryCache() {
  306. if (clearTimer) {
  307. clearTimeout(clearTimer);
  308. }
  309. clearTimer = setTimeout(() => {
  310. instances = {};
  311. }, 30 * 60 * 1000);
  312. }
  313. }