UNPKG

@smoud/tiny

Version:

Fast and tiny JavaScript library for HTML5 game and playable ads creation.

245 lines (199 loc) 8.8 kB
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 };