ShaderProgram

encapsulate a glsl shader program

represents a shaderprogram in WebGL

vertexShader

the vertex shader for this program

fragmentShader

the fragment shader for this program

geometrySshader

the geometry shader for this program

compiled

flag for if this program is compiled

md5hash

the md5 hash of this program

/** Options for attribute normalization. */
enum NormalizeOption {
/// The values range across the limits of the numeric type.
/// This option instructs the rendering engine to normalize them to
/// the range [0.0, 1.0] for unsigned types, and [-1.0, 1.0] for signed
/// types.
/// For example, unsigned char values will be mapped so that 0 = 0.0,
/// and 255 = 1.0.
/// The resulting floating point numbers will be passed into
/// the shader program.
Normalize,
/// The values should be used as-is. Do not perform any normalization.
NoNormalize
};

isBound()

Check if the program is currently bound, or not.

releaseGraphicsResources(win)

release any graphics resources being used

handle

the handle of the shader program.

error

the error message (empty if none) for the shader program.

enableAttributeArray(name)

Enable the named attribute array. Return false if the attribute array is
not contained in the linked shader program.

disableAttributeArray(name)

Disable the named attribute array. Return false if the attribute array is
not contained in the linked shader program.

useAttributeArray(name, offset, stride,

                  elementType, elementTupleSize,
                  normalize);

Use the named attribute array with the bound BufferObject.

  • name of the attribute (as seen in the shader program).
  • offset into the bound BufferObject.
  • stride The stride of the element access (i.e. the size of each
    element in the currently bound BufferObject). 0 may be used to indicate
    tightly packed data.
  • elementType Tag identifying the memory representation of the
    element.
  • elementTupleSize The number of elements per vertex (e.g. a 3D
    position attribute would be 3).
  • normalize Indicates the range used by the attribute data.
  • See NormalizeOption for more information.
    return false if the attribute array does not exist.

setAttributeArray(name, array,

                  tupleSize, normalize);

/** Set the @p name uniform value to int @p v. */
bool SetUniformi(const char *name, int v);
bool SetUniformf(const char *name, float v);
bool SetUniform2i(const char *name, int v1, int v2);
bool SetUniform2f(const char *name, float v1, float v2);
bool SetUniform3f(const char *name, float v1, float v2, float v3);
bool SetUniform3fArray(const char *name, const float v[3]);
bool SetUniform4f(const char *name, const float v[4]);
bool SetUniform3uc(const char *name, const unsigned char v[3]); // maybe remove
bool SetUniform4uc(const char *name, const unsigned char v[4]); // maybe remove
bool SetUniformMatrix(const char *name, vtkMatrix3x3 *v);
bool SetUniformMatrix(const char *name, vtkMatrix4x4 *v);
bool SetUniformMatrix3x3(const char *name, float *v);
bool SetUniformMatrix4x4(const char *name, float *v);

/** Set the @p name uniform array to @p f with @p count elements */
bool SetUniform1iv(const char *name, const int count, const int *f);
bool SetUniform1fv(const char *name, const int count, const float *f);
bool SetUniform2fv(const char *name, const int count, const float (*f)[2]);
bool SetUniform3fv(const char *name, const int count, const float (*f)[3]);
bool SetUniform4fv(const char *name, const int count, const float (*f)[4]);
bool SetUniformMatrix4x4v(const char *name, const int count, float *v);

// How many outputs does this program produce
// only valid for OpenGL 3.2 or later
vtkSetMacro(NumberOfOutputs,unsigned int);

//BTX
// Description:
// perform in place string substitutions, indicate if a substitution was done
// this is useful for building up shader strings which typically involve
// lots of string substitutions. Return true if a substitution was done.
static bool Substitute(
std::string &source,
const std::string &search,
const std::string &replace,
bool all = true);

// Description:
// methods to inquire as to what uniforms/attributes are used by
// this shader. This can save some compute time if the uniforms
// or attributes are expensive to compute
bool IsUniformUsed(const char *);

// Description:
// Return true if the compiled and linked shader has an attribute matching @a
// name.
bool IsAttributeUsed(const char *name);

*/

attachShader(shader)

  • Attach the supplied shader to this program.

  • @note A maximum of one Vertex shader and one Fragment shader can be

  • attached to a shader program.

  • @return true on success.

    */

    detachShader(shader)

/** Detach the supplied shader from this program.

  • @note A maximum of one Vertex shader and one Fragment shader can be
  • attached to a shader program.
  • @return true on success.

compileShader()

Compile this shader program and attached shaders

link()

  • Attempt to link the shader program.
  • @return false on failure. Query error to get the reason.
  • @note The shaders attached to the program must have been compiled.

bind()

Bind the program in order to use it. If the program has not been linked
then link() will be called.

release();

Releases the shader program from the current context. */

Source

index.js
import macro from 'vtk.js/Sources/macros';
import vtkShader from 'vtk.js/Sources/Rendering/OpenGL/Shader';

const { vtkErrorMacro } = macro;

// perform in place string substitutions, indicate if a substitution was done
// this is useful for building up shader strings which typically involve
// lots of string substitutions. Return true if a substitution was done.
export function substitute(source, search, replace, all) {
// We only accept strings or array of strings, typeof is faster than Array.isArray
const replaceStr = typeof replace === 'string' ? replace : replace.join('\n');

// We don't need to instantiate a RegExp if we don't want a global substitution.
// In all other cases, we need to take the provided string or RegExp and
// instantiate a new one to add the `g` flag.
// Argument defaults are transpiled to slow `arguments`-based operations
// better assume undefined as flag to know if the value is set or not
const replaceSearch = all === false ? search : new RegExp(search, 'g');

const resultstr = source.replace(replaceSearch, replaceStr);
return {
// If the result is different than the input, we did perform a replacement
replace: resultstr !== replaceStr,
result: resultstr,
};
}

// ----------------------------------------------------------------------------
// vtkShaderProgram methods
// ----------------------------------------------------------------------------

function vtkShaderProgram(publicAPI, model) {
// Set our className
model.classHierarchy.push('vtkShaderProgram');

publicAPI.compileShader = () => {
if (!model.vertexShader.compile()) {
vtkErrorMacro(
model.vertexShader
.getSource()
.split('\n')
.map((line, index) => `${index}: ${line}`)
.join('\n')
);
vtkErrorMacro(model.vertexShader.getError());
return 0;
}
if (!model.fragmentShader.compile()) {
vtkErrorMacro(
model.fragmentShader
.getSource()
.split('\n')
.map((line, index) => `${index}: ${line}`)
.join('\n')
);
vtkErrorMacro(model.fragmentShader.getError());
return 0;
}
// skip geometry for now
if (!publicAPI.attachShader(model.vertexShader)) {
vtkErrorMacro(model.error);
return 0;
}
if (!publicAPI.attachShader(model.fragmentShader)) {
vtkErrorMacro(model.error);
return 0;
}

if (!publicAPI.link()) {
vtkErrorMacro(`Links failed: ${model.error}`);
return 0;
}

publicAPI.setCompiled(true);
return 1;
};

publicAPI.cleanup = () => {
if (model.shaderType === 'Unknown' || model.handle === 0) {
return;
}
publicAPI.release();
if (model.vertexShaderHandle !== 0) {
model.context.detachShader(model.handle, model.vertexShaderHandle);
model.vertexShaderHandle = 0;
}
if (model.fragmentShaderHandle !== 0) {
model.context.detachShader(model.handle, model.fragmentShaderHandle);
model.fragmentShaderHandle = 0;
}
model.context.deleteProgram(model.handle);
model.handle = 0;
publicAPI.setCompiled(false);
};

publicAPI.bind = () => {
if (!model.linked && !publicAPI.link()) {
return false;
}

model.context.useProgram(model.handle);
publicAPI.setBound(true);
return true;
};

publicAPI.isBound = () => !!model.bound;

publicAPI.release = () => {
model.context.useProgram(null);
publicAPI.setBound(false);
};

publicAPI.setContext = (ctx) => {
model.vertexShader.setContext(ctx);
model.fragmentShader.setContext(ctx);
model.geometryShader.setContext(ctx);
};

publicAPI.link = () => {
if (model.linked) {
return true;
}

if (model.handle === 0) {
model.error =
'Program has not been initialized, and/or does not have shaders.';
return false;
}

// clear out the list of uniforms used
model.uniformLocs = {};

model.context.linkProgram(model.handle);
const isCompiled = model.context.getProgramParameter(
model.handle,
model.context.LINK_STATUS
);
if (!isCompiled) {
const lastError = model.context.getProgramInfoLog(model.handle);
vtkErrorMacro(`Error linking shader ${lastError}`);
model.handle = 0;
return false;
}

publicAPI.setLinked(true);
model.attributeLocs = {};
return true;
};

publicAPI.setUniformMatrix = (name, v) => {
const location = publicAPI.findUniform(name);
if (location === -1) {
model.error = `Could not set uniform ${name} . No such uniform.`;
return false;
}
const f32 = new Float32Array(v);
model.context.uniformMatrix4fv(location, false, f32);
return true;
};

publicAPI.setUniformMatrix3x3 = (name, v) => {
const location = publicAPI.findUniform(name);
if (location === -1) {
model.error = `Could not set uniform ${name} . No such uniform.`;
return false;
}
const f32 = new Float32Array(v);
model.context.uniformMatrix3fv(location, false, f32);
return true;
};

publicAPI.setUniformf = (name, v) => {
const location = publicAPI.findUniform(name);
if (location === -1) {
model.error = `Could not set uniform ${name} . No such uniform.`;
return false;
}
model.context.uniform1f(location, v);
return true;
};

publicAPI.setUniformfv = (name, v) => {
const location = publicAPI.findUniform(name);
if (location === -1) {
model.error = `Could not set uniform ${name} . No such uniform.`;
return false;
}
model.context.uniform1fv(location, v);
return true;
};

publicAPI.setUniformi = (name, v) => {
const location = publicAPI.findUniform(name);
if (location === -1) {
model.error = `Could not set uniform ${name} . No such uniform.`;
return false;
}
model.context.uniform1i(location, v);
return true;
};

publicAPI.setUniformiv = (name, v) => {
const location = publicAPI.findUniform(name);
if (location === -1) {
model.error = `Could not set uniform ${name} . No such uniform.`;
return false;
}
model.context.uniform1iv(location, v);
return true;
};

publicAPI.setUniform2f = (name, v1, v2) => {
const location = publicAPI.findUniform(name);
if (location === -1) {
model.error = `Could not set uniform ${name} . No such uniform.`;
return false;
}
if (v2 === undefined) {
throw new RangeError('Invalid number of values for array');
}
model.context.uniform2f(location, v1, v2);
return true;
};

publicAPI.setUniform2fv = (name, v) => {
const location = publicAPI.findUniform(name);
if (location === -1) {
model.error = `Could not set uniform ${name} . No such uniform.`;
return false;
}
model.context.uniform2fv(location, v);
return true;
};

publicAPI.setUniform2i = (name, v1, v2) => {
const location = publicAPI.findUniform(name);
if (location === -1) {
model.error = `Could not set uniform ${name} . No such uniform.`;
return false;
}
if (v2 === undefined) {
throw new RangeError('Invalid number of values for array');
}
model.context.uniform2i(location, v1, v2);
return true;
};

publicAPI.setUniform2iv = (name, v) => {
const location = publicAPI.findUniform(name);
if (location === -1) {
model.error = `Could not set uniform ${name} . No such uniform.`;
return false;
}
model.context.uniform2iv(location, v);
return true;
};

publicAPI.setUniform3f = (name, a1, a2, a3) => {
const location = publicAPI.findUniform(name);
if (location === -1) {
model.error = `Could not set uniform ${name} . No such uniform.`;
return false;
}
if (a3 === undefined) {
throw new RangeError('Invalid number of values for array');
}
model.context.uniform3f(location, a1, a2, a3);
return true;
};

publicAPI.setUniform3fArray = (name, a) => {
const location = publicAPI.findUniform(name);
if (location === -1) {
model.error = `Could not set uniform ${name} . No such uniform.`;
return false;
}
if (!Array.isArray(a) || a.length !== 3) {
throw new RangeError('Invalid number of values for array');
}
model.context.uniform3f(location, a[0], a[1], a[2]);
return true;
};

publicAPI.setUniform3fv = (name, v) => {
const location = publicAPI.findUniform(name);
if (location === -1) {
model.error = `Could not set uniform ${name} . No such uniform.`;
return false;
}
model.context.uniform3fv(location, v);
return true;
};

publicAPI.setUniform3i = (name, ...args) => {
const location = publicAPI.findUniform(name);
if (location === -1) {
model.error = `Could not set uniform ${name} . No such uniform.`;
return false;
}
let array = args;
// allow an array passed as a single argument
if (array.length === 1 && Array.isArray(array[0])) {
array = array[0];
}
if (array.length !== 3) {
throw new RangeError('Invalid number of values for array');
}
model.context.uniform3i(location, array[0], array[1], array[2]);
return true;
};

publicAPI.setUniform3iv = (name, v) => {
const location = publicAPI.findUniform(name);
if (location === -1) {
model.error = `Could not set uniform ${name} . No such uniform.`;
return false;
}
model.context.uniform3iv(location, v);
return true;
};

publicAPI.setUniform4f = (name, ...args) => {
const location = publicAPI.findUniform(name);
if (location === -1) {
model.error = `Could not set uniform ${name} . No such uniform.`;
return false;
}
let array = args;
// allow an array passed as a single argument
if (array.length === 1 && Array.isArray(array[0])) {
array = array[0];
}
if (array.length !== 4) {
throw new RangeError('Invalid number of values for array');
}
model.context.uniform4f(location, array[0], array[1], array[2], array[3]);
return true;
};

publicAPI.setUniform4fv = (name, v) => {
const location = publicAPI.findUniform(name);
if (location === -1) {
model.error = `Could not set uniform ${name} . No such uniform.`;
return false;
}
model.context.uniform4fv(location, v);
return true;
};

publicAPI.setUniform4i = (name, ...args) => {
const location = publicAPI.findUniform(name);
if (location === -1) {
model.error = `Could not set uniform ${name} . No such uniform.`;
return false;
}
let array = args;
// allow an array passed as a single argument
if (array.length === 1 && Array.isArray(array[0])) {
array = array[0];
}
if (array.length !== 4) {
throw new RangeError('Invalid number of values for array');
}
model.context.uniform4i(location, array[0], array[1], array[2], array[3]);
return true;
};

publicAPI.setUniform4iv = (name, v) => {
const location = publicAPI.findUniform(name);
if (location === -1) {
model.error = `Could not set uniform ${name} . No such uniform.`;
return false;
}
model.context.uniform4iv(location, v);
return true;
};

publicAPI.findUniform = (name) => {
if (!name || !model.linked) {
return -1;
}

// see if we have cached the result
let loc = model.uniformLocs[name];
if (loc !== undefined) {
return loc;
}

loc = model.context.getUniformLocation(model.handle, name);
if (loc === null) {
model.error = `Uniform ${name} not found in current shader program.`;
model.uniformLocs[name] = -1;
return -1;
}

model.uniformLocs[name] = loc;
return loc;
};

publicAPI.isUniformUsed = (name) => {
if (!name) {
return false;
}

// see if we have cached the result
let loc = model.uniformLocs[name];
if (loc !== undefined) {
return loc !== null;
}

if (!model.linked) {
vtkErrorMacro(
'attempt to find uniform when the shader program is not linked'
);
return false;
}

loc = model.context.getUniformLocation(model.handle, name);
model.uniformLocs[name] = loc;

if (loc === null) {
return false;
}

return true;
};

publicAPI.isAttributeUsed = (name) => {
if (!name) {
return false;
}

// see if we have cached the result
if (name in model.attributeLocs) {
return true;
}

if (!model.linked) {
vtkErrorMacro(
'attempt to find uniform when the shader program is not linked'
);
return false;
}

const loc = model.context.getAttribLocation(model.handle, name);
if (loc === -1) {
return false;
}
model.attributeLocs[name] = loc;

return true;
};

publicAPI.attachShader = (shader) => {
if (shader.getHandle() === 0) {
model.error = 'Shader object was not initialized, cannot attach it.';
return false;
}
if (shader.getShaderType() === 'Unknown') {
model.error = 'Shader object is of type Unknown and cannot be used.';
return false;
}

if (model.handle === 0) {
const thandle = model.context.createProgram();
if (thandle === 0) {
model.error = 'Could not create shader program.';
return false;
}
model.handle = thandle;
model.linked = false;
}

if (shader.getShaderType() === 'Vertex') {
if (model.vertexShaderHandle !== 0) {
model.context.detachShader(model.handle, model.vertexShaderHandle);
}
model.vertexShaderHandle = shader.getHandle();
}
if (shader.getShaderType() === 'Fragment') {
if (model.fragmentShaderHandle !== 0) {
model.context.detachShader(model.handle, model.fragmentShaderHandle);
}
model.fragmentShaderHandle = shader.getHandle();
}

model.context.attachShader(model.handle, shader.getHandle());
publicAPI.setLinked(false);
return true;
};

publicAPI.detachShader = (shader) => {
if (shader.getHandle() === 0) {
model.error = 'shader object was not initialized, cannot attach it.';
return false;
}
if (shader.getShaderType() === 'Unknown') {
model.error = 'Shader object is of type Unknown and cannot be used.';
return false;
}
if (model.handle === 0) {
model.error = 'This shader program has not been initialized yet.';
}

switch (shader.getShaderType()) {
case 'Vertex':
if (model.vertexShaderHandle !== shader.getHandle()) {
model.error = 'The supplied shader was not attached to this program.';
return false;
}
model.context.detachShader(model.handle, shader.getHandle());
model.vertexShaderHandle = 0;
model.linked = false;
return true;
case 'Fragment':
if (model.fragmentShaderHandle !== shader.getHandle()) {
model.error = 'The supplied shader was not attached to this program.';
return false;
}
model.context.detachShader(model.handle, shader.getHandle());
model.fragmentShaderHandle = 0;
model.linked = false;
return true;
default:
return false;
}
};

publicAPI.setContext = (ctx) => {
model.context = ctx;
model.vertexShader.setContext(ctx);
model.fragmentShader.setContext(ctx);
model.geometryShader.setContext(ctx);
};

publicAPI.setLastCameraMTime = (mtime) => {
model.lastCameraMTime = mtime;
};

// publicAPI.enableAttributeArray = (name) => {
// const location = publicAPI.findAttributeArray(name);
// if (location === -1) {
// model.error = `Could not enable attribute ${name} No such attribute.`;
// return false;
// }
// model.context.enableVertexAttribArray(location);
// return true;
// };

// publicAPI.disableAttributeArray = (name) => {
// const location = publicAPI.findAttributeArray(name);
// if (location === -1) {
// model.error = `Could not enable attribute ${name} No such attribute.`;
// return false;
// }
// model.context.disableVertexAttribArray(location);
// return true;
// };
}

// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------

const DEFAULT_VALUES = {
vertexShaderHandle: 0,
fragmentShaderHandle: 0,
geometryShaderHandle: 0,
vertexShader: null,
fragmentShader: null,
geometryShader: null,

linked: false,
bound: false,
compiled: false,
error: '',
handle: 0,
numberOfOutputs: 0,
attributesLocs: null,
uniformLocs: null,
md5Hash: 0,
context: null,
lastCameraMTime: null,
};

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

function extend(publicAPI, model, initialValues = {}) {
Object.assign(model, DEFAULT_VALUES, initialValues);

// Instantiate internal objects
model.attributesLocs = {};
model.uniformLocs = {};
model.vertexShader = vtkShader.newInstance();
model.vertexShader.setShaderType('Vertex');
model.fragmentShader = vtkShader.newInstance();
model.fragmentShader.setShaderType('Fragment');
model.geometryShader = vtkShader.newInstance();
model.geometryShader.setShaderType('Geometry');

// Build VTK API
macro.obj(publicAPI, model);
macro.get(publicAPI, model, ['lastCameraMTime']);
macro.setGet(publicAPI, model, [
'error',
'handle',
'compiled',
'bound',
'md5Hash',
'vertexShader',
'fragmentShader',
'geometryShader',
'linked',
]);

// Object methods
vtkShaderProgram(publicAPI, model);
}

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

const newInstance = macro.newInstance(extend, 'vtkShaderProgram');

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

export default { newInstance, extend, substitute };