WebGl

WebGLUtil

This is a utility class used to manipulate GL resources.

var WebGlUtil = require('paraviewweb/src/Common/Misc/WebGl');

showGlInfo(gl)

Print GL information regarding the WebGL context provided.

createGLResources(gl, glConfig) : glResources

Create and configure all Gl resources described in the configuration using the
provided GL context.

var sampleConfig = {
programs: {
displayProgram: {
vertexShader: require('./shaders/vertex/basicVertex.c'),
fragmentShader: require('./shaders/fragment/displayFragment.c'),
mapping: 'default'
},
compositeProgram: {
vertexShader: require('./shaders/vertex/basicVertex.c'),
fragmentShader: require('./shaders/fragment/compositeFragment.c'),
mapping: 'default'
}
},
resources: {
buffers: [
{
id: 'texCoord',
data: 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
])
},{
id: 'posCoord',
data: new Float32Array([
-1, -1,
1, -1,
-1, 1,
-1, 1,
1, -1,
1, 1
])
}
],
textures: [
{
id: 'texture2D',
pixelStore: [
[ 'UNPACK_FLIP_Y_WEBGL', true ]
],
texParameter: [
[ 'TEXTURE_MAG_FILTER', 'NEAREST' ],
[ 'TEXTURE_MIN_FILTER', 'NEAREST' ],
[ 'TEXTURE_WRAP_S', 'CLAMP_TO_EDGE' ],
[ 'TEXTURE_WRAP_T', 'CLAMP_TO_EDGE' ],
]
},{
id: 'ping',
pixelStore: [
[ 'UNPACK_FLIP_Y_WEBGL', true ]
],
texParameter: [
[ 'TEXTURE_MAG_FILTER', 'NEAREST' ],
[ 'TEXTURE_MIN_FILTER', 'NEAREST' ],
[ 'TEXTURE_WRAP_S', 'CLAMP_TO_EDGE' ],
[ 'TEXTURE_WRAP_T', 'CLAMP_TO_EDGE' ],
]
},{
id: 'pong',
pixelStore: [
[ 'UNPACK_FLIP_Y_WEBGL', true ]
],
texParameter: [
[ 'TEXTURE_MAG_FILTER', 'NEAREST' ],
[ 'TEXTURE_MIN_FILTER', 'NEAREST' ],
[ 'TEXTURE_WRAP_S', 'CLAMP_TO_EDGE' ],
[ 'TEXTURE_WRAP_T', 'CLAMP_TO_EDGE' ],
]
}
],
framebuffers: [
{
id: 'ping',
width: this.width,
height: this.height
},{
id: 'pong',
width: this.width,
height: this.height
}
]
},
mappings: {
default: [
{ id: 'posCoord', name: 'positionLocation', attribute: 'a_position', format: [ 2, this.gl.FLOAT, false, 0, 0 ] },
{ id: 'texCoord', name: 'texCoordLocation', attribute: 'a_texCoord', format: [ 2, this.gl.FLOAT, false, 0, 0 ] }
]
}
};

The returned glResource object will have a destroy() method that let you
free the created resources.

applyProgramDataMapping(gl, programName, mappingName, glConfig, glResources)

The mapping between buffers and programs is done at creation time but if other
mapping need to be done after the resource creation, this can be performed using
that function.

TransformShader(shaderString, variableDict, config)

This function can transform shader programs before they are compiled into
a WebGL program in one of several ways.

String replacement

The variableDict can contain key value mappings and this function will look
for the keys (which must be surrounded by “${“ and “}”) in the shader string,
and replace them with the values from the dictionary. For example, if the
variableDict contains the mapping "MAX_COUNT" -> "7", then this function will
look for instances of the string "${MAX_COUNT}" in the file and replace them
with the value "7".

String replacement happens before any other kind of shader transformations,
and the result of string replacement is then passed on to the next level of
processing, loop unrolling.

Loop unrolling

This function can unroll properly annotated loops within a shader source file.
Insert a comment just before and after the loop and then pass
'inlineLoops': true in the config argument to get loops unrolled before the
shader source is compiled, but after string replacement has occurred.

The comment preceeding the loop code in the shader source must take the form
"//@INLINE_LOOP (<variableName>, <minLoopIndex>, <maxLoopIndex>)",
where <minLoopIndex> is the first loop variable value, and <maxLoopIndex>
is the last loop index variable (but is not inclusive). The comment after
the loop code in the shader must take the form "//@INLINE_LOOP" to indicate
the end of the block.

Following is an example of an annotated loop and how it would be unrolled:

GLSL loop code:

//@INLINE_LOOP (loopIdx, 0, 3)
for (int loopIdx = 0; loopIdx < 3; ++loopIdx) {
if (loopIdx == someVariable) {
gl_FragColor = texture2D(someSampler[loopIdx], someTexCoord);
}
}
//@INLINE_LOOP

Unrolled loop:

if (0 == someVariable) {
gl_FragColor = texture2D(someSampler[0], someTexCoord);
}

if (1 == someVariable) {
gl_FragColor = texture2D(someSampler[1], someTexCoord);
}

if (2 == someVariable) {
gl_FragColor = texture2D(someSampler[2], someTexCoord);
}

Shader tranformation debugging

In order to get the system to print out the transformed shader to the console
log before compiling, pass 'debug': true to the TransformShader function
within the config argument.

Source

index.js
// Show GL informations
function showGlInfo(gl) {
const vertexUnits = gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS);
const fragmentUnits = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
const combinedUnits = gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS);
console.log('vertex texture image units:', vertexUnits);
console.log('fragment texture image units:', fragmentUnits);
console.log('combined texture image units:', combinedUnits);
}

// Compile a shader
function compileShader(gl, src, type) {
const shader = gl.createShader(type);

gl.shaderSource(shader, src);

// Compile and check status
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
// Something went wrong during compilation; get the error
const lastError = gl.getShaderInfoLog(shader);
console.error(`Error compiling shader '${shader}': ${lastError}`);
gl.deleteShader(shader);

return null;
}

return shader;
}

// Create a shader program
function createShaderProgram(gl, shaders) {
const program = gl.createProgram();

for (let i = 0; i < shaders.length; i++) {
gl.attachShader(program, shaders[i]);
}

gl.linkProgram(program);

// Check the link status
const linked = gl.getProgramParameter(program, gl.LINK_STATUS);
if (!linked) {
// something went wrong with the link
const lastError = gl.getProgramInfoLog(program);
console.error('Error in program linking:', lastError);
gl.deleteProgram(program);

return null;
}

program.shaders = shaders;
gl.useProgram(program);

return program;
}

// Apply new mapping to a program
function applyProgramDataMapping(
gl,
programName,
mappingName,
glConfig,
glResources
) {
const program = glResources.programs[programName];
const mapping = glConfig.mappings[mappingName];

mapping.forEach((bufferMapping) => {
const glBuffer = glResources.buffers[bufferMapping.id];

gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer);
program[bufferMapping.name] = gl.getAttribLocation(
program,
bufferMapping.attribute
);
gl.enableVertexAttribArray(program[bufferMapping.name]);
gl.vertexAttribPointer(
program[bufferMapping.name],
...bufferMapping.format
);
// FIXME: Remove this check when Apple fixes this bug
/* global navigator */
// const buggyBrowserVersion = ['AppleWebKit/602.1.50', 'AppleWebKit/602.2.14'];
if (navigator.userAgent.indexOf('AppleWebKit/602') === -1) {
gl.bindBuffer(gl.ARRAY_BUFFER, null);
}
});
}

// Create a shader program
function buildShaderProgram(gl, name, config, resources) {
const progConfig = config.programs[name];
const compiledVertexShader = compileShader(
gl,
progConfig.vertexShader,
gl.VERTEX_SHADER
);
const compiledFragmentShader = compileShader(
gl,
progConfig.fragmentShader,
gl.FRAGMENT_SHADER
);
const program = createShaderProgram(gl, [
compiledVertexShader,
compiledFragmentShader,
]);

// Store the created program in the resources
resources.programs[name] = program;

// Handle mapping if any
if (progConfig.mapping) {
applyProgramDataMapping(gl, name, progConfig.mapping, config, resources);
}

// Return program
return program;
}

// Bind texture to Framebuffer
function bindTextureToFramebuffer(gl, fbo, texture) {
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
gl.bindTexture(gl.TEXTURE_2D, texture);

gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.RGBA,
fbo.width,
fbo.height,
0,
gl.RGBA,
gl.UNSIGNED_BYTE,
null
);

gl.framebufferTexture2D(
gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D,
texture,
0
);

// Check fbo status
const fbs = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
if (fbs !== gl.FRAMEBUFFER_COMPLETE) {
console.log('ERROR: There is a problem with the framebuffer:', fbs);
}

// Clear the bindings we created in this function.
gl.bindTexture(gl.TEXTURE_2D, null);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
}

// Free GL resources
function freeGLResources(glResources) {
const gl = glResources.gl;

// Delete each program
Object.keys(glResources.programs).forEach((programName) => {
const program = glResources.programs[programName];
const shaders = program.shaders;

let count = shaders.length;

// Delete shaders
while (count) {
count -= 1;
gl.deleteShader(shaders[count]);
}

// Delete program
gl.deleteProgram(program);
});

// Delete framebuffers
Object.keys(glResources.framebuffers).forEach((fbName) => {
gl.deleteFramebuffer(glResources.framebuffers[fbName]);
});

// Delete textures
Object.keys(glResources.textures).forEach((textureName) => {
gl.deleteTexture(glResources.textures[textureName]);
});

// Delete buffers
Object.keys(glResources.buffers).forEach((bufferName) => {
gl.deleteBuffer(glResources.buffers[bufferName]);
});
}

// Create GL resources
function createGLResources(gl, glConfig) {
const resources = {
gl,
buffers: {},
textures: {},
framebuffers: {},
programs: {},
};
const buffers = glConfig.resources.buffers || [];
const textures = glConfig.resources.textures || [];
const framebuffers = glConfig.resources.framebuffers || [];

// Create Buffer
buffers.forEach((buffer) => {
const glBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer);
gl.bufferData(gl.ARRAY_BUFFER, buffer.data, gl.STATIC_DRAW);
resources.buffers[buffer.id] = glBuffer;
});

// Create Texture
textures.forEach((texture) => {
const glTexture = gl.createTexture();
const pixelStore = texture.pixelStore || [];
const texParameter = texture.texParameter || [];

gl.bindTexture(gl.TEXTURE_2D, glTexture);

pixelStore.forEach((option) => {
gl.pixelStorei(gl[option[0]], option[1]);
});

texParameter.forEach((option) => {
gl.texParameteri(gl.TEXTURE_2D, gl[option[0]], gl[option[1]]);
});

resources.textures[texture.id] = glTexture;
});

// Create Framebuffer
framebuffers.forEach((framebuffer) => {
const glFramebuffer = gl.createFramebuffer();
glFramebuffer.width = framebuffer.width;
glFramebuffer.height = framebuffer.height;

resources.framebuffers[framebuffer.id] = glFramebuffer;
});

// Create programs
Object.keys(glConfig.programs).forEach((programName) => {
buildShaderProgram(gl, programName, glConfig, resources);
});

// Add destroy function
resources.destroy = () => {
freeGLResources(resources);
};

return resources;
}

//----------------------------------------------------------------------------

function transformShader(shaderContent, variableDict, config) {
let match = null;
let unrolledContents = null;
let shaderString = shaderContent;

// First do all the variable replacements
Object.keys(variableDict).forEach((vname) => {
const value = variableDict[vname];
const r = new RegExp(`\\$\\{${vname}\\}`, 'g');
shaderString = shaderString.replace(r, value);
});

// Now check if any loops need to be inlined
if (config.inlineLoops) {
const loopRegex = /\/\/@INLINE_LOOP([\s\S]+?)(?=\/\/@INLINE_LOOP)\/\/@INLINE_LOOP/;

match = shaderString.match(loopRegex);
while (match) {
const capture = match[1];
/* eslint-disable no-useless-escape */
const infoRegex = /^\s*\(([^\),]+)\s*,\s*([^\),]+)\s*,\s*([^\)]+)\)/;
const infoRegexMatch = capture.match(infoRegex);
const loopVariableName = infoRegexMatch[1];
const loopMin = infoRegexMatch[2];
const loopCount = infoRegexMatch[3];
const forLoop = capture.replace(infoRegex, '');
const loopContentsRegex = /^\s*[^\{]+\{([\s\S]+?)\s*\}\s*$/;
/* eslint-enable no-useless-escape */
const forLoopMatch = forLoop.match(loopContentsRegex);
const loopBody = forLoopMatch[1];
const loopBodyReplacer = new RegExp(loopVariableName, 'g');

unrolledContents = '';
for (let i = loopMin; i < loopCount; ++i) {
unrolledContents += loopBody.replace(loopBodyReplacer, i);
unrolledContents += '\n';
}

shaderString = shaderString.replace(loopRegex, unrolledContents);
match = shaderString.match(loopRegex);
}
}

if (config.debug) {
console.log('Transformed shader string:');
console.log(shaderString);
}

return shaderString;
}

//----------------------------------------------------------------------------

export default {
applyProgramDataMapping,
bindTextureToFramebuffer,
createGLResources,
showGlInfo,
transformShader,
};