webgl.js 17 KB

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