forked from qwerty/tupali
470 lines
15 KiB
JavaScript
470 lines
15 KiB
JavaScript
/* *
|
|
*
|
|
* Copyright (c) 2019-2020 Highsoft AS
|
|
*
|
|
* Boost module: stripped-down renderer for higher performance
|
|
*
|
|
* License: highcharts.com/license
|
|
*
|
|
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
|
*
|
|
* */
|
|
'use strict';
|
|
import U from '../../parts/Utilities.js';
|
|
var clamp = U.clamp, error = U.error, pick = U.pick;
|
|
/* eslint-disable valid-jsdoc */
|
|
/**
|
|
* A static shader mimicing axis translation functions found in parts/Axis
|
|
*
|
|
* @private
|
|
* @function GLShader
|
|
*
|
|
* @param {WebGLContext} gl
|
|
* the context in which the shader is active
|
|
*
|
|
* @return {*}
|
|
*/
|
|
function GLShader(gl) {
|
|
var vertShade = [
|
|
/* eslint-disable max-len, @typescript-eslint/indent */
|
|
'#version 100',
|
|
'#define LN10 2.302585092994046',
|
|
'precision highp float;',
|
|
'attribute vec4 aVertexPosition;',
|
|
'attribute vec4 aColor;',
|
|
'varying highp vec2 position;',
|
|
'varying highp vec4 vColor;',
|
|
'uniform mat4 uPMatrix;',
|
|
'uniform float pSize;',
|
|
'uniform float translatedThreshold;',
|
|
'uniform bool hasThreshold;',
|
|
'uniform bool skipTranslation;',
|
|
'uniform float xAxisTrans;',
|
|
'uniform float xAxisMin;',
|
|
'uniform float xAxisMinPad;',
|
|
'uniform float xAxisPointRange;',
|
|
'uniform float xAxisLen;',
|
|
'uniform bool xAxisPostTranslate;',
|
|
'uniform float xAxisOrdinalSlope;',
|
|
'uniform float xAxisOrdinalOffset;',
|
|
'uniform float xAxisPos;',
|
|
'uniform bool xAxisCVSCoord;',
|
|
'uniform bool xAxisIsLog;',
|
|
'uniform bool xAxisReversed;',
|
|
'uniform float yAxisTrans;',
|
|
'uniform float yAxisMin;',
|
|
'uniform float yAxisMinPad;',
|
|
'uniform float yAxisPointRange;',
|
|
'uniform float yAxisLen;',
|
|
'uniform bool yAxisPostTranslate;',
|
|
'uniform float yAxisOrdinalSlope;',
|
|
'uniform float yAxisOrdinalOffset;',
|
|
'uniform float yAxisPos;',
|
|
'uniform bool yAxisCVSCoord;',
|
|
'uniform bool yAxisIsLog;',
|
|
'uniform bool yAxisReversed;',
|
|
'uniform bool isBubble;',
|
|
'uniform bool bubbleSizeByArea;',
|
|
'uniform float bubbleZMin;',
|
|
'uniform float bubbleZMax;',
|
|
'uniform float bubbleZThreshold;',
|
|
'uniform float bubbleMinSize;',
|
|
'uniform float bubbleMaxSize;',
|
|
'uniform bool bubbleSizeAbs;',
|
|
'uniform bool isInverted;',
|
|
'float bubbleRadius(){',
|
|
'float value = aVertexPosition.w;',
|
|
'float zMax = bubbleZMax;',
|
|
'float zMin = bubbleZMin;',
|
|
'float radius = 0.0;',
|
|
'float pos = 0.0;',
|
|
'float zRange = zMax - zMin;',
|
|
'if (bubbleSizeAbs){',
|
|
'value = value - bubbleZThreshold;',
|
|
'zMax = max(zMax - bubbleZThreshold, zMin - bubbleZThreshold);',
|
|
'zMin = 0.0;',
|
|
'}',
|
|
'if (value < zMin){',
|
|
'radius = bubbleZMin / 2.0 - 1.0;',
|
|
'} else {',
|
|
'pos = zRange > 0.0 ? (value - zMin) / zRange : 0.5;',
|
|
'if (bubbleSizeByArea && pos > 0.0){',
|
|
'pos = sqrt(pos);',
|
|
'}',
|
|
'radius = ceil(bubbleMinSize + pos * (bubbleMaxSize - bubbleMinSize)) / 2.0;',
|
|
'}',
|
|
'return radius * 2.0;',
|
|
'}',
|
|
'float translate(float val,',
|
|
'float pointPlacement,',
|
|
'float localA,',
|
|
'float localMin,',
|
|
'float minPixelPadding,',
|
|
'float pointRange,',
|
|
'float len,',
|
|
'bool cvsCoord,',
|
|
'bool isLog,',
|
|
'bool reversed',
|
|
'){',
|
|
'float sign = 1.0;',
|
|
'float cvsOffset = 0.0;',
|
|
'if (cvsCoord) {',
|
|
'sign *= -1.0;',
|
|
'cvsOffset = len;',
|
|
'}',
|
|
'if (isLog) {',
|
|
'val = log(val) / LN10;',
|
|
'}',
|
|
'if (reversed) {',
|
|
'sign *= -1.0;',
|
|
'cvsOffset -= sign * len;',
|
|
'}',
|
|
'return sign * (val - localMin) * localA + cvsOffset + ',
|
|
'(sign * minPixelPadding);',
|
|
'}',
|
|
'float xToPixels(float value) {',
|
|
'if (skipTranslation){',
|
|
'return value;// + xAxisPos;',
|
|
'}',
|
|
'return translate(value, 0.0, xAxisTrans, xAxisMin, xAxisMinPad, xAxisPointRange, xAxisLen, xAxisCVSCoord, xAxisIsLog, xAxisReversed);// + xAxisPos;',
|
|
'}',
|
|
'float yToPixels(float value, float checkTreshold) {',
|
|
'float v;',
|
|
'if (skipTranslation){',
|
|
'v = value;// + yAxisPos;',
|
|
'} else {',
|
|
'v = translate(value, 0.0, yAxisTrans, yAxisMin, yAxisMinPad, yAxisPointRange, yAxisLen, yAxisCVSCoord, yAxisIsLog, yAxisReversed);// + yAxisPos;',
|
|
'if (v > yAxisLen) {',
|
|
'v = yAxisLen;',
|
|
'}',
|
|
'}',
|
|
'if (checkTreshold > 0.0 && hasThreshold) {',
|
|
'v = min(v, translatedThreshold);',
|
|
'}',
|
|
'return v;',
|
|
'}',
|
|
'void main(void) {',
|
|
'if (isBubble){',
|
|
'gl_PointSize = bubbleRadius();',
|
|
'} else {',
|
|
'gl_PointSize = pSize;',
|
|
'}',
|
|
// 'gl_PointSize = 10.0;',
|
|
'vColor = aColor;',
|
|
'if (skipTranslation && isInverted) {',
|
|
// If we get translated values from JS, just swap them (x, y)
|
|
'gl_Position = uPMatrix * vec4(aVertexPosition.y + yAxisPos, aVertexPosition.x + xAxisPos, 0.0, 1.0);',
|
|
'} else if (isInverted) {',
|
|
// But when calculating pixel positions directly,
|
|
// swap axes and values (x, y)
|
|
'gl_Position = uPMatrix * vec4(yToPixels(aVertexPosition.y, aVertexPosition.z) + yAxisPos, xToPixels(aVertexPosition.x) + xAxisPos, 0.0, 1.0);',
|
|
'} else {',
|
|
'gl_Position = uPMatrix * vec4(xToPixels(aVertexPosition.x) + xAxisPos, yToPixels(aVertexPosition.y, aVertexPosition.z) + yAxisPos, 0.0, 1.0);',
|
|
'}',
|
|
// 'gl_Position = uPMatrix * vec4(aVertexPosition.x, aVertexPosition.y, 0.0, 1.0);',
|
|
'}'
|
|
/* eslint-enable max-len, @typescript-eslint/indent */
|
|
].join('\n'),
|
|
// Fragment shader source
|
|
fragShade = [
|
|
/* eslint-disable max-len, @typescript-eslint/indent */
|
|
'precision highp float;',
|
|
'uniform vec4 fillColor;',
|
|
'varying highp vec2 position;',
|
|
'varying highp vec4 vColor;',
|
|
'uniform sampler2D uSampler;',
|
|
'uniform bool isCircle;',
|
|
'uniform bool hasColor;',
|
|
// 'vec4 toColor(float value, vec2 point) {',
|
|
// 'return vec4(0.0, 0.0, 0.0, 0.0);',
|
|
// '}',
|
|
'void main(void) {',
|
|
'vec4 col = fillColor;',
|
|
'vec4 tcol;',
|
|
'if (hasColor) {',
|
|
'col = vColor;',
|
|
'}',
|
|
'if (isCircle) {',
|
|
'tcol = texture2D(uSampler, gl_PointCoord.st);',
|
|
'col *= tcol;',
|
|
'if (tcol.r < 0.0) {',
|
|
'discard;',
|
|
'} else {',
|
|
'gl_FragColor = col;',
|
|
'}',
|
|
'} else {',
|
|
'gl_FragColor = col;',
|
|
'}',
|
|
'}'
|
|
/* eslint-enable max-len, @typescript-eslint/indent */
|
|
].join('\n'), uLocations = {},
|
|
// The shader program
|
|
shaderProgram,
|
|
// Uniform handle to the perspective matrix
|
|
pUniform,
|
|
// Uniform for point size
|
|
psUniform,
|
|
// Uniform for fill color
|
|
fillColorUniform,
|
|
// Uniform for isBubble
|
|
isBubbleUniform,
|
|
// Uniform for bubble abs sizing
|
|
bubbleSizeAbsUniform, bubbleSizeAreaUniform,
|
|
// Skip translation uniform
|
|
skipTranslationUniform,
|
|
// Set to 1 if circle
|
|
isCircleUniform,
|
|
// Uniform for invertion
|
|
isInverted,
|
|
// Error stack
|
|
errors = [],
|
|
// Texture uniform
|
|
uSamplerUniform;
|
|
/**
|
|
* Handle errors accumulated in errors stack
|
|
* @private
|
|
*/
|
|
function handleErrors() {
|
|
if (errors.length) {
|
|
error('[highcharts boost] shader error - ' + errors.join('\n'));
|
|
}
|
|
}
|
|
/**
|
|
* String to shader program
|
|
* @private
|
|
* @param {string} str - the program source
|
|
* @param {string} type - the program type: either `vertex` or `fragment`
|
|
* @returns {bool|shader}
|
|
*/
|
|
function stringToProgram(str, type) {
|
|
var t = type === 'vertex' ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER, shader = gl.createShader(t);
|
|
gl.shaderSource(shader, str);
|
|
gl.compileShader(shader);
|
|
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
errors.push('when compiling ' +
|
|
type +
|
|
' shader:\n' +
|
|
gl.getShaderInfoLog(shader));
|
|
return false;
|
|
}
|
|
return shader;
|
|
}
|
|
/**
|
|
* Create the shader.
|
|
* Loads the shader program statically defined above
|
|
* @private
|
|
*/
|
|
function createShader() {
|
|
var v = stringToProgram(vertShade, 'vertex'), f = stringToProgram(fragShade, 'fragment');
|
|
if (!v || !f) {
|
|
shaderProgram = false;
|
|
handleErrors();
|
|
return false;
|
|
}
|
|
/**
|
|
* @private
|
|
*/
|
|
function uloc(n) {
|
|
return gl.getUniformLocation(shaderProgram, n);
|
|
}
|
|
shaderProgram = gl.createProgram();
|
|
gl.attachShader(shaderProgram, v);
|
|
gl.attachShader(shaderProgram, f);
|
|
gl.linkProgram(shaderProgram);
|
|
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
|
|
errors.push(gl.getProgramInfoLog(shaderProgram));
|
|
handleErrors();
|
|
shaderProgram = false;
|
|
return false;
|
|
}
|
|
gl.useProgram(shaderProgram);
|
|
gl.bindAttribLocation(shaderProgram, 0, 'aVertexPosition');
|
|
pUniform = uloc('uPMatrix');
|
|
psUniform = uloc('pSize');
|
|
fillColorUniform = uloc('fillColor');
|
|
isBubbleUniform = uloc('isBubble');
|
|
bubbleSizeAbsUniform = uloc('bubbleSizeAbs');
|
|
bubbleSizeAreaUniform = uloc('bubbleSizeByArea');
|
|
uSamplerUniform = uloc('uSampler');
|
|
skipTranslationUniform = uloc('skipTranslation');
|
|
isCircleUniform = uloc('isCircle');
|
|
isInverted = uloc('isInverted');
|
|
return true;
|
|
}
|
|
/**
|
|
* Destroy the shader
|
|
* @private
|
|
*/
|
|
function destroy() {
|
|
if (gl && shaderProgram) {
|
|
gl.deleteProgram(shaderProgram);
|
|
shaderProgram = false;
|
|
}
|
|
}
|
|
/**
|
|
* Bind the shader.
|
|
* This makes the shader the active one until another one is bound,
|
|
* or until 0 is bound.
|
|
* @private
|
|
*/
|
|
function bind() {
|
|
if (gl && shaderProgram) {
|
|
gl.useProgram(shaderProgram);
|
|
}
|
|
}
|
|
/**
|
|
* Set a uniform value.
|
|
* This uses a hash map to cache uniform locations.
|
|
* @private
|
|
* @param name {string} - the name of the uniform to set
|
|
* @param val {float} - the value to set
|
|
*/
|
|
function setUniform(name, val) {
|
|
if (gl && shaderProgram) {
|
|
var u = uLocations[name] = (uLocations[name] ||
|
|
gl.getUniformLocation(shaderProgram, name));
|
|
gl.uniform1f(u, val);
|
|
}
|
|
}
|
|
/**
|
|
* Set the active texture
|
|
* @private
|
|
* @param texture - the texture
|
|
*/
|
|
function setTexture(texture) {
|
|
if (gl && shaderProgram) {
|
|
gl.uniform1i(uSamplerUniform, texture);
|
|
}
|
|
}
|
|
/**
|
|
* Set if inversion state
|
|
* @private
|
|
* @flag is the state
|
|
*/
|
|
function setInverted(flag) {
|
|
if (gl && shaderProgram) {
|
|
gl.uniform1i(isInverted, flag);
|
|
}
|
|
}
|
|
/**
|
|
* Enable/disable circle drawing
|
|
* @private
|
|
*/
|
|
function setDrawAsCircle(flag) {
|
|
if (gl && shaderProgram) {
|
|
gl.uniform1i(isCircleUniform, flag ? 1 : 0);
|
|
}
|
|
}
|
|
/**
|
|
* Flush
|
|
* @private
|
|
*/
|
|
function reset() {
|
|
if (gl && shaderProgram) {
|
|
gl.uniform1i(isBubbleUniform, 0);
|
|
gl.uniform1i(isCircleUniform, 0);
|
|
}
|
|
}
|
|
/**
|
|
* Set bubble uniforms
|
|
* @private
|
|
* @param series {Highcharts.Series} - the series to use
|
|
*/
|
|
function setBubbleUniforms(series, zCalcMin, zCalcMax) {
|
|
var seriesOptions = series.options, zMin = Number.MAX_VALUE, zMax = -Number.MAX_VALUE;
|
|
if (gl && shaderProgram && series.type === 'bubble') {
|
|
zMin = pick(seriesOptions.zMin, clamp(zCalcMin, seriesOptions.displayNegative === false ?
|
|
seriesOptions.zThreshold : -Number.MAX_VALUE, zMin));
|
|
zMax = pick(seriesOptions.zMax, Math.max(zMax, zCalcMax));
|
|
gl.uniform1i(isBubbleUniform, 1);
|
|
gl.uniform1i(isCircleUniform, 1);
|
|
gl.uniform1i(bubbleSizeAreaUniform, (series.options.sizeBy !== 'width'));
|
|
gl.uniform1i(bubbleSizeAbsUniform, series.options
|
|
.sizeByAbsoluteValue);
|
|
setUniform('bubbleZMin', zMin);
|
|
setUniform('bubbleZMax', zMax);
|
|
setUniform('bubbleZThreshold', series.options.zThreshold);
|
|
setUniform('bubbleMinSize', series.minPxSize);
|
|
setUniform('bubbleMaxSize', series.maxPxSize);
|
|
}
|
|
}
|
|
/**
|
|
* Set the Color uniform.
|
|
* @private
|
|
* @param color {Array<float>} - an array with RGBA values
|
|
*/
|
|
function setColor(color) {
|
|
if (gl && shaderProgram) {
|
|
gl.uniform4f(fillColorUniform, color[0] / 255.0, color[1] / 255.0, color[2] / 255.0, color[3]);
|
|
}
|
|
}
|
|
/**
|
|
* Set skip translation
|
|
* @private
|
|
*/
|
|
function setSkipTranslation(flag) {
|
|
if (gl && shaderProgram) {
|
|
gl.uniform1i(skipTranslationUniform, flag === true ? 1 : 0);
|
|
}
|
|
}
|
|
/**
|
|
* Set the perspective matrix
|
|
* @private
|
|
* @param m {Matrix4x4} - the matrix
|
|
*/
|
|
function setPMatrix(m) {
|
|
if (gl && shaderProgram) {
|
|
gl.uniformMatrix4fv(pUniform, false, m);
|
|
}
|
|
}
|
|
/**
|
|
* Set the point size.
|
|
* @private
|
|
* @param p {float} - point size
|
|
*/
|
|
function setPointSize(p) {
|
|
if (gl && shaderProgram) {
|
|
gl.uniform1f(psUniform, p);
|
|
}
|
|
}
|
|
/**
|
|
* Get the shader program handle
|
|
* @private
|
|
* @return {GLInt} - the handle for the program
|
|
*/
|
|
function getProgram() {
|
|
return shaderProgram;
|
|
}
|
|
if (gl) {
|
|
if (!createShader()) {
|
|
return false;
|
|
}
|
|
}
|
|
return {
|
|
psUniform: function () {
|
|
return psUniform;
|
|
},
|
|
pUniform: function () {
|
|
return pUniform;
|
|
},
|
|
fillColorUniform: function () {
|
|
return fillColorUniform;
|
|
},
|
|
setBubbleUniforms: setBubbleUniforms,
|
|
bind: bind,
|
|
program: getProgram,
|
|
create: createShader,
|
|
setUniform: setUniform,
|
|
setPMatrix: setPMatrix,
|
|
setColor: setColor,
|
|
setPointSize: setPointSize,
|
|
setSkipTranslation: setSkipTranslation,
|
|
setTexture: setTexture,
|
|
setDrawAsCircle: setDrawAsCircle,
|
|
reset: reset,
|
|
setInverted: setInverted,
|
|
destroy: destroy
|
|
};
|
|
}
|
|
export default GLShader;
|