webgl.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. /* Copyright 2017 Mozilla Foundation
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. 'use strict';
  16. var sharedUtil = require('../shared/util.js');
  17. var displayDOMUtils = require('./dom_utils.js');
  18. var shadow = sharedUtil.shadow;
  19. var getDefaultSetting = displayDOMUtils.getDefaultSetting;
  20. var WebGLUtils = function WebGLUtilsClosure() {
  21. function loadShader(gl, code, shaderType) {
  22. var shader = gl.createShader(shaderType);
  23. gl.shaderSource(shader, code);
  24. gl.compileShader(shader);
  25. var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
  26. if (!compiled) {
  27. var errorMsg = gl.getShaderInfoLog(shader);
  28. throw new Error('Error during shader compilation: ' + errorMsg);
  29. }
  30. return shader;
  31. }
  32. function createVertexShader(gl, code) {
  33. return loadShader(gl, code, gl.VERTEX_SHADER);
  34. }
  35. function createFragmentShader(gl, code) {
  36. return loadShader(gl, code, gl.FRAGMENT_SHADER);
  37. }
  38. function createProgram(gl, shaders) {
  39. var program = gl.createProgram();
  40. for (var i = 0, ii = shaders.length; i < ii; ++i) {
  41. gl.attachShader(program, shaders[i]);
  42. }
  43. gl.linkProgram(program);
  44. var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
  45. if (!linked) {
  46. var errorMsg = gl.getProgramInfoLog(program);
  47. throw new Error('Error during program linking: ' + errorMsg);
  48. }
  49. return program;
  50. }
  51. function createTexture(gl, image, textureId) {
  52. gl.activeTexture(textureId);
  53. var texture = gl.createTexture();
  54. gl.bindTexture(gl.TEXTURE_2D, texture);
  55. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  56. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  57. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  58. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
  59. gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
  60. return texture;
  61. }
  62. var currentGL, currentCanvas;
  63. function generateGL() {
  64. if (currentGL) {
  65. return;
  66. }
  67. currentCanvas = document.createElement('canvas');
  68. currentGL = currentCanvas.getContext('webgl', { premultipliedalpha: false });
  69. }
  70. var smaskVertexShaderCode = '\
  71. attribute vec2 a_position; \
  72. attribute vec2 a_texCoord; \
  73. \
  74. uniform vec2 u_resolution; \
  75. \
  76. varying vec2 v_texCoord; \
  77. \
  78. void main() { \
  79. vec2 clipSpace = (a_position / u_resolution) * 2.0 - 1.0; \
  80. gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1); \
  81. \
  82. v_texCoord = a_texCoord; \
  83. } ';
  84. var smaskFragmentShaderCode = '\
  85. precision mediump float; \
  86. \
  87. uniform vec4 u_backdrop; \
  88. uniform int u_subtype; \
  89. uniform sampler2D u_image; \
  90. uniform sampler2D u_mask; \
  91. \
  92. varying vec2 v_texCoord; \
  93. \
  94. void main() { \
  95. vec4 imageColor = texture2D(u_image, v_texCoord); \
  96. vec4 maskColor = texture2D(u_mask, v_texCoord); \
  97. if (u_backdrop.a > 0.0) { \
  98. maskColor.rgb = maskColor.rgb * maskColor.a + \
  99. u_backdrop.rgb * (1.0 - maskColor.a); \
  100. } \
  101. float lum; \
  102. if (u_subtype == 0) { \
  103. lum = maskColor.a; \
  104. } else { \
  105. lum = maskColor.r * 0.3 + maskColor.g * 0.59 + \
  106. maskColor.b * 0.11; \
  107. } \
  108. imageColor.a *= lum; \
  109. imageColor.rgb *= imageColor.a; \
  110. gl_FragColor = imageColor; \
  111. } ';
  112. var smaskCache = null;
  113. function initSmaskGL() {
  114. var canvas, gl;
  115. generateGL();
  116. canvas = currentCanvas;
  117. currentCanvas = null;
  118. gl = currentGL;
  119. currentGL = null;
  120. var vertexShader = createVertexShader(gl, smaskVertexShaderCode);
  121. var fragmentShader = createFragmentShader(gl, smaskFragmentShaderCode);
  122. var program = createProgram(gl, [
  123. vertexShader,
  124. fragmentShader
  125. ]);
  126. gl.useProgram(program);
  127. var cache = {};
  128. cache.gl = gl;
  129. cache.canvas = canvas;
  130. cache.resolutionLocation = gl.getUniformLocation(program, 'u_resolution');
  131. cache.positionLocation = gl.getAttribLocation(program, 'a_position');
  132. cache.backdropLocation = gl.getUniformLocation(program, 'u_backdrop');
  133. cache.subtypeLocation = gl.getUniformLocation(program, 'u_subtype');
  134. var texCoordLocation = gl.getAttribLocation(program, 'a_texCoord');
  135. var texLayerLocation = gl.getUniformLocation(program, 'u_image');
  136. var texMaskLocation = gl.getUniformLocation(program, 'u_mask');
  137. var texCoordBuffer = gl.createBuffer();
  138. gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
  139. gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
  140. 0.0,
  141. 0.0,
  142. 1.0,
  143. 0.0,
  144. 0.0,
  145. 1.0,
  146. 0.0,
  147. 1.0,
  148. 1.0,
  149. 0.0,
  150. 1.0,
  151. 1.0
  152. ]), gl.STATIC_DRAW);
  153. gl.enableVertexAttribArray(texCoordLocation);
  154. gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);
  155. gl.uniform1i(texLayerLocation, 0);
  156. gl.uniform1i(texMaskLocation, 1);
  157. smaskCache = cache;
  158. }
  159. function composeSMask(layer, mask, properties) {
  160. var width = layer.width, height = layer.height;
  161. if (!smaskCache) {
  162. initSmaskGL();
  163. }
  164. var cache = smaskCache, canvas = cache.canvas, gl = cache.gl;
  165. canvas.width = width;
  166. canvas.height = height;
  167. gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
  168. gl.uniform2f(cache.resolutionLocation, width, height);
  169. if (properties.backdrop) {
  170. gl.uniform4f(cache.resolutionLocation, properties.backdrop[0], properties.backdrop[1], properties.backdrop[2], 1);
  171. } else {
  172. gl.uniform4f(cache.resolutionLocation, 0, 0, 0, 0);
  173. }
  174. gl.uniform1i(cache.subtypeLocation, properties.subtype === 'Luminosity' ? 1 : 0);
  175. var texture = createTexture(gl, layer, gl.TEXTURE0);
  176. var maskTexture = createTexture(gl, mask, gl.TEXTURE1);
  177. var buffer = gl.createBuffer();
  178. gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  179. gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
  180. 0,
  181. 0,
  182. width,
  183. 0,
  184. 0,
  185. height,
  186. 0,
  187. height,
  188. width,
  189. 0,
  190. width,
  191. height
  192. ]), gl.STATIC_DRAW);
  193. gl.enableVertexAttribArray(cache.positionLocation);
  194. gl.vertexAttribPointer(cache.positionLocation, 2, gl.FLOAT, false, 0, 0);
  195. gl.clearColor(0, 0, 0, 0);
  196. gl.enable(gl.BLEND);
  197. gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
  198. gl.clear(gl.COLOR_BUFFER_BIT);
  199. gl.drawArrays(gl.TRIANGLES, 0, 6);
  200. gl.flush();
  201. gl.deleteTexture(texture);
  202. gl.deleteTexture(maskTexture);
  203. gl.deleteBuffer(buffer);
  204. return canvas;
  205. }
  206. var figuresVertexShaderCode = '\
  207. attribute vec2 a_position; \
  208. attribute vec3 a_color; \
  209. \
  210. uniform vec2 u_resolution; \
  211. uniform vec2 u_scale; \
  212. uniform vec2 u_offset; \
  213. \
  214. varying vec4 v_color; \
  215. \
  216. void main() { \
  217. vec2 position = (a_position + u_offset) * u_scale; \
  218. vec2 clipSpace = (position / u_resolution) * 2.0 - 1.0; \
  219. gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1); \
  220. \
  221. v_color = vec4(a_color / 255.0, 1.0); \
  222. } ';
  223. var figuresFragmentShaderCode = '\
  224. precision mediump float; \
  225. \
  226. varying vec4 v_color; \
  227. \
  228. void main() { \
  229. gl_FragColor = v_color; \
  230. } ';
  231. var figuresCache = null;
  232. function initFiguresGL() {
  233. var canvas, gl;
  234. generateGL();
  235. canvas = currentCanvas;
  236. currentCanvas = null;
  237. gl = currentGL;
  238. currentGL = null;
  239. var vertexShader = createVertexShader(gl, figuresVertexShaderCode);
  240. var fragmentShader = createFragmentShader(gl, figuresFragmentShaderCode);
  241. var program = createProgram(gl, [
  242. vertexShader,
  243. fragmentShader
  244. ]);
  245. gl.useProgram(program);
  246. var cache = {};
  247. cache.gl = gl;
  248. cache.canvas = canvas;
  249. cache.resolutionLocation = gl.getUniformLocation(program, 'u_resolution');
  250. cache.scaleLocation = gl.getUniformLocation(program, 'u_scale');
  251. cache.offsetLocation = gl.getUniformLocation(program, 'u_offset');
  252. cache.positionLocation = gl.getAttribLocation(program, 'a_position');
  253. cache.colorLocation = gl.getAttribLocation(program, 'a_color');
  254. figuresCache = cache;
  255. }
  256. function drawFigures(width, height, backgroundColor, figures, context) {
  257. if (!figuresCache) {
  258. initFiguresGL();
  259. }
  260. var cache = figuresCache, canvas = cache.canvas, gl = cache.gl;
  261. canvas.width = width;
  262. canvas.height = height;
  263. gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
  264. gl.uniform2f(cache.resolutionLocation, width, height);
  265. var count = 0;
  266. var i, ii, rows;
  267. for (i = 0, ii = figures.length; i < ii; i++) {
  268. switch (figures[i].type) {
  269. case 'lattice':
  270. rows = figures[i].coords.length / figures[i].verticesPerRow | 0;
  271. count += (rows - 1) * (figures[i].verticesPerRow - 1) * 6;
  272. break;
  273. case 'triangles':
  274. count += figures[i].coords.length;
  275. break;
  276. }
  277. }
  278. var coords = new Float32Array(count * 2);
  279. var colors = new Uint8Array(count * 3);
  280. var coordsMap = context.coords, colorsMap = context.colors;
  281. var pIndex = 0, cIndex = 0;
  282. for (i = 0, ii = figures.length; i < ii; i++) {
  283. var figure = figures[i], ps = figure.coords, cs = figure.colors;
  284. switch (figure.type) {
  285. case 'lattice':
  286. var cols = figure.verticesPerRow;
  287. rows = ps.length / cols | 0;
  288. for (var row = 1; row < rows; row++) {
  289. var offset = row * cols + 1;
  290. for (var col = 1; col < cols; col++, offset++) {
  291. coords[pIndex] = coordsMap[ps[offset - cols - 1]];
  292. coords[pIndex + 1] = coordsMap[ps[offset - cols - 1] + 1];
  293. coords[pIndex + 2] = coordsMap[ps[offset - cols]];
  294. coords[pIndex + 3] = coordsMap[ps[offset - cols] + 1];
  295. coords[pIndex + 4] = coordsMap[ps[offset - 1]];
  296. coords[pIndex + 5] = coordsMap[ps[offset - 1] + 1];
  297. colors[cIndex] = colorsMap[cs[offset - cols - 1]];
  298. colors[cIndex + 1] = colorsMap[cs[offset - cols - 1] + 1];
  299. colors[cIndex + 2] = colorsMap[cs[offset - cols - 1] + 2];
  300. colors[cIndex + 3] = colorsMap[cs[offset - cols]];
  301. colors[cIndex + 4] = colorsMap[cs[offset - cols] + 1];
  302. colors[cIndex + 5] = colorsMap[cs[offset - cols] + 2];
  303. colors[cIndex + 6] = colorsMap[cs[offset - 1]];
  304. colors[cIndex + 7] = colorsMap[cs[offset - 1] + 1];
  305. colors[cIndex + 8] = colorsMap[cs[offset - 1] + 2];
  306. coords[pIndex + 6] = coords[pIndex + 2];
  307. coords[pIndex + 7] = coords[pIndex + 3];
  308. coords[pIndex + 8] = coords[pIndex + 4];
  309. coords[pIndex + 9] = coords[pIndex + 5];
  310. coords[pIndex + 10] = coordsMap[ps[offset]];
  311. coords[pIndex + 11] = coordsMap[ps[offset] + 1];
  312. colors[cIndex + 9] = colors[cIndex + 3];
  313. colors[cIndex + 10] = colors[cIndex + 4];
  314. colors[cIndex + 11] = colors[cIndex + 5];
  315. colors[cIndex + 12] = colors[cIndex + 6];
  316. colors[cIndex + 13] = colors[cIndex + 7];
  317. colors[cIndex + 14] = colors[cIndex + 8];
  318. colors[cIndex + 15] = colorsMap[cs[offset]];
  319. colors[cIndex + 16] = colorsMap[cs[offset] + 1];
  320. colors[cIndex + 17] = colorsMap[cs[offset] + 2];
  321. pIndex += 12;
  322. cIndex += 18;
  323. }
  324. }
  325. break;
  326. case 'triangles':
  327. for (var j = 0, jj = ps.length; j < jj; j++) {
  328. coords[pIndex] = coordsMap[ps[j]];
  329. coords[pIndex + 1] = coordsMap[ps[j] + 1];
  330. colors[cIndex] = colorsMap[cs[j]];
  331. colors[cIndex + 1] = colorsMap[cs[j] + 1];
  332. colors[cIndex + 2] = colorsMap[cs[j] + 2];
  333. pIndex += 2;
  334. cIndex += 3;
  335. }
  336. break;
  337. }
  338. }
  339. if (backgroundColor) {
  340. gl.clearColor(backgroundColor[0] / 255, backgroundColor[1] / 255, backgroundColor[2] / 255, 1.0);
  341. } else {
  342. gl.clearColor(0, 0, 0, 0);
  343. }
  344. gl.clear(gl.COLOR_BUFFER_BIT);
  345. var coordsBuffer = gl.createBuffer();
  346. gl.bindBuffer(gl.ARRAY_BUFFER, coordsBuffer);
  347. gl.bufferData(gl.ARRAY_BUFFER, coords, gl.STATIC_DRAW);
  348. gl.enableVertexAttribArray(cache.positionLocation);
  349. gl.vertexAttribPointer(cache.positionLocation, 2, gl.FLOAT, false, 0, 0);
  350. var colorsBuffer = gl.createBuffer();
  351. gl.bindBuffer(gl.ARRAY_BUFFER, colorsBuffer);
  352. gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
  353. gl.enableVertexAttribArray(cache.colorLocation);
  354. gl.vertexAttribPointer(cache.colorLocation, 3, gl.UNSIGNED_BYTE, false, 0, 0);
  355. gl.uniform2f(cache.scaleLocation, context.scaleX, context.scaleY);
  356. gl.uniform2f(cache.offsetLocation, context.offsetX, context.offsetY);
  357. gl.drawArrays(gl.TRIANGLES, 0, count);
  358. gl.flush();
  359. gl.deleteBuffer(coordsBuffer);
  360. gl.deleteBuffer(colorsBuffer);
  361. return canvas;
  362. }
  363. function cleanup() {
  364. if (smaskCache && smaskCache.canvas) {
  365. smaskCache.canvas.width = 0;
  366. smaskCache.canvas.height = 0;
  367. }
  368. if (figuresCache && figuresCache.canvas) {
  369. figuresCache.canvas.width = 0;
  370. figuresCache.canvas.height = 0;
  371. }
  372. smaskCache = null;
  373. figuresCache = null;
  374. }
  375. return {
  376. get isEnabled() {
  377. if (getDefaultSetting('disableWebGL')) {
  378. return false;
  379. }
  380. var enabled = false;
  381. try {
  382. generateGL();
  383. enabled = !!currentGL;
  384. } catch (e) {
  385. }
  386. return shadow(this, 'isEnabled', enabled);
  387. },
  388. composeSMask: composeSMask,
  389. drawFigures: drawFigures,
  390. clear: cleanup
  391. };
  392. }();
  393. exports.WebGLUtils = WebGLUtils;