@smoud/tiny
Version:
Fast and tiny JavaScript library for HTML5 game and playable ads creation.
245 lines (199 loc) • 8.8 kB
JavaScript
var ID = 1;
function Material({
vertex,
fragment,
uniforms = {},
transparent = false,
cullFace = WebGLRenderingContext.BACK,
frontFace = WebGLRenderingContext.CCW,
depthTest = true,
depthWrite = true,
depthFunc = WebGLRenderingContext.LESS
} = {}) {
this.id = ID++;
this.uniforms = uniforms;
if (!vertex) console.warn('vertex shader not supplied');
if (!fragment) console.warn('fragment shader not supplied');
this.vertex = vertex;
this.fragment = fragment;
// Store program state
this.transparent = transparent;
this.cullFace = cullFace;
this.frontFace = frontFace;
this.depthTest = depthTest;
this.depthWrite = depthWrite;
this.depthFunc = depthFunc;
this.blendFunc = {};
this.blendEquation = {};
}
Material.prototype = {
constructor: Material,
initialize: function (gl) {
var { vertex, fragment, uniforms } = this;
this.gl = gl;
// set default blendFunc if transparent flagged
if (this.transparent && !this.blendFunc.src) {
if (this.gl.renderer.premultipliedAlpha)
this.setBlendFunc(this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA);
else this.setBlendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA);
}
// compile vertex shader and log errors
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertex);
gl.compileShader(vertexShader);
if (gl.getShaderInfoLog(vertexShader) !== '') {
console.warn(`${gl.getShaderInfoLog(vertexShader)}\nVertex Shader\n${addLineNumbers(vertex)}`);
}
// compile fragment shader and log errors
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragment);
gl.compileShader(fragmentShader);
if (gl.getShaderInfoLog(fragmentShader) !== '') {
console.warn(
`${gl.getShaderInfoLog(fragmentShader)}\nFragment Shader\n${addLineNumbers(fragment)}`
);
}
// compile program and log errors
this.program = gl.createProgram();
gl.attachShader(this.program, vertexShader);
gl.attachShader(this.program, fragmentShader);
gl.linkProgram(this.program);
if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) {
return console.warn(gl.getProgramInfoLog(this.program));
}
// Remove shader once linked
gl.deleteShader(vertexShader);
gl.deleteShader(fragmentShader);
// Get active uniform locations
this.uniformLocations = new Map();
var numUniforms = gl.getProgramParameter(this.program, gl.ACTIVE_UNIFORMS);
for (var uIndex = 0; uIndex < numUniforms; uIndex++) {
var uniform = gl.getActiveUniform(this.program, uIndex);
var location = gl.getUniformLocation(this.program, uniform.name);
this.uniformLocations.set(uniform, location);
// split uniforms' names to separate array and struct declarations
var split = uniform.name.match(/(\w+)/g);
uniform.uniformName = split[0];
uniform.nameComponents = split.slice(1);
if (uniforms[uniform.name]) {
uniforms[uniform.name].location = location;
}
}
// Get active attribute locations
this.attributeLocations = new Map();
var locations = [];
var numAttribs = gl.getProgramParameter(this.program, gl.ACTIVE_ATTRIBUTES);
for (var aIndex = 0; aIndex < numAttribs; aIndex++) {
var attribute = gl.getActiveAttrib(this.program, aIndex);
var location = gl.getAttribLocation(this.program, attribute.name);
// Ignore special built-in inputs. eg gl_VertexID, gl_InstanceID
if (location === -1) continue;
locations[location] = attribute.name;
this.attributeLocations.set(attribute, location);
}
this.attributeOrder = locations.join('');
},
setBlendFunc: function (src, dst, srcAlpha, dstAlpha) {
this.blendFunc.src = src;
this.blendFunc.dst = dst;
this.blendFunc.srcAlpha = srcAlpha;
this.blendFunc.dstAlpha = dstAlpha;
if (src) this.transparent = true;
},
setBlendEquation: function (modeRGB, modeAlpha) {
this.blendEquation.modeRGB = modeRGB;
this.blendEquation.modeAlpha = modeAlpha;
},
applyState: function () {
if (this.depthTest) this.gl.renderer.enable(this.gl.DEPTH_TEST);
else this.gl.renderer.disable(this.gl.DEPTH_TEST);
if (this.cullFace) this.gl.renderer.enable(this.gl.CULL_FACE);
else this.gl.renderer.disable(this.gl.CULL_FACE);
if (this.blendFunc.src) this.gl.renderer.enable(this.gl.BLEND);
else this.gl.renderer.disable(this.gl.BLEND);
if (this.cullFace) this.gl.renderer.setCullFace(this.cullFace);
this.gl.renderer.setFrontFace(this.frontFace);
this.gl.renderer.setDepthMask(this.depthWrite);
this.gl.renderer.setDepthFunc(this.depthFunc);
if (this.blendFunc.src)
this.gl.renderer.setBlendFunc(
this.blendFunc.src,
this.blendFunc.dst,
this.blendFunc.srcAlpha,
this.blendFunc.dstAlpha
);
this.gl.renderer.setBlendEquation(this.blendEquation.modeRGB, this.blendEquation.modeAlpha);
},
use: function ({ flipFaces = false } = {}) {
var textureUnit = -1;
var programActive = this.gl.renderer.state.currentProgram === this.id;
// Avoid gl call if program already in use
if (!programActive) {
this.gl.useProgram(this.program);
this.gl.renderer.state.currentProgram = this.id;
}
// Set only the active uniforms found in the shader
this.uniformLocations.forEach((location, activeUniform) => {
var uniform = this.uniforms[activeUniform.uniformName];
for (var component of activeUniform.nameComponents) {
if (!uniform) break;
if (component in uniform) {
uniform = uniform[component];
} else if (Array.isArray(uniform.value)) {
break;
} else {
uniform = undefined;
break;
}
}
if (!uniform) {
return warn(`Active uniform ${activeUniform.name} has not been supplied`);
}
if (uniform && uniform.value === undefined) {
return warn(`${activeUniform.name} uniform is missing a value parameter`);
}
if (uniform.isTextureUniform && this.gl.renderer.state.currentTexture !== uniform.value) {
this.gl.renderer.state.currentTexture = uniform.value;
textureUnit = textureUnit + 1;
uniform.value.update(textureUnit);
uniform.apply(this.gl, textureUnit);
}
// For texture arrays, set uniform as an array of texture units instead of just one
//@TODO uncomment later when uniforms correctly implemented
// if (uniform.Array.isArray(value && uniform.value[0].texture) {
// var textureUnits = [];
// uniform.value.forEach((value) => {
// textureUnit = textureUnit + 1;
// value.update(textureUnit);
// textureUnits.push(textureUnit);
// });
//
// return setUniform(this.gl, activeUniform.type, location, textureUnits);
// }
if (uniform.dirty) {
uniform.apply(this.gl);
}
});
this.applyState();
if (flipFaces)
this.gl.renderer.setFrontFace(this.frontFace === this.gl.CCW ? this.gl.CW : this.gl.CCW);
},
remove: function () {
this.gl.deleteProgram(this.program);
}
};
function addLineNumbers(string) {
var lines = string.split('\n');
for (var i = 0; i < lines.length; i++) {
lines[i] = i + 1 + ': ' + lines[i];
}
return lines.join('\n');
}
var warnCount = 0;
function warn(message) {
if (warnCount > 100) return;
console.warn(message);
warnCount++;
if (warnCount > 100) console.warn('More than 100 program warnings - stopping logs.');
}
export { Material };