@elastic/charts
Version:
Elastic-Charts data visualization library
426 lines • 19.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.testContextLoss = exports.getRenderer = exports.bindVertexArray = exports.getAttributes = exports.readPixel = exports.createTexture = exports.NullTexture = exports.bindFramebuffer = exports.clearRect = exports.createLinkedProgram = exports.createCompiledShader = exports.resetState = void 0;
const webgl_constants_1 = require("./webgl_constants");
const logger_1 = require("../utils/logger");
const GL_DEBUG = true;
const currentPrograms = new WeakMap();
const currentVertexArrayObjects = new WeakMap();
const currentDepthTests = new WeakMap();
const currentReadFrameBuffers = new WeakMap();
const currentDrawFrameBuffers = new WeakMap();
const currentViewport = new WeakMap();
const currentScissor = new WeakMap();
const currentClearColor = new WeakMap();
const currentClearDepth = new WeakMap();
const currentFlags = new WeakMap();
const programUniforms = new WeakMap();
const locationUniformValues = new WeakMap();
const setGlobalConstants = new WeakSet();
const resetState = (gl) => {
currentPrograms.delete(gl);
currentVertexArrayObjects.delete(gl);
currentDepthTests.delete(gl);
currentReadFrameBuffers.delete(gl);
currentDrawFrameBuffers.delete(gl);
currentViewport.delete(gl);
currentScissor.delete(gl);
currentClearColor.delete(gl);
currentClearDepth.delete(gl);
currentFlags.delete(gl);
setGlobalConstants.delete(gl);
};
exports.resetState = resetState;
const setViewport = (gl, ...xyWidthHeight) => {
const key = xyWidthHeight.join('|');
if (currentViewport.get(gl) !== key) {
gl.viewport(...xyWidthHeight);
currentViewport.set(gl, key);
}
};
const setScissor = (gl, ...xyWidthHeight) => {
const key = xyWidthHeight.join('|');
if (currentScissor.get(gl) !== key) {
gl.scissor(...xyWidthHeight);
currentScissor.set(gl, key);
}
};
const clearColor = (gl, ...rgba) => {
const key = rgba.join('|');
if (currentClearColor.get(gl) !== key) {
gl.clearColor(...rgba);
currentClearColor.set(gl, key);
}
};
const clearDepth = (gl, depth) => {
if (currentClearDepth.get(gl) !== depth) {
gl.clearDepth(depth);
currentClearDepth.set(gl, depth);
}
};
const flagSet = (gl, key, value) => {
const flags = currentFlags.get(gl);
if (flags[key] !== value) {
if (value) {
gl.enable(key);
}
else {
gl.disable(key);
}
flags[key] = value;
}
};
const createCompiledShader = (gl, shaderType, source) => {
const shader = gl.createShader(shaderType);
if (!shader)
throw new Error(`kinGLy exception: shader could not be created`);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (GL_DEBUG && !gl.getShaderParameter(shader, webgl_constants_1.GL.COMPILE_STATUS) && !gl.isContextLost()) {
const shaderTypeName = shaderType === webgl_constants_1.GL.VERTEX_SHADER ? 'vertex' : 'fragment';
logger_1.Logger.warn(`kinGLy exception: compilation error in a ${shaderTypeName} shader: ${gl.getShaderInfoLog(shader)}`);
}
return shader;
};
exports.createCompiledShader = createCompiledShader;
const createLinkedProgram = (gl, vertexShader, fragmentShader, attributeLocations) => {
const program = gl.createProgram();
if (!program)
throw new Error(`kinGLy exception: shader program could not be created`);
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
if (GL_DEBUG && gl.getProgramParameter(program, webgl_constants_1.GL.ATTACHED_SHADERS) !== 2)
logger_1.Logger.warn('kinGLy exception: did not manage to attach the two shaders');
Object.entries(attributeLocations).forEach(([name, i]) => gl.bindAttribLocation(program, i, name));
gl.linkProgram(program);
if (GL_DEBUG) {
if (!gl.getProgramParameter(program, webgl_constants_1.GL.LINK_STATUS) && !gl.isContextLost())
logger_1.Logger.warn(`kinGLy exception: shader program failed to link: ${gl.getProgramInfoLog(program)}`);
gl.validateProgram(program);
if (!gl.getProgramParameter(program, webgl_constants_1.GL.LINK_STATUS) && !gl.isContextLost())
logger_1.Logger.warn(`kinGLy exception: could not validate the shader program: ${gl.getProgramInfoLog(program)}`);
}
else {
window.setTimeout(() => {
gl.detachShader(program, vertexShader);
gl.detachShader(program, fragmentShader);
gl.deleteShader(vertexShader);
gl.deleteShader(fragmentShader);
});
}
return program;
};
exports.createLinkedProgram = createLinkedProgram;
const uniformSetterLookup = {
[webgl_constants_1.GL.BOOL]: (gl, location) => (value) => {
if (locationUniformValues.get(location) !== value) {
gl.uniform1ui(location, value);
locationUniformValues.set(location, value);
}
},
[webgl_constants_1.GL.INT]: (gl, location) => (value) => {
if (locationUniformValues.get(location) !== value) {
gl.uniform1i(location, value);
locationUniformValues.set(location, value);
}
},
[webgl_constants_1.GL.FLOAT_VEC2]: (gl, location) => (values) => {
const value = values.join('|');
if (locationUniformValues.get(location) !== value) {
gl.uniform2fv(location, values);
locationUniformValues.set(location, value);
}
},
[webgl_constants_1.GL.FLOAT_VEC3]: (gl, location) => (values) => {
const value = values.join('|');
if (locationUniformValues.get(location) !== value) {
gl.uniform3fv(location, values);
locationUniformValues.set(location, value);
}
},
[webgl_constants_1.GL.INT_VEC2]: (gl, location) => (values) => {
const value = values.join('|');
if (locationUniformValues.get(location) !== value) {
gl.uniform2iv(location, values);
locationUniformValues.set(location, value);
}
},
[webgl_constants_1.GL.FLOAT]: (gl, location) => (value) => {
if (locationUniformValues.get(location) !== value) {
gl.uniform1f(location, value);
locationUniformValues.set(location, value);
}
},
[webgl_constants_1.GL.FLOAT_MAT2]: (gl, location) => (array) => {
const value = array.join('|');
if (locationUniformValues.get(location) !== value) {
gl.uniformMatrix2fv(location, false, array);
locationUniformValues.set(location, value);
}
},
[webgl_constants_1.GL.FLOAT_MAT4]: (gl, location) => (array) => {
const value = array.join('|');
if (locationUniformValues.get(location) !== value) {
gl.uniformMatrix4fv(location, false, array);
locationUniformValues.set(location, value);
}
},
[webgl_constants_1.GL.SAMPLER_2D]: (gl, location) => ({ setUniform }) => {
if (locationUniformValues.get(location) !== setUniform) {
setUniform(location);
locationUniformValues.set(location, setUniform);
}
},
};
const getUniforms = (gl, program) => {
if (programUniforms.has(program))
return programUniforms.get(program);
const uniforms = new Map([...new Array(gl.getProgramParameter(program, webgl_constants_1.GL.ACTIVE_UNIFORMS))].flatMap((_, index) => {
const activeUniform = gl.getActiveUniform(program, index);
if (!activeUniform) {
logger_1.Logger.warn(`kinGLy exception: active uniform not found`);
return [];
}
const { name, type } = activeUniform;
const location = gl.getUniformLocation(program, name);
if (location === null) {
logger_1.Logger.warn(`kinGLy exception: uniform location ${location} (name: ${name}, type: ${type}) not found`);
return [];
}
const setValue = location ? uniformSetterLookup[type]?.(gl, location) : () => { };
if (!setValue) {
logger_1.Logger.warn(`kinGLy exception: no setValue for uniform GL[${type}] (name: ${name}) implemented yet`);
return [];
}
return [[name, setValue]];
}));
programUniforms.set(program, uniforms);
return uniforms;
};
const clearRect = (gl, { rect, color, depth, stencilIndex }) => {
if (rect) {
flagSet(gl, webgl_constants_1.GL.SCISSOR_TEST, true);
setScissor(gl, ...rect);
}
let flags = 0;
if (color) {
clearColor(gl, ...color);
flags |= webgl_constants_1.GL.COLOR_BUFFER_BIT;
}
if (typeof depth === 'number') {
clearDepth(gl, depth);
flags |= webgl_constants_1.GL.DEPTH_BUFFER_BIT;
}
if (typeof stencilIndex === 'number') {
gl.clearStencil(stencilIndex);
flags |= webgl_constants_1.GL.STENCIL_BUFFER_BIT;
}
gl.clear(flags);
};
exports.clearRect = clearRect;
const textureSrcFormatLookup = {
[webgl_constants_1.GL.RGBA8]: webgl_constants_1.GL.RGBA,
[webgl_constants_1.GL.RGBA32F]: webgl_constants_1.GL.RGBA,
};
const textureTypeLookup = {
[webgl_constants_1.GL.RGBA8]: webgl_constants_1.GL.UNSIGNED_BYTE,
[webgl_constants_1.GL.RGBA32F]: webgl_constants_1.GL.FLOAT,
};
const bindFramebuffer = (gl, target, targetFrameBuffer) => {
const updateReadTarget = (target === webgl_constants_1.GL.READ_FRAMEBUFFER || target === webgl_constants_1.GL.FRAMEBUFFER) &&
targetFrameBuffer !== currentReadFrameBuffers.get(gl);
const updateWriteTarget = (target === webgl_constants_1.GL.DRAW_FRAMEBUFFER || target === webgl_constants_1.GL.FRAMEBUFFER) &&
targetFrameBuffer !== currentDrawFrameBuffers.get(gl);
if (updateReadTarget)
currentReadFrameBuffers.set(gl, targetFrameBuffer);
if (updateWriteTarget)
currentDrawFrameBuffers.set(gl, targetFrameBuffer);
if (updateReadTarget || updateWriteTarget) {
const targetToUpdate = updateReadTarget && updateWriteTarget
? webgl_constants_1.GL.FRAMEBUFFER
: updateReadTarget
? webgl_constants_1.GL.READ_FRAMEBUFFER
: webgl_constants_1.GL.DRAW_FRAMEBUFFER;
gl.bindFramebuffer(targetToUpdate, targetFrameBuffer);
}
};
exports.bindFramebuffer = bindFramebuffer;
exports.NullTexture = {
clear: () => { },
setUniform: () => { },
target: () => null,
delete: () => { },
width: 0,
height: 0,
};
const createTexture = (gl, { textureIndex, internalFormat, width, height, data, min = webgl_constants_1.GL.NEAREST, mag = webgl_constants_1.GL.NEAREST }) => {
if (GL_DEBUG && !(0 <= textureIndex && textureIndex <= gl.getParameter(webgl_constants_1.GL.MAX_COMBINED_TEXTURE_IMAGE_UNITS)))
logger_1.Logger.warn('kinGLy exception: WebGL2 is guaranteed to support at least 32 textures but not necessarily more than that');
const srcFormat = textureSrcFormatLookup[internalFormat] ?? NaN;
const type = textureTypeLookup[internalFormat] ?? NaN;
const texture = gl.createTexture();
const setTextureContents = () => {
gl.activeTexture(webgl_constants_1.GL.TEXTURE0 + textureIndex);
gl.bindTexture(webgl_constants_1.GL.TEXTURE_2D, texture);
gl.texImage2D(webgl_constants_1.GL.TEXTURE_2D, 0, internalFormat, width, height, 0, srcFormat, type, data);
};
setTextureContents();
gl.texParameteri(webgl_constants_1.GL.TEXTURE_2D, webgl_constants_1.GL.TEXTURE_MIN_FILTER, min);
gl.texParameteri(webgl_constants_1.GL.TEXTURE_2D, webgl_constants_1.GL.TEXTURE_MAG_FILTER, mag);
gl.texParameteri(webgl_constants_1.GL.TEXTURE_2D, webgl_constants_1.GL.TEXTURE_WRAP_S, webgl_constants_1.GL.CLAMP_TO_EDGE);
gl.texParameteri(webgl_constants_1.GL.TEXTURE_2D, webgl_constants_1.GL.TEXTURE_WRAP_T, webgl_constants_1.GL.CLAMP_TO_EDGE);
if (GL_DEBUG) {
const error = gl.getError();
if (error !== gl.NO_ERROR && error !== gl.CONTEXT_LOST_WEBGL)
logger_1.Logger.warn(`kinGLy exception: failed to set the texture with texImage2D, code ${error}`);
}
let frameBuffer = null;
const getTarget = () => {
if (!frameBuffer) {
frameBuffer = gl.createFramebuffer();
(0, exports.bindFramebuffer)(gl, webgl_constants_1.GL.DRAW_FRAMEBUFFER, frameBuffer);
gl.framebufferTexture2D(webgl_constants_1.GL.FRAMEBUFFER, webgl_constants_1.GL.COLOR_ATTACHMENT0, webgl_constants_1.GL.TEXTURE_2D, texture, 0);
if (GL_DEBUG) {
const framebufferStatus = gl.checkFramebufferStatus(webgl_constants_1.GL.DRAW_FRAMEBUFFER);
if (framebufferStatus !== webgl_constants_1.GL.FRAMEBUFFER_COMPLETE) {
logger_1.Logger.warn(`kinGLy exception: target framebuffer is not complete`);
}
}
}
return frameBuffer;
};
return {
clear: () => {
(0, exports.bindFramebuffer)(gl, webgl_constants_1.GL.DRAW_FRAMEBUFFER, getTarget());
(0, exports.clearRect)(gl, { color: [0, 0, 0, 0] });
},
setUniform: (location) => gl.uniform1i(location, textureIndex),
target: getTarget,
delete: () => {
if (frameBuffer) {
if (currentReadFrameBuffers.get(gl) === frameBuffer)
(0, exports.bindFramebuffer)(gl, webgl_constants_1.GL.READ_FRAMEBUFFER, null);
if (currentDrawFrameBuffers.get(gl) === frameBuffer)
(0, exports.bindFramebuffer)(gl, webgl_constants_1.GL.DRAW_FRAMEBUFFER, null);
gl.deleteFramebuffer(frameBuffer);
}
gl.deleteTexture(texture);
return true;
},
width,
height,
};
};
exports.createTexture = createTexture;
const pickPixel = new Uint8Array(4);
const readPixel = (gl, canvasX, canvasY) => {
gl.readPixels(canvasX, canvasY, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pickPixel);
return pickPixel;
};
exports.readPixel = readPixel;
const attribSizeLookup = { [webgl_constants_1.GL.FLOAT_VEC2]: 2, [webgl_constants_1.GL.FLOAT_VEC4]: 4, [webgl_constants_1.GL.FLOAT]: 1, [webgl_constants_1.GL.INT]: 1 };
const attribElementTypeLookup = {
[webgl_constants_1.GL.FLOAT_VEC2]: webgl_constants_1.GL.FLOAT,
[webgl_constants_1.GL.FLOAT_VEC4]: webgl_constants_1.GL.FLOAT,
[webgl_constants_1.GL.FLOAT]: webgl_constants_1.GL.FLOAT,
[webgl_constants_1.GL.INT]: webgl_constants_1.GL.INT,
};
const integerTypes = new Set([webgl_constants_1.GL.BYTE, webgl_constants_1.GL.SHORT, webgl_constants_1.GL.INT, webgl_constants_1.GL.UNSIGNED_BYTE, webgl_constants_1.GL.UNSIGNED_SHORT, webgl_constants_1.GL.UNSIGNED_INT]);
const getAttributes = (gl, program, attributeLocations) => new Map([...new Array(gl.getProgramParameter(program, webgl_constants_1.GL.ACTIVE_ATTRIBUTES))].map((_, index) => {
const normalize = false;
const stride = 0;
const offset = 0;
const activeAttribInfo = gl.getActiveAttrib(program, index);
if (!activeAttribInfo)
throw new Error(`kinGLy exception: active attribute info could not be read`);
const { name, type } = activeAttribInfo;
if (name.startsWith('gl_'))
return [name, () => { }];
const location = attributeLocations[name] ?? NaN;
const buffer = gl.createBuffer();
gl.bindBuffer(webgl_constants_1.GL.ARRAY_BUFFER, buffer);
gl.enableVertexAttribArray(location);
const attribSize = attribSizeLookup[type] ?? NaN;
const attribElementType = attribElementTypeLookup[type] ?? NaN;
if (GL_DEBUG && (attribSize === undefined || attribElementType === undefined))
throw new Error(`Attribute type ${type} is not yet properly covered`);
if (integerTypes.has(attribElementType)) {
gl.vertexAttribIPointer(location, attribSize, webgl_constants_1.GL.INT, stride, offset);
}
else {
gl.vertexAttribPointer(location, attribSize, webgl_constants_1.GL.FLOAT, normalize, stride, offset);
}
const setValue = (data) => {
gl.bindBuffer(webgl_constants_1.GL.ARRAY_BUFFER, buffer);
gl.bufferData(webgl_constants_1.GL.ARRAY_BUFFER, data, webgl_constants_1.GL.STATIC_DRAW);
};
return [name, setValue];
}));
exports.getAttributes = getAttributes;
const bindVertexArray = (gl, vertexArrayObject) => {
if (vertexArrayObject !== currentVertexArrayObjects.get(gl)) {
currentVertexArrayObjects.set(gl, vertexArrayObject);
gl.bindVertexArray(vertexArrayObject);
}
};
exports.bindVertexArray = bindVertexArray;
const getRenderer = (gl, program, vao, { depthTest = false, blend = true, frontFace = webgl_constants_1.GL.CCW }) => {
const uniforms = getUniforms(gl, program);
return ({ uniformValues, viewport, target, clear, scissor, draw }) => {
if (!setGlobalConstants.has(gl)) {
setGlobalConstants.add(gl);
currentFlags.set(gl, { [webgl_constants_1.GL.DITHER]: true });
gl.blendFuncSeparate(webgl_constants_1.GL.SRC_ALPHA, webgl_constants_1.GL.ONE_MINUS_SRC_ALPHA, 1, 1);
gl.blendEquation(webgl_constants_1.GL.FUNC_ADD);
flagSet(gl, webgl_constants_1.GL.DITHER, false);
gl.depthMask(true);
gl.depthFunc(webgl_constants_1.GL.LESS);
gl.depthRange(0, 1);
gl.frontFace(frontFace);
flagSet(gl, webgl_constants_1.GL.CULL_FACE, true);
gl.cullFace(webgl_constants_1.GL.BACK);
gl.hint(webgl_constants_1.GL.FRAGMENT_SHADER_DERIVATIVE_HINT, webgl_constants_1.GL.NICEST);
}
if (depthTest !== currentDepthTests.get(gl)) {
currentDepthTests.set(gl, depthTest);
flagSet(gl, webgl_constants_1.GL.DEPTH_TEST, depthTest);
}
flagSet(gl, webgl_constants_1.GL.BLEND, blend);
if (program !== currentPrograms.get(gl)) {
currentPrograms.set(gl, program);
gl.useProgram(program);
}
if (vao)
(0, exports.bindVertexArray)(gl, vao);
if (viewport)
setViewport(gl, viewport.x, viewport.y, viewport.width, viewport.height);
if (uniformValues) {
uniforms.forEach((setValue, name) => uniformValues[name] && setValue(uniformValues[name]));
}
(0, exports.bindFramebuffer)(gl, webgl_constants_1.GL.DRAW_FRAMEBUFFER, target);
if (clear)
(0, exports.clearRect)(gl, clear);
if (draw) {
flagSet(gl, webgl_constants_1.GL.SCISSOR_TEST, scissor !== undefined);
if (scissor) {
setScissor(gl, ...scissor);
}
gl.drawArraysInstanced(draw.geom, draw.offset, draw.count, draw.instanceCount || 1);
}
};
};
exports.getRenderer = getRenderer;
const testContextLoss = (gl) => {
const lossTimeMs = 5000;
const regainTimeMs = 0;
const ext = gl.getExtension('WEBGL_lose_context');
if (ext) {
window.setInterval(() => {
console.log('Context loss test triggered, the webgl rendering will freeze or disappear');
ext.loseContext();
window.setTimeout(() => ext.restoreContext(), regainTimeMs);
}, lossTimeMs);
}
};
exports.testContextLoss = testContextLoss;
//# sourceMappingURL=kingly.js.map