interactive-shader-format
Version:
Rendering engine for Interactive Shader Format effects and generators
282 lines (243 loc) • 9.36 kB
JavaScript
import MetadataExtractor from './MetadataExtractor';
/*
Uniforms you will need to set, in addition to any inputs specified are
RENDERSIZE: vec2 rendering size in pixels
TIME: float time in seconds since rendering started
PASSINDEX: int index of the current pass being rendered
See http://vdmx.vidvox.net/blog/isf for more info
*/
const typeUniformMap = {
float: 'float',
image: 'sampler2D',
bool: 'bool',
event: 'bool',
long: 'int',
color: 'vec4',
point2D: 'vec2',
};
const ISFParser = function ISFParser() {};
ISFParser.prototype.parse = function parse(rawFragmentShader, rawVertexShader) {
try {
this.valid = true;
this.rawFragmentShader = rawFragmentShader;
this.rawVertexShader = rawVertexShader || ISFParser.vertexShaderDefault;
this.error = null;
const metadataInfo = MetadataExtractor(this.rawFragmentShader);
const metadata = metadataInfo.objectValue;
const metadataString = metadataInfo.stringValue;
this.metadata = metadata;
this.credit = metadata.CREDIT;
this.categories = metadata.CATEGORIES;
this.inputs = metadata.INPUTS;
this.imports = (metadata.IMPORTED || {});
this.description = metadata.DESCRIPTION;
const passesArray = metadata.PASSES || [{}];
this.passes = this.parsePasses(passesArray);
const endOfMetadata =
this.rawFragmentShader.indexOf(metadataString) + metadataString.length + 2;
this.rawFragmentMain = this.rawFragmentShader.substring(endOfMetadata);
this.generateShaders();
this.inferFilterType();
this.isfVersion = this.inferISFVersion();
} catch (e) {
this.valid = false;
this.error = e;
this.inputs = [];
this.categories = [];
this.credit = '';
this.errorLine = e.lineNumber;
}
};
ISFParser.prototype.parsePasses = function parsePasses(passesArray) {
const passes = [];
for (let i = 0; i < passesArray.length; ++i) {
const passDefinition = passesArray[i];
const pass = { };
if (passDefinition.TARGET) pass.target = passDefinition.TARGET;
pass.persistent = !!passDefinition.PERSISTENT;
pass.width = passDefinition.WIDTH || '$WIDTH';
pass.height = passDefinition.HEIGHT || '$HEIGHT';
pass.float = !!passDefinition.FLOAT;
passes.push(pass);
}
return passes;
};
ISFParser.prototype.generateShaders = function generateShaders() {
this.uniformDefs = '';
for (let i = 0; i < this.inputs.length; ++i) {
this.addUniform(this.inputs[i]);
}
for (let i = 0; i < this.passes.length; ++i) {
if (this.passes[i].target) {
this.addUniform({ NAME: this.passes[i].target, TYPE: 'image' });
}
}
for (const k in this.imports) {
if ({}.hasOwnProperty.call(this.imports, k)) {
this.addUniform({ NAME: k, TYPE: 'image' });
}
}
this.fragmentShader = this.buildFragmentShader();
this.vertexShader = this.buildVertexShader();
};
ISFParser.prototype.addUniform = function addUniform(input) {
const type = this.inputToType(input.TYPE);
this.addUniformLine(`uniform ${type} ${input.NAME};`);
if (type === 'sampler2D') {
this.addUniformLine(this.samplerUniforms(input));
}
};
ISFParser.prototype.addUniformLine = function addUniformLine(line) {
this.uniformDefs += `${line}\n`;
};
ISFParser.prototype.samplerUniforms = function samplerUniforms(input) {
const name = input.NAME;
let lines = '';
lines += `uniform vec4 _${name}_imgRect;\n`;
lines += `uniform vec2 _${name}_imgSize;\n`;
lines += `uniform bool _${name}_flip;\n`;
lines += `varying vec2 _${name}_normTexCoord;\n`;
lines += `varying vec2 _${name}_texCoord;\n`;
lines += '\n';
return lines;
};
ISFParser.prototype.buildFragmentShader = function buildFragmentShader() {
const main = this.replaceSpecialFunctions(this.rawFragmentMain);
return ISFParser.fragmentShaderSkeleton.replace('[[uniforms]]', this.uniformDefs).replace('[[main]]', main);
};
ISFParser.prototype.replaceSpecialFunctions = function replaceSpecialFunctions(source) {
let regex;
// IMG_THIS_PIXEL
regex = /IMG_THIS_PIXEL\((.+?)\)/g;
source = source.replace(regex, (fullMatch, innerMatch) => `texture2D(${innerMatch}, isf_FragNormCoord)`);
// IMG_THIS_NORM_PIXEL
regex = /IMG_THIS_NORM_PIXEL\((.+?)\)/g;
source = source.replace(regex, (fullMatch, innerMatch) => `texture2D(${innerMatch}, isf_FragNormCoord)`);
// IMG_PIXEL
regex = /IMG_PIXEL\((.+?)\)/g;
source = source.replace(regex, (fullMatch, innerMatch) => {
const results = innerMatch.split(',');
const sampler = results[0];
const coord = results[1];
return `texture2D(${sampler}, (${coord}) / RENDERSIZE)`;
});
// IMG_NORM_PIXEL
regex = /IMG_NORM_PIXEL\((.+?)\)/g;
source = source.replace(regex, (fullMatch, innerMatch) => {
const results = innerMatch.split(',');
const sampler = results[0];
const coord = results[1];
return `VVSAMPLER_2DBYNORM(${sampler}, _${sampler}_imgRect, _${sampler}_imgSize, _${sampler}_flip, ${coord})`;
});
// IMG_SIZE
regex = /IMG_SIZE\((.+?)\)/g;
source = source.replace(regex, (fullMatch, imgName) => {
return `_${imgName}_imgSize`;
});
return source;
};
ISFParser.prototype.buildVertexShader = function buildVertexShader() {
let functionLines = '\n';
for (let i = 0; i < this.inputs.length; ++i) {
const input = this.inputs[i];
if (input.TYPE === 'image') {
functionLines += `${this.texCoordFunctions(input)}\n`;
}
}
return ISFParser.vertexShaderSkeleton.replace('[[functions]]', functionLines).replace('[[uniforms]]', this.uniformDefs).replace('[[main]]', this.rawVertexShader);
};
ISFParser.prototype.texCoordFunctions = function texCoordFunctions(input) {
const name = input.NAME;
return [
'_[[name]]_texCoord =',
' vec2(((isf_fragCoord.x / _[[name]]_imgSize.x * _[[name]]_imgRect.z) + _[[name]]_imgRect.x), ',
' (isf_fragCoord.y / _[[name]]_imgSize.y * _[[name]]_imgRect.w) + _[[name]]_imgRect.y);',
'',
'_[[name]]_normTexCoord =',
' vec2((((isf_FragNormCoord.x * _[[name]]_imgSize.x) / _[[name]]_imgSize.x * _[[name]]_imgRect.z) + _[[name]]_imgRect.x),',
' ((isf_FragNormCoord.y * _[[name]]_imgSize.y) / _[[name]]_imgSize.y * _[[name]]_imgRect.w) + _[[name]]_imgRect.y);',
].join('\n').replace(/\[\[name\]\]/g, name);
};
ISFParser.prototype.inferFilterType = function inferFilterType() {
function any(arr, test) {
return arr.filter(test).length > 0;
}
const isFilter = any(this.inputs, input => input.TYPE === 'image' && input.NAME === 'inputImage');
const isTransition =
any(this.inputs, input => input.TYPE === 'image' && input.NAME === 'startImage')
&&
any(this.inputs, input => input.TYPE === 'image' && input.NAME === 'endImage')
&&
any(this.inputs, input => input.TYPE === 'float' && input.NAME === 'progress');
if (isFilter) {
this.type = 'filter';
} else if (isTransition) {
this.type = 'transition';
} else {
this.type = 'generator';
}
};
ISFParser.prototype.inferISFVersion = function inferISFVersion() {
let v = 2;
if (this.metadata.PERSISTENT_BUFFERS ||
this.rawFragmentShader.indexOf('vv_FragNormCoord') !== -1 ||
this.rawVertexShader.indexOf('vv_vertShaderInit') !== -1 ||
this.rawVertexShader.indexOf('vv_FragNormCoord') !== -1) {
v = 1;
}
return v;
};
ISFParser.prototype.inputToType = function inputToType(inputType) {
const type = typeUniformMap[inputType];
if (!type) throw new Error(`Unknown input type [${inputType}]`);
return type;
};
ISFParser.fragmentShaderSkeleton = `
precision highp float;
precision highp int;
uniform int PASSINDEX;
uniform vec2 RENDERSIZE;
varying vec2 isf_FragNormCoord;
varying vec2 isf_FragCoord;
uniform float TIME;
uniform float TIMEDELTA;
uniform int FRAMEINDEX;
uniform vec4 DATE;
[[uniforms]]
// We don't need 2DRect functions since we control all inputs. Don't need flip either, but leaving
// for consistency sake.
vec4 VVSAMPLER_2DBYPIXEL(sampler2D sampler, vec4 samplerImgRect, vec2 samplerImgSize, bool samplerFlip, vec2 loc) {
return (samplerFlip)
? texture2D (sampler,vec2(((loc.x/samplerImgSize.x*samplerImgRect.z)+samplerImgRect.x), (samplerImgRect.w-(loc.y/samplerImgSize.y*samplerImgRect.w)+samplerImgRect.y)))
: texture2D (sampler,vec2(((loc.x/samplerImgSize.x*samplerImgRect.z)+samplerImgRect.x), ((loc.y/samplerImgSize.y*samplerImgRect.w)+samplerImgRect.y)));
}
vec4 VVSAMPLER_2DBYNORM(sampler2D sampler, vec4 samplerImgRect, vec2 samplerImgSize, bool samplerFlip, vec2 normLoc) {
vec4 returnMe = VVSAMPLER_2DBYPIXEL( sampler,samplerImgRect,samplerImgSize,samplerFlip,vec2(normLoc.x*samplerImgSize.x, normLoc.y*samplerImgSize.y));
return returnMe;
}
[[main]]
`;
ISFParser.vertexShaderDefault = `
void main() {
isf_vertShaderInit();
}
`;
ISFParser.vertexShaderSkeleton = `
precision highp float;
precision highp int;
void isf_vertShaderInit();
attribute vec2 isf_position; // -1..1
uniform int PASSINDEX;
uniform vec2 RENDERSIZE;
varying vec2 isf_FragNormCoord; // 0..1
vec2 isf_fragCoord; // Pixel Space
[[uniforms]]
[[main]]
void isf_vertShaderInit(void) {
gl_Position = vec4( isf_position, 0.0, 1.0 );
isf_FragNormCoord = vec2((gl_Position.x+1.0)/2.0, (gl_Position.y+1.0)/2.0);
isf_fragCoord = floor(isf_FragNormCoord * RENDERSIZE);
[[functions]]
}
`;
export default ISFParser;