de36b2aaba
Note that Prettier, purposely, has only limited [configuration options](https://prettier.io/docs/en/options.html). The configuration file is based on [the one in `mozilla central`](https://searchfox.org/mozilla-central/source/.prettierrc) with just a few additions (to avoid future breakage if the defaults ever changes). Prettier is being used for a couple of reasons: - To be consistent with `mozilla-central`, where Prettier is already in use across the tree. - To ensure a *consistent* coding style everywhere, which is automatically enforced during linting (since Prettier is used as an ESLint plugin). This thus ends "all" formatting disussions once and for all, removing the need for review comments on most stylistic matters. Many ESLint options are now redundant, and I've tried my best to remove all the now unnecessary options (but I may have missed some). Note also that since Prettier considers the `printWidth` option as a guide, rather than a hard rule, this patch resorts to a small hack in the ESLint config to ensure that *comments* won't become too long. *Please note:* This patch is generated automatically, by appending the `--fix` argument to the ESLint call used in the `gulp lint` task. It will thus require some additional clean-up, which will be done in a *separate* commit. (On a more personal note, I'll readily admit that some of the changes Prettier makes are *extremely* ugly. However, in the name of consistency we'll probably have to live with that.)
500 lines
17 KiB
JavaScript
500 lines
17 KiB
JavaScript
/* Copyright 2014 Mozilla Foundation
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
/* eslint-disable no-multi-str */
|
|
|
|
import { shadow } from "../shared/util";
|
|
|
|
class WebGLContext {
|
|
constructor({ enable = false }) {
|
|
this._enabled = enable === true;
|
|
}
|
|
|
|
get isEnabled() {
|
|
let enabled = this._enabled;
|
|
if (enabled) {
|
|
enabled = WebGLUtils.tryInitGL();
|
|
}
|
|
return shadow(this, "isEnabled", enabled);
|
|
}
|
|
|
|
composeSMask({ layer, mask, properties }) {
|
|
return WebGLUtils.composeSMask(layer, mask, properties);
|
|
}
|
|
|
|
drawFigures({ width, height, backgroundColor, figures, context }) {
|
|
return WebGLUtils.drawFigures(
|
|
width,
|
|
height,
|
|
backgroundColor,
|
|
figures,
|
|
context
|
|
);
|
|
}
|
|
|
|
clear() {
|
|
WebGLUtils.cleanup();
|
|
}
|
|
}
|
|
|
|
var WebGLUtils = (function WebGLUtilsClosure() {
|
|
function loadShader(gl, code, shaderType) {
|
|
var shader = gl.createShader(shaderType);
|
|
gl.shaderSource(shader, code);
|
|
gl.compileShader(shader);
|
|
var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
|
|
if (!compiled) {
|
|
var errorMsg = gl.getShaderInfoLog(shader);
|
|
throw new Error("Error during shader compilation: " + errorMsg);
|
|
}
|
|
return shader;
|
|
}
|
|
function createVertexShader(gl, code) {
|
|
return loadShader(gl, code, gl.VERTEX_SHADER);
|
|
}
|
|
function createFragmentShader(gl, code) {
|
|
return loadShader(gl, code, gl.FRAGMENT_SHADER);
|
|
}
|
|
function createProgram(gl, shaders) {
|
|
var program = gl.createProgram();
|
|
for (var i = 0, ii = shaders.length; i < ii; ++i) {
|
|
gl.attachShader(program, shaders[i]);
|
|
}
|
|
gl.linkProgram(program);
|
|
var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
|
|
if (!linked) {
|
|
var errorMsg = gl.getProgramInfoLog(program);
|
|
throw new Error("Error during program linking: " + errorMsg);
|
|
}
|
|
return program;
|
|
}
|
|
function createTexture(gl, image, textureId) {
|
|
gl.activeTexture(textureId);
|
|
var texture = gl.createTexture();
|
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
|
|
// Set the parameters so we can render any size image.
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
|
|
// Upload the image into the texture.
|
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
|
|
return texture;
|
|
}
|
|
|
|
var currentGL, currentCanvas;
|
|
function generateGL() {
|
|
if (currentGL) {
|
|
return;
|
|
}
|
|
|
|
// The temporary canvas is used in the WebGL context.
|
|
currentCanvas = document.createElement("canvas");
|
|
currentGL = currentCanvas.getContext("webgl", {
|
|
premultipliedalpha: false,
|
|
});
|
|
}
|
|
|
|
var smaskVertexShaderCode =
|
|
"\
|
|
attribute vec2 a_position; \
|
|
attribute vec2 a_texCoord; \
|
|
\
|
|
uniform vec2 u_resolution; \
|
|
\
|
|
varying vec2 v_texCoord; \
|
|
\
|
|
void main() { \
|
|
vec2 clipSpace = (a_position / u_resolution) * 2.0 - 1.0; \
|
|
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1); \
|
|
\
|
|
v_texCoord = a_texCoord; \
|
|
} ";
|
|
|
|
var smaskFragmentShaderCode =
|
|
"\
|
|
precision mediump float; \
|
|
\
|
|
uniform vec4 u_backdrop; \
|
|
uniform int u_subtype; \
|
|
uniform sampler2D u_image; \
|
|
uniform sampler2D u_mask; \
|
|
\
|
|
varying vec2 v_texCoord; \
|
|
\
|
|
void main() { \
|
|
vec4 imageColor = texture2D(u_image, v_texCoord); \
|
|
vec4 maskColor = texture2D(u_mask, v_texCoord); \
|
|
if (u_backdrop.a > 0.0) { \
|
|
maskColor.rgb = maskColor.rgb * maskColor.a + \
|
|
u_backdrop.rgb * (1.0 - maskColor.a); \
|
|
} \
|
|
float lum; \
|
|
if (u_subtype == 0) { \
|
|
lum = maskColor.a; \
|
|
} else { \
|
|
lum = maskColor.r * 0.3 + maskColor.g * 0.59 + \
|
|
maskColor.b * 0.11; \
|
|
} \
|
|
imageColor.a *= lum; \
|
|
imageColor.rgb *= imageColor.a; \
|
|
gl_FragColor = imageColor; \
|
|
} ";
|
|
|
|
var smaskCache = null;
|
|
|
|
function initSmaskGL() {
|
|
var canvas, gl;
|
|
|
|
generateGL();
|
|
canvas = currentCanvas;
|
|
currentCanvas = null;
|
|
gl = currentGL;
|
|
currentGL = null;
|
|
|
|
// setup a GLSL program
|
|
var vertexShader = createVertexShader(gl, smaskVertexShaderCode);
|
|
var fragmentShader = createFragmentShader(gl, smaskFragmentShaderCode);
|
|
var program = createProgram(gl, [vertexShader, fragmentShader]);
|
|
gl.useProgram(program);
|
|
|
|
var cache = {};
|
|
cache.gl = gl;
|
|
cache.canvas = canvas;
|
|
cache.resolutionLocation = gl.getUniformLocation(program, "u_resolution");
|
|
cache.positionLocation = gl.getAttribLocation(program, "a_position");
|
|
cache.backdropLocation = gl.getUniformLocation(program, "u_backdrop");
|
|
cache.subtypeLocation = gl.getUniformLocation(program, "u_subtype");
|
|
|
|
var texCoordLocation = gl.getAttribLocation(program, "a_texCoord");
|
|
var texLayerLocation = gl.getUniformLocation(program, "u_image");
|
|
var texMaskLocation = gl.getUniformLocation(program, "u_mask");
|
|
|
|
// provide texture coordinates for the rectangle.
|
|
var texCoordBuffer = gl.createBuffer();
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
|
|
// prettier-ignore
|
|
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);
|
|
gl.enableVertexAttribArray(texCoordLocation);
|
|
gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);
|
|
|
|
gl.uniform1i(texLayerLocation, 0);
|
|
gl.uniform1i(texMaskLocation, 1);
|
|
|
|
smaskCache = cache;
|
|
}
|
|
|
|
function composeSMask(layer, mask, properties) {
|
|
var width = layer.width,
|
|
height = layer.height;
|
|
|
|
if (!smaskCache) {
|
|
initSmaskGL();
|
|
}
|
|
var cache = smaskCache,
|
|
canvas = cache.canvas,
|
|
gl = cache.gl;
|
|
canvas.width = width;
|
|
canvas.height = height;
|
|
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
|
|
gl.uniform2f(cache.resolutionLocation, width, height);
|
|
|
|
if (properties.backdrop) {
|
|
gl.uniform4f(
|
|
cache.resolutionLocation,
|
|
properties.backdrop[0],
|
|
properties.backdrop[1],
|
|
properties.backdrop[2],
|
|
1
|
|
);
|
|
} else {
|
|
gl.uniform4f(cache.resolutionLocation, 0, 0, 0, 0);
|
|
}
|
|
gl.uniform1i(
|
|
cache.subtypeLocation,
|
|
properties.subtype === "Luminosity" ? 1 : 0
|
|
);
|
|
|
|
// Create a textures
|
|
var texture = createTexture(gl, layer, gl.TEXTURE0);
|
|
var maskTexture = createTexture(gl, mask, gl.TEXTURE1);
|
|
|
|
// Create a buffer and put a single clipspace rectangle in
|
|
// it (2 triangles)
|
|
var buffer = gl.createBuffer();
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
// prettier-ignore
|
|
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
|
|
0, 0,
|
|
width, 0,
|
|
0, height,
|
|
0, height,
|
|
width, 0,
|
|
width, height]), gl.STATIC_DRAW);
|
|
gl.enableVertexAttribArray(cache.positionLocation);
|
|
gl.vertexAttribPointer(cache.positionLocation, 2, gl.FLOAT, false, 0, 0);
|
|
|
|
// draw
|
|
gl.clearColor(0, 0, 0, 0);
|
|
gl.enable(gl.BLEND);
|
|
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
|
|
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
|
|
gl.flush();
|
|
|
|
gl.deleteTexture(texture);
|
|
gl.deleteTexture(maskTexture);
|
|
gl.deleteBuffer(buffer);
|
|
|
|
return canvas;
|
|
}
|
|
|
|
var figuresVertexShaderCode =
|
|
"\
|
|
attribute vec2 a_position; \
|
|
attribute vec3 a_color; \
|
|
\
|
|
uniform vec2 u_resolution; \
|
|
uniform vec2 u_scale; \
|
|
uniform vec2 u_offset; \
|
|
\
|
|
varying vec4 v_color; \
|
|
\
|
|
void main() { \
|
|
vec2 position = (a_position + u_offset) * u_scale; \
|
|
vec2 clipSpace = (position / u_resolution) * 2.0 - 1.0; \
|
|
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1); \
|
|
\
|
|
v_color = vec4(a_color / 255.0, 1.0); \
|
|
} ";
|
|
|
|
var figuresFragmentShaderCode =
|
|
"\
|
|
precision mediump float; \
|
|
\
|
|
varying vec4 v_color; \
|
|
\
|
|
void main() { \
|
|
gl_FragColor = v_color; \
|
|
} ";
|
|
|
|
var figuresCache = null;
|
|
|
|
function initFiguresGL() {
|
|
var canvas, gl;
|
|
|
|
generateGL();
|
|
canvas = currentCanvas;
|
|
currentCanvas = null;
|
|
gl = currentGL;
|
|
currentGL = null;
|
|
|
|
// setup a GLSL program
|
|
var vertexShader = createVertexShader(gl, figuresVertexShaderCode);
|
|
var fragmentShader = createFragmentShader(gl, figuresFragmentShaderCode);
|
|
var program = createProgram(gl, [vertexShader, fragmentShader]);
|
|
gl.useProgram(program);
|
|
|
|
var cache = {};
|
|
cache.gl = gl;
|
|
cache.canvas = canvas;
|
|
cache.resolutionLocation = gl.getUniformLocation(program, "u_resolution");
|
|
cache.scaleLocation = gl.getUniformLocation(program, "u_scale");
|
|
cache.offsetLocation = gl.getUniformLocation(program, "u_offset");
|
|
cache.positionLocation = gl.getAttribLocation(program, "a_position");
|
|
cache.colorLocation = gl.getAttribLocation(program, "a_color");
|
|
|
|
figuresCache = cache;
|
|
}
|
|
|
|
function drawFigures(width, height, backgroundColor, figures, context) {
|
|
if (!figuresCache) {
|
|
initFiguresGL();
|
|
}
|
|
var cache = figuresCache,
|
|
canvas = cache.canvas,
|
|
gl = cache.gl;
|
|
|
|
canvas.width = width;
|
|
canvas.height = height;
|
|
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
|
|
gl.uniform2f(cache.resolutionLocation, width, height);
|
|
|
|
// count triangle points
|
|
var count = 0;
|
|
var i, ii, rows;
|
|
for (i = 0, ii = figures.length; i < ii; i++) {
|
|
switch (figures[i].type) {
|
|
case "lattice":
|
|
rows = (figures[i].coords.length / figures[i].verticesPerRow) | 0;
|
|
count += (rows - 1) * (figures[i].verticesPerRow - 1) * 6;
|
|
break;
|
|
case "triangles":
|
|
count += figures[i].coords.length;
|
|
break;
|
|
}
|
|
}
|
|
// transfer data
|
|
var coords = new Float32Array(count * 2);
|
|
var colors = new Uint8Array(count * 3);
|
|
var coordsMap = context.coords,
|
|
colorsMap = context.colors;
|
|
var pIndex = 0,
|
|
cIndex = 0;
|
|
for (i = 0, ii = figures.length; i < ii; i++) {
|
|
var figure = figures[i],
|
|
ps = figure.coords,
|
|
cs = figure.colors;
|
|
switch (figure.type) {
|
|
case "lattice":
|
|
var cols = figure.verticesPerRow;
|
|
rows = (ps.length / cols) | 0;
|
|
for (var row = 1; row < rows; row++) {
|
|
var offset = row * cols + 1;
|
|
for (var col = 1; col < cols; col++, offset++) {
|
|
coords[pIndex] = coordsMap[ps[offset - cols - 1]];
|
|
coords[pIndex + 1] = coordsMap[ps[offset - cols - 1] + 1];
|
|
coords[pIndex + 2] = coordsMap[ps[offset - cols]];
|
|
coords[pIndex + 3] = coordsMap[ps[offset - cols] + 1];
|
|
coords[pIndex + 4] = coordsMap[ps[offset - 1]];
|
|
coords[pIndex + 5] = coordsMap[ps[offset - 1] + 1];
|
|
colors[cIndex] = colorsMap[cs[offset - cols - 1]];
|
|
colors[cIndex + 1] = colorsMap[cs[offset - cols - 1] + 1];
|
|
colors[cIndex + 2] = colorsMap[cs[offset - cols - 1] + 2];
|
|
colors[cIndex + 3] = colorsMap[cs[offset - cols]];
|
|
colors[cIndex + 4] = colorsMap[cs[offset - cols] + 1];
|
|
colors[cIndex + 5] = colorsMap[cs[offset - cols] + 2];
|
|
colors[cIndex + 6] = colorsMap[cs[offset - 1]];
|
|
colors[cIndex + 7] = colorsMap[cs[offset - 1] + 1];
|
|
colors[cIndex + 8] = colorsMap[cs[offset - 1] + 2];
|
|
|
|
coords[pIndex + 6] = coords[pIndex + 2];
|
|
coords[pIndex + 7] = coords[pIndex + 3];
|
|
coords[pIndex + 8] = coords[pIndex + 4];
|
|
coords[pIndex + 9] = coords[pIndex + 5];
|
|
coords[pIndex + 10] = coordsMap[ps[offset]];
|
|
coords[pIndex + 11] = coordsMap[ps[offset] + 1];
|
|
colors[cIndex + 9] = colors[cIndex + 3];
|
|
colors[cIndex + 10] = colors[cIndex + 4];
|
|
colors[cIndex + 11] = colors[cIndex + 5];
|
|
colors[cIndex + 12] = colors[cIndex + 6];
|
|
colors[cIndex + 13] = colors[cIndex + 7];
|
|
colors[cIndex + 14] = colors[cIndex + 8];
|
|
colors[cIndex + 15] = colorsMap[cs[offset]];
|
|
colors[cIndex + 16] = colorsMap[cs[offset] + 1];
|
|
colors[cIndex + 17] = colorsMap[cs[offset] + 2];
|
|
pIndex += 12;
|
|
cIndex += 18;
|
|
}
|
|
}
|
|
break;
|
|
case "triangles":
|
|
for (var j = 0, jj = ps.length; j < jj; j++) {
|
|
coords[pIndex] = coordsMap[ps[j]];
|
|
coords[pIndex + 1] = coordsMap[ps[j] + 1];
|
|
colors[cIndex] = colorsMap[cs[j]];
|
|
colors[cIndex + 1] = colorsMap[cs[j] + 1];
|
|
colors[cIndex + 2] = colorsMap[cs[j] + 2];
|
|
pIndex += 2;
|
|
cIndex += 3;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// draw
|
|
if (backgroundColor) {
|
|
gl.clearColor(
|
|
backgroundColor[0] / 255,
|
|
backgroundColor[1] / 255,
|
|
backgroundColor[2] / 255,
|
|
1.0
|
|
);
|
|
} else {
|
|
gl.clearColor(0, 0, 0, 0);
|
|
}
|
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
|
|
var coordsBuffer = gl.createBuffer();
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, coordsBuffer);
|
|
gl.bufferData(gl.ARRAY_BUFFER, coords, gl.STATIC_DRAW);
|
|
gl.enableVertexAttribArray(cache.positionLocation);
|
|
gl.vertexAttribPointer(cache.positionLocation, 2, gl.FLOAT, false, 0, 0);
|
|
|
|
var colorsBuffer = gl.createBuffer();
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, colorsBuffer);
|
|
gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
|
|
gl.enableVertexAttribArray(cache.colorLocation);
|
|
gl.vertexAttribPointer(
|
|
cache.colorLocation,
|
|
3,
|
|
gl.UNSIGNED_BYTE,
|
|
false,
|
|
0,
|
|
0
|
|
);
|
|
|
|
gl.uniform2f(cache.scaleLocation, context.scaleX, context.scaleY);
|
|
gl.uniform2f(cache.offsetLocation, context.offsetX, context.offsetY);
|
|
|
|
gl.drawArrays(gl.TRIANGLES, 0, count);
|
|
|
|
gl.flush();
|
|
|
|
gl.deleteBuffer(coordsBuffer);
|
|
gl.deleteBuffer(colorsBuffer);
|
|
|
|
return canvas;
|
|
}
|
|
|
|
return {
|
|
tryInitGL() {
|
|
try {
|
|
generateGL();
|
|
return !!currentGL;
|
|
} catch (ex) {}
|
|
return false;
|
|
},
|
|
|
|
composeSMask,
|
|
|
|
drawFigures,
|
|
|
|
cleanup() {
|
|
if (smaskCache && smaskCache.canvas) {
|
|
smaskCache.canvas.width = 0;
|
|
smaskCache.canvas.height = 0;
|
|
}
|
|
if (figuresCache && figuresCache.canvas) {
|
|
figuresCache.canvas.width = 0;
|
|
figuresCache.canvas.height = 0;
|
|
}
|
|
smaskCache = null;
|
|
figuresCache = null;
|
|
},
|
|
};
|
|
})();
|
|
|
|
export { WebGLContext };
|