mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-22 16:18:08 +02:00
[api-minor] Remove the WebGL implementation
Reasons for the removal include: - This functionality was always somewhat experimental and has never been enabled by default, partly because of worries about rendering bugs caused by e.g. bad/outdated graphics drivers. - After the initial implementation, in PR 4286 (back in 2014), no additional functionality has been added to the WebGL implementation. - The vast majority of all documents do not benefit from WebGL rendering, since only a couple of *specific* features are supported (e.g. some Soft Masks and Patterns). - There is, and has always been, *zero* test-coverage for the WebGL implementation. - Overall performance, in the PDF.js library, has improved since the experimental WebGL implementation was added. Rather than shipping unused *and* untested code, it seems reasonable to simply remove the WebGL implementation for now; thanks to version control it's always possible to bring back the code should the need ever arise.
This commit is contained in:
parent
99eac86478
commit
2ba4b65ca8
9 changed files with 28 additions and 608 deletions
|
@ -57,7 +57,6 @@ import { MessageHandler } from "../shared/message_handler.js";
|
|||
import { Metadata } from "./metadata.js";
|
||||
import { OptionalContentConfig } from "./optional_content_config.js";
|
||||
import { PDFDataTransportStream } from "./transport_stream.js";
|
||||
import { WebGLContext } from "./webgl.js";
|
||||
|
||||
const DEFAULT_RANGE_CHUNK_SIZE = 65536; // 2^16 = 65536
|
||||
const RENDERING_CANCELLED_TIMEOUT = 100; // ms
|
||||
|
@ -1092,8 +1091,6 @@ class PDFDocumentProxy {
|
|||
* the `PDFPageProxy.getViewport` method.
|
||||
* @property {string} [intent] - Rendering intent, can be 'display' or 'print'.
|
||||
* The default value is 'display'.
|
||||
* @property {boolean} [enableWebGL] - Enables WebGL accelerated rendering for
|
||||
* some operations. The default value is `false`.
|
||||
* @property {boolean} [renderInteractiveForms] - Whether or not interactive
|
||||
* form elements are rendered in the display layer. If so, we do not render
|
||||
* them on the canvas as well. The default value is `false`.
|
||||
|
@ -1272,7 +1269,6 @@ class PDFPageProxy {
|
|||
canvasContext,
|
||||
viewport,
|
||||
intent = "display",
|
||||
enableWebGL = false,
|
||||
renderInteractiveForms = false,
|
||||
transform = null,
|
||||
imageLayer = null,
|
||||
|
@ -1319,9 +1315,6 @@ class PDFPageProxy {
|
|||
const canvasFactoryInstance =
|
||||
canvasFactory ||
|
||||
new DefaultCanvasFactory({ ownerDocument: this._ownerDocument });
|
||||
const webGLContext = new WebGLContext({
|
||||
enable: enableWebGL,
|
||||
});
|
||||
const annotationStorage = includeAnnotationStorage
|
||||
? this._transport.annotationStorage.serializable
|
||||
: null;
|
||||
|
@ -1388,7 +1381,6 @@ class PDFPageProxy {
|
|||
operatorList: intentState.operatorList,
|
||||
pageIndex: this._pageIndex,
|
||||
canvasFactory: canvasFactoryInstance,
|
||||
webGLContext,
|
||||
useRequestAnimationFrame: renderingIntent !== "print",
|
||||
pdfBug: this._pdfBug,
|
||||
});
|
||||
|
@ -3022,7 +3014,6 @@ const InternalRenderTask = (function InternalRenderTaskClosure() {
|
|||
operatorList,
|
||||
pageIndex,
|
||||
canvasFactory,
|
||||
webGLContext,
|
||||
useRequestAnimationFrame = false,
|
||||
pdfBug = false,
|
||||
}) {
|
||||
|
@ -3034,7 +3025,6 @@ const InternalRenderTask = (function InternalRenderTaskClosure() {
|
|||
this.operatorList = operatorList;
|
||||
this._pageIndex = pageIndex;
|
||||
this.canvasFactory = canvasFactory;
|
||||
this.webGLContext = webGLContext;
|
||||
this._pdfBug = pdfBug;
|
||||
|
||||
this.running = false;
|
||||
|
@ -3093,7 +3083,6 @@ const InternalRenderTask = (function InternalRenderTaskClosure() {
|
|||
this.commonObjs,
|
||||
this.objs,
|
||||
this.canvasFactory,
|
||||
this.webGLContext,
|
||||
imageLayer,
|
||||
optionalContentConfig
|
||||
);
|
||||
|
|
|
@ -808,7 +808,7 @@ const CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||
}
|
||||
}
|
||||
|
||||
function composeSMask(ctx, smask, layerCtx, webGLContext) {
|
||||
function composeSMask(ctx, smask, layerCtx) {
|
||||
const mask = smask.canvas;
|
||||
const maskCtx = smask.context;
|
||||
|
||||
|
@ -821,27 +821,13 @@ const CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||
smask.offsetY
|
||||
);
|
||||
|
||||
const backdrop = smask.backdrop || null;
|
||||
if (!smask.transferMap && webGLContext.isEnabled) {
|
||||
const composed = webGLContext.composeSMask({
|
||||
layer: layerCtx.canvas,
|
||||
mask,
|
||||
properties: {
|
||||
subtype: smask.subtype,
|
||||
backdrop,
|
||||
},
|
||||
});
|
||||
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||
ctx.drawImage(composed, smask.offsetX, smask.offsetY);
|
||||
return;
|
||||
}
|
||||
genericComposeSMask(
|
||||
maskCtx,
|
||||
layerCtx,
|
||||
mask.width,
|
||||
mask.height,
|
||||
smask.subtype,
|
||||
backdrop,
|
||||
smask.backdrop,
|
||||
smask.transferMap
|
||||
);
|
||||
ctx.drawImage(mask, 0, 0);
|
||||
|
@ -859,7 +845,6 @@ const CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||
commonObjs,
|
||||
objs,
|
||||
canvasFactory,
|
||||
webGLContext,
|
||||
imageLayer,
|
||||
optionalContentConfig
|
||||
) {
|
||||
|
@ -873,7 +858,6 @@ const CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||
this.commonObjs = commonObjs;
|
||||
this.objs = objs;
|
||||
this.canvasFactory = canvasFactory;
|
||||
this.webGLContext = webGLContext;
|
||||
this.imageLayer = imageLayer;
|
||||
this.groupStack = [];
|
||||
this.processingType3 = null;
|
||||
|
@ -1042,7 +1026,6 @@ const CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||
}
|
||||
|
||||
this.cachedCanvases.clear();
|
||||
this.webGLContext.clear();
|
||||
|
||||
if (this.imageLayer) {
|
||||
this.imageLayer.endLayout();
|
||||
|
@ -1192,12 +1175,7 @@ const CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||
this.groupLevel--;
|
||||
this.ctx = this.groupStack.pop();
|
||||
|
||||
composeSMask(
|
||||
this.ctx,
|
||||
this.current.activeSMask,
|
||||
groupCtx,
|
||||
this.webGLContext
|
||||
);
|
||||
composeSMask(this.ctx, this.current.activeSMask, groupCtx);
|
||||
this.ctx.restore();
|
||||
this.ctx.save(); // save is needed since SMask will be resumed.
|
||||
copyCtxState(groupCtx, this.ctx);
|
||||
|
@ -1235,12 +1213,7 @@ const CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||
this.groupLevel--;
|
||||
this.ctx = this.groupStack.pop();
|
||||
|
||||
composeSMask(
|
||||
this.ctx,
|
||||
this.current.activeSMask,
|
||||
groupCtx,
|
||||
this.webGLContext
|
||||
);
|
||||
composeSMask(this.ctx, this.current.activeSMask, groupCtx);
|
||||
this.ctx.restore();
|
||||
copyCtxState(groupCtx, this.ctx);
|
||||
// Transform was changed in the SMask canvas, reflecting this change on
|
||||
|
@ -2004,8 +1977,7 @@ const CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||
ctx,
|
||||
this.commonObjs,
|
||||
this.objs,
|
||||
this.canvasFactory,
|
||||
this.webGLContext
|
||||
this.canvasFactory
|
||||
);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -233,8 +233,7 @@ const createMeshCanvas = (function createMeshCanvasClosure() {
|
|||
colors,
|
||||
figures,
|
||||
backgroundColor,
|
||||
cachedCanvases,
|
||||
webGLContext
|
||||
cachedCanvases
|
||||
) {
|
||||
// we will increase scale on some weird factor to let antialiasing take
|
||||
// care of "rough" edges
|
||||
|
@ -273,49 +272,29 @@ const createMeshCanvas = (function createMeshCanvasClosure() {
|
|||
const paddedWidth = width + BORDER_SIZE * 2;
|
||||
const paddedHeight = height + BORDER_SIZE * 2;
|
||||
|
||||
let canvas, tmpCanvas, i, ii;
|
||||
if (webGLContext.isEnabled) {
|
||||
canvas = webGLContext.drawFigures({
|
||||
width,
|
||||
height,
|
||||
backgroundColor,
|
||||
figures,
|
||||
context,
|
||||
});
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=972126
|
||||
tmpCanvas = cachedCanvases.getCanvas(
|
||||
"mesh",
|
||||
paddedWidth,
|
||||
paddedHeight,
|
||||
false
|
||||
);
|
||||
tmpCanvas.context.drawImage(canvas, BORDER_SIZE, BORDER_SIZE);
|
||||
canvas = tmpCanvas.canvas;
|
||||
} else {
|
||||
tmpCanvas = cachedCanvases.getCanvas(
|
||||
"mesh",
|
||||
paddedWidth,
|
||||
paddedHeight,
|
||||
false
|
||||
);
|
||||
const tmpCtx = tmpCanvas.context;
|
||||
const tmpCanvas = cachedCanvases.getCanvas(
|
||||
"mesh",
|
||||
paddedWidth,
|
||||
paddedHeight,
|
||||
false
|
||||
);
|
||||
const tmpCtx = tmpCanvas.context;
|
||||
|
||||
const data = tmpCtx.createImageData(width, height);
|
||||
if (backgroundColor) {
|
||||
const bytes = data.data;
|
||||
for (i = 0, ii = bytes.length; i < ii; i += 4) {
|
||||
bytes[i] = backgroundColor[0];
|
||||
bytes[i + 1] = backgroundColor[1];
|
||||
bytes[i + 2] = backgroundColor[2];
|
||||
bytes[i + 3] = 255;
|
||||
}
|
||||
const data = tmpCtx.createImageData(width, height);
|
||||
if (backgroundColor) {
|
||||
const bytes = data.data;
|
||||
for (let i = 0, ii = bytes.length; i < ii; i += 4) {
|
||||
bytes[i] = backgroundColor[0];
|
||||
bytes[i + 1] = backgroundColor[1];
|
||||
bytes[i + 2] = backgroundColor[2];
|
||||
bytes[i + 3] = 255;
|
||||
}
|
||||
for (i = 0; i < figures.length; i++) {
|
||||
drawFigure(data, figures[i], context);
|
||||
}
|
||||
tmpCtx.putImageData(data, BORDER_SIZE, BORDER_SIZE);
|
||||
canvas = tmpCanvas.canvas;
|
||||
}
|
||||
for (let i = 0, ii = figures.length; i < ii; i++) {
|
||||
drawFigure(data, figures[i], context);
|
||||
}
|
||||
tmpCtx.putImageData(data, BORDER_SIZE, BORDER_SIZE);
|
||||
const canvas = tmpCanvas.canvas;
|
||||
|
||||
return {
|
||||
canvas,
|
||||
|
@ -362,8 +341,7 @@ ShadingIRs.Mesh = {
|
|||
colors,
|
||||
figures,
|
||||
shadingFill ? null : background,
|
||||
owner.cachedCanvases,
|
||||
owner.webGLContext
|
||||
owner.cachedCanvases
|
||||
);
|
||||
|
||||
if (!shadingFill) {
|
||||
|
|
|
@ -1,495 +0,0 @@
|
|||
/* 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.js";
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
const WebGLUtils = (function WebGLUtilsClosure() {
|
||||
function loadShader(gl, code, shaderType) {
|
||||
const shader = gl.createShader(shaderType);
|
||||
gl.shaderSource(shader, code);
|
||||
gl.compileShader(shader);
|
||||
const compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
|
||||
if (!compiled) {
|
||||
const 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) {
|
||||
const program = gl.createProgram();
|
||||
for (let i = 0, ii = shaders.length; i < ii; ++i) {
|
||||
gl.attachShader(program, shaders[i]);
|
||||
}
|
||||
gl.linkProgram(program);
|
||||
const linked = gl.getProgramParameter(program, gl.LINK_STATUS);
|
||||
if (!linked) {
|
||||
const errorMsg = gl.getProgramInfoLog(program);
|
||||
throw new Error("Error during program linking: " + errorMsg);
|
||||
}
|
||||
return program;
|
||||
}
|
||||
function createTexture(gl, image, textureId) {
|
||||
gl.activeTexture(textureId);
|
||||
const 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;
|
||||
}
|
||||
|
||||
let 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,
|
||||
});
|
||||
}
|
||||
|
||||
const 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; \
|
||||
} ";
|
||||
|
||||
const 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; \
|
||||
} ";
|
||||
|
||||
let smaskCache = null;
|
||||
|
||||
function initSmaskGL() {
|
||||
generateGL();
|
||||
const canvas = currentCanvas;
|
||||
currentCanvas = null;
|
||||
const gl = currentGL;
|
||||
currentGL = null;
|
||||
|
||||
// setup a GLSL program
|
||||
const vertexShader = createVertexShader(gl, smaskVertexShaderCode);
|
||||
const fragmentShader = createFragmentShader(gl, smaskFragmentShaderCode);
|
||||
const program = createProgram(gl, [vertexShader, fragmentShader]);
|
||||
gl.useProgram(program);
|
||||
|
||||
const 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");
|
||||
|
||||
const texCoordLocation = gl.getAttribLocation(program, "a_texCoord");
|
||||
const texLayerLocation = gl.getUniformLocation(program, "u_image");
|
||||
const texMaskLocation = gl.getUniformLocation(program, "u_mask");
|
||||
|
||||
// provide texture coordinates for the rectangle.
|
||||
const 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) {
|
||||
const width = layer.width,
|
||||
height = layer.height;
|
||||
|
||||
if (!smaskCache) {
|
||||
initSmaskGL();
|
||||
}
|
||||
const 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
|
||||
const texture = createTexture(gl, layer, gl.TEXTURE0);
|
||||
const maskTexture = createTexture(gl, mask, gl.TEXTURE1);
|
||||
|
||||
// Create a buffer and put a single clipspace rectangle in
|
||||
// it (2 triangles)
|
||||
const 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;
|
||||
}
|
||||
|
||||
const 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); \
|
||||
} ";
|
||||
|
||||
const figuresFragmentShaderCode =
|
||||
"\
|
||||
precision mediump float; \
|
||||
\
|
||||
varying vec4 v_color; \
|
||||
\
|
||||
void main() { \
|
||||
gl_FragColor = v_color; \
|
||||
} ";
|
||||
|
||||
let figuresCache = null;
|
||||
|
||||
function initFiguresGL() {
|
||||
generateGL();
|
||||
const canvas = currentCanvas;
|
||||
currentCanvas = null;
|
||||
const gl = currentGL;
|
||||
currentGL = null;
|
||||
|
||||
// setup a GLSL program
|
||||
const vertexShader = createVertexShader(gl, figuresVertexShaderCode);
|
||||
const fragmentShader = createFragmentShader(gl, figuresFragmentShaderCode);
|
||||
const program = createProgram(gl, [vertexShader, fragmentShader]);
|
||||
gl.useProgram(program);
|
||||
|
||||
const 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();
|
||||
}
|
||||
const 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
|
||||
let count = 0;
|
||||
for (let i = 0, ii = figures.length; i < ii; i++) {
|
||||
switch (figures[i].type) {
|
||||
case "lattice":
|
||||
const 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
|
||||
const coords = new Float32Array(count * 2);
|
||||
const colors = new Uint8Array(count * 3);
|
||||
const coordsMap = context.coords,
|
||||
colorsMap = context.colors;
|
||||
let pIndex = 0,
|
||||
cIndex = 0;
|
||||
for (let i = 0, ii = figures.length; i < ii; i++) {
|
||||
const figure = figures[i],
|
||||
ps = figure.coords,
|
||||
cs = figure.colors;
|
||||
switch (figure.type) {
|
||||
case "lattice":
|
||||
const cols = figure.verticesPerRow;
|
||||
const rows = (ps.length / cols) | 0;
|
||||
for (let row = 1; row < rows; row++) {
|
||||
let offset = row * cols + 1;
|
||||
for (let 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 (let 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);
|
||||
|
||||
const 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);
|
||||
|
||||
const 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?.canvas) {
|
||||
smaskCache.canvas.width = 0;
|
||||
smaskCache.canvas.height = 0;
|
||||
}
|
||||
if (figuresCache?.canvas) {
|
||||
figuresCache.canvas.width = 0;
|
||||
figuresCache.canvas.height = 0;
|
||||
}
|
||||
smaskCache = null;
|
||||
figuresCache = null;
|
||||
},
|
||||
};
|
||||
})();
|
||||
|
||||
export { WebGLContext };
|
Loading…
Add table
Add a link
Reference in a new issue