/* <copyright> This file contains proprietary software owned by Motorola Mobility, Inc.<br/> No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.<br/> (c) Copyright 2011 Motorola Mobility, Inc. All Rights Reserved. </copyright> */ /* this API should be familiar to anyone who has worked with HLSL effect files. */ /* * A map of types to uniform 'binding' functions */ bindMap={}; bindMap['int'] = function(ctx, a,b) { ctx.uniform1iv(a,b); }; bindMap['float'] = function(ctx, a,b) { ctx.uniform1fv(a,b); }; bindMap['vec2'] = function(ctx, a,b) { ctx.uniform2fv(a,b); }; bindMap['vec3'] = function(ctx, a,b) { ctx.uniform3fv(a,b); }; bindMap['vec4'] = function(ctx, a,b) { ctx.uniform4fv(a,b); }; bindMap['mat3'] = function(ctx, a,b) { ctx.uniformMatrix3fv(a,false,b); }; bindMap['mat4'] = function(ctx, a,b) { ctx.uniformMatrix4fv(a,false,b); g_Engine.getContext().debug.mat4CallCount++; }; bindMap['tex2d'] = function(ctx, a,b) { ctx.activeTexture(ctx.TEXTURE0+b[0]); ctx.bindTexture(ctx.TEXTURE_2D, b[1]); ctx.uniform1iv(a,[b[0]]); }; bindMap['texCube']=function(ctx, a,b) { ctx.activeTexture(ctx.TEXTURE0+b[0]); ctx.bindTexture(ctx.TEXTURE_CUBE_MAP, b[1]); ctx.uniform1iv(a,[b[0]]); }; lightDataMap = [ function(ctx, loc, lightNode) { ctx.uniform3fv(loc, lightNode.position); }, function(ctx, loc, lightNode) { ctx.uniform4fv(loc, lightNode.lightDiffuse); }, function(ctx, loc, lightNode) { ctx.uniform4fv(loc, lightNode.lightAmbient); }, function(ctx, loc, lightNode) { ctx.uniform4fv(loc, lightNode.lightSpecular); } ]; paramTypeNameMapping = null; jshader = function(addr) { this.name = addr; this.def = null; this.technique = {}; this.params = {}; this.compiledShaders = {}; this.resetRS = false; this.currentPass = 0; this.type_jshader = {}; this.global = {}; this.renderer = g_Engine.getContext().renderer; this.ctx = this.renderer.ctx; // load jshader definition at addr (if provided) if (addr != undefined && addr != null) { // a synchronous ajax request request = new XMLHttpRequest(); request.open("GET", addr, false); request.send(null); this.def = JSON.parse(request.responseText); } if (!paramTypeNameMapping) { var gl = this.ctx; paramTypeNameMapping = {}; paramTypeNameMapping[gl.BOOL] = "bool"; paramTypeNameMapping[gl.INT] = "int"; paramTypeNameMapping[gl.FLOAT] = "float"; paramTypeNameMapping[gl.FLOAT_VEC2] = "vec2"; paramTypeNameMapping[gl.FLOAT_VEC3] = "vec3"; paramTypeNameMapping[gl.FLOAT_VEC4] = "vec4"; paramTypeNameMapping[gl.INT_VEC2] = "vec2"; paramTypeNameMapping[gl.INT_VEC3] = "vec3"; paramTypeNameMapping[gl.INT_VEC4] = "vec4"; paramTypeNameMapping[gl.BOOL_VEC2] = "vec2"; paramTypeNameMapping[gl.BOOL_VEC3] = "vec3"; paramTypeNameMapping[gl.BOOL_VEC4] = "vec4"; paramTypeNameMapping[gl.FLOAT_MAT2] = "mat2"; paramTypeNameMapping[gl.FLOAT_MAT3] = "mat3"; paramTypeNameMapping[gl.FLOAT_MAT4] = "mat4"; paramTypeNameMapping[gl.SAMPLER_2D] = "tex2d"; paramTypeNameMapping[gl.SAMPLER_CUBE] = "texCube"; } /* * private helper functions */ this.bindParameters = function(pass) { var params = pass.defParamsList; // global parameters to start with var lightParams = pass.lightParams; var lightContext = pass.lightContext; var length = params.length; var idx = 0; var texArg = new Array(2) // global parameters var texUnit = 0; for (idx = 0; idx < length; ++idx) { if (params[idx].type == 'tex2d' || params[idx].type == 'texCube') { texArg[0] = texUnit++; texArg[1] = params[idx].data[0]; bindMap[params[idx].type](this.ctx, params[idx].loc, texArg); } else { bindMap[params[idx].type](this.ctx, params[idx].loc, rdgeGlobalParameters[params[idx].name].data); } } // light settings defined by the material var len = rdgeConstants.MAX_MATERIAL_LIGHTS; for (var i = 0; i < len; ++i) { // if there is a context for a light check to see if we have a binding to the light if (lightContext[i] != null) { // see if we have parameters to bind to this light if (lightParams[i]) { // something is here lets bind it var numParams = lightParams[i].length; for (var lp = 0; lp < numParams; ++lp) { // bind the parameters using the lightDataMap function lookup, dataIndex is the key lightDataMap[lightParams[i][lp].dataIndex](this.ctx, lightParams[i][lp].loc, lightContext[i]); } } } } // let locally defined uniforms stomp globally defined uniforms texUnit = this.renderer.usedTextureUnits; // start adding texture after the default textures params = pass.paramsList; length = params.length; for (idx = 0; idx < length; ++idx) { if (params[idx].type == 'tex2d' || params[idx].type == 'texCube') { texArg[0] = texUnit++; texArg[1] = params[idx].data[0]; bindMap[params[idx].type](this.ctx, params[idx].loc, texArg); } else { bindMap[params[idx].type](this.ctx, params[idx].loc, params[idx].data); } } }; /* * helper function for setting up a texture */ createJShaderTexture = function(ctx, param) { var texHandle = null; if (typeof param.data == "string") { texHandle = ctx.canvas.renderer.getTextureByName(param.data, param.wrap, param.repeat, param.mips); } else { texHandle = ctx.canvas.renderer.getTextureByName(param.data.lookUpName, param.wrap, param.repeat, param.mips); } return [texHandle]; } paramType = function(ctx, name, def, program, technique) { var texUnit = 0; // Get the uniform location and store it this.loc = ctx.getUniformLocation(program, name); // if the parameter does not exist in the shader cull it from the pass if (this.loc == null) { window.console.log("ctx:" + ctx.canvas.rdgeid + ", technique: " + technique + ", uniform: " + name + " was not found, jshader param will have no affect"); //return; } var param = def[name]; this.type = param.type; // if data was not provided then create default data if (param.data == undefined) { switch (param.type) { case "vec4": this.data = vec4.zero(); break; case "vec3": this.data = vec3.zero(); break; case "vec2": this.data = vec2.zero(); break; case "mat4": this.data = mat4.zero(); break; case "mat3": this.data = new Array(9); break; case "mat2": this.data = [0, 0, 0, 0]; break; case "float": this.data = [0]; break; case "int": this.data = [0]; break; case "tex2d": this.data = [ctx.canvas.renderer.getTextureByName(g_Engine._assetPath+"images/white.png")]; break; case "texCube": this.data = [ctx.canvas.renderer.getTextureByName(g_Engine._assetPath+"images/white.png")]; break; } } else { if (param.type == 'tex2d' || param.type == 'texCube') { this.data = createJShaderTexture(ctx, param); } else { this.data = param.data.slice(); } } this.get = function() { return this.data.slice(); } this.set = function(v) { if (this.type == 'tex2d' || this.type == 'texCube') { if (typeof v == "string") { v = ctx.canvas.renderer.getTextureByName(v); } this.data[0] = v; } else { var len = this.data.length; for (var i = 0; i < len; ++i) this.data[i] = v[i]; } } } globalParam = function(ctx, name, param, program) { this.type = param.type; this.data = param.data; // Get the uniform location and store it this.loc = ctx.getUniformLocation(program, name); // if data was not provided then create default data if (!this.data) { switch (param.type) { case "vec4": this.data = vec4.zero(); break; case "vec3": this.data = vec3.zero(); break; case "vec2": this.data = vec2.zero(); break; case "mat4": this.data = mat4.zero(); break; case "mat3": this.data = new Array(9); break; case "mat2": this.data = [0, 0, 0, 0]; break; case "float": this.data = [0]; break; case "int": this.data = [0]; break; case "tex2d": this.data = [ctx.canvas.renderer.getTextureByName(g_Engine._assetPath+"images/white.png")]; break; case "texCube": this.data = [ctx.canvas.renderer.getTextureByName(g_Engine._assetPath+"images/white.png")]; break; } } else { if (param.type == 'tex2d' || param.type == 'texCube') { this.data = createJShaderTexture(ctx, param); } else { this.data = param.data.slice(); } } this.get = function() { return this.data.slice(); } this.set = function(v) { if (this.type == 'tex2d' || this.type == 'texCube') { if (typeof v == "string") { v = ctx.canvas.renderer.getTextureByName(v); } this.data[0] = v; } else { var len = this.data.length; for (var i = 0; i < len; ++i) this.data[i] = v[i]; } } } this.init = function() { var techniques = this.def.techniques; var defaultTech = null; for (t in techniques) { defaultTech = t; var curTechnique = techniques[t]; this[t] = { 'passes': [] }; var numPasses = curTechnique.length; var i = 0; while (i < numPasses) { var program = this.buildProgram(curTechnique[i]); this.ctx.useProgram(program); // automatically create a parameter def for every active attribute in the shader. var numAttribs = this.ctx.getProgramParameter(program, this.ctx.ACTIVE_ATTRIBUTES); for (j = 0; j < numAttribs; ++j) { var attribInfo = this.ctx.getActiveAttrib(program, j); curTechnique[i].attributes[attribInfo.name] = { 'type': paramTypeNameMapping[attribInfo.type] }; } // automatically create a parameter def for every active uniform in the shader. var numUniforms = this.ctx.getProgramParameter(program, this.ctx.ACTIVE_UNIFORMS); for (j = 0; j < numUniforms; ++j) { var uniformInfo = this.ctx.getActiveUniform(program, j); if (!rdgeGlobalParameters[uniformInfo.name]) { curTechnique[i].params[uniformInfo.name] = { 'type': paramTypeNameMapping[uniformInfo.type] }; } } program.ctxId = this.ctx.canvas.rdgeid; if (!program) { this.renderer.console.log("Build errors found in technique: " + t); this.def[t] = null; // remove bad technique break; } else { this[t].passes.push({ "program": program, "params": {}, "defParams": {}, "states": curTechnique[i].states, "attributes": curTechnique[i].attribPairs }); } // init default parameters for (var p in rdgeGlobalParameters) { var gp = new globalParam(this.ctx, p, rdgeGlobalParameters[p], program); if (gp.loc != null) { gp.loc.ctxID = this.ctx.canvas.rdgeid; this[t].passes[i].defParams[p] = gp; this.global[p] = gp; } } // attach light parameters and container to light context this[t].passes[i].lightParams = [null, null, null, null]; this[t].passes[i].lightContext = [null, null, null, null]; // attach a parameter list that will be used to optimize binding attributes if (!this[t].passes[i].paramsList) this[t].passes[i].paramsList = []; // locate individual light parameters to bind with local context var totalLights = rdgeConstants.MAX_MATERIAL_LIGHTS; for (var lightIdx = 0; lightIdx < totalLights; ++lightIdx) { // clear parameter this[t].passes[i].lightParams[lightIdx] = null; // 0 = pos, 1 = diff, 2 = amb, 3 = spec // this is order assumed for light parameters // the parameter index key - lets us know which piece of data we are getting/setting var lightDataIndex = 0; for (var lp in g_Engine.lightManager.lightUniforms[lightIdx]) { loc = this.ctx.getUniformLocation(program, lp); // if item found enable this light param and set parameters to bind and lookup data if (loc != null) { if (!this[t].passes[i].lightParams[lightIdx]) this[t].passes[i].lightParams[lightIdx] = []; this[t].passes[i].lightParams[lightIdx].push({ 'loc': loc, 'name': lp, 'dataIndex': lightDataIndex }); } lightDataIndex++; } } // init user defined parameters for (var p in curTechnique[i].params) { if (typeof curTechnique[i].params[p] == 'string') { continue; } var newParam = new paramType(this.ctx, p, curTechnique[i].params, program, t); // if(newParam.loc != null) // { this[t].passes[i].params[p] = newParam; this[t][p] = newParam; // } } // link up aliases for (var p in curTechnique[i].params) { if (typeof curTechnique[i].params[p] == 'string') { // this just redirects to an already existing parameter. this[t][p] = this[t].passes[i].params[p]; } } i++; } } // create linear lists of parameters - optimization for (t in techniques) { var numPasses = this[t].passes.length; for (var i = 0; i < numPasses; ++i) { this[t].passes[i].defParamsList = []; for (var p in this[t].passes[i].params) { var param = this[t].passes[i].params[p]; param.name = p; this[t].passes[i].paramsList.push(param); } for (var p in this[t].passes[i].defParams) { var param = this[t].passes[i].defParams[p]; param.name = p; this[t].passes[i].defParamsList.push(param); } } } this.setTechnique(defaultTech); } /* * Init a local parameter at any time during the life of the jshader. * This will add the parameter to the list of parameters to be bound * before rendering */ this.initLocalParameter = function(name, param) { var techniques = this.def.techniques; for (t in techniques) { var curTechnique = techniques[t]; var numPasses = curTechnique.length; var i = 0; while (i < numPasses) { var newParam = new paramType(this.ctx, name, param, curTechnique[i].program, t); if (newParam) { curTechnique[i][name] = newParam; // this params list is created here because a parameter could be added before the jshader is initialized if (!curTechnique[i].paramsList) curTechnique[i].paramsList = []; curTechnique[i].paramsList.push(newParam); } i++; } } } this.buildShader = function(shaderType, shaderStr) { // pre-pend preprocessor settings var preProcessor = "#define PC\n" preProcessor += shaderStr; shaderStr = preProcessor; // Create the shader object var shader = this.ctx.createShader(shaderType); if (shader == null) { this.renderer.console.log("*** Error: unable to create shader '" + shaderType + "'"); return null; } // Load the shader source this.ctx.shaderSource(shader, shaderStr); // Compile the shader this.ctx.compileShader(shader); // Check the compile status var compiled = this.ctx.getShaderParameter(shader, this.ctx.COMPILE_STATUS); if (!compiled) { // compile failed, report error. var error = this.ctx.getShaderInfoLog(shader); window.console.error("*** Error compiling shader '" + shaderType + "':" + error); this.ctx.deleteShader(shader); return null; } return shader; } this.buildProgram = function(t) { window.console.log("building shader pair: <" + t.vshader + ", " + t.fshader + ">"); var vShaderDef = this.def.shaders[t.vshader]; var fShaderDef = this.def.shaders[t.fshader]; this.ctx.useProgram(null); var vertexShader = null; // if (this.compiledShaders[t.vshader] != undefined) { // vertexShader = this.compiledShaders[t.vshader]; // } else { var source = null; if (vShaderDef.indexOf('{') != -1) { source = vShaderDef; } else { var vshaderRequest = new XMLHttpRequest(); var urlVertShader = vShaderDef; vshaderRequest.open("GET", urlVertShader, false); vshaderRequest.send(null); source = vshaderRequest.responseText; } vertexShader = this.buildShader(this.ctx.VERTEX_SHADER, source); } var fragmentShader = null; // if (this.compiledShaders[t.fshader] != undefined) // { // fragmentShader = this.compiledShaders[t.vshader]; // } else { var source = null; if (vShaderDef.indexOf('{') != -1) { source = fShaderDef; } else { var vshaderRequest = new XMLHttpRequest(); var urlFragShader = fShaderDef; vshaderRequest.open("GET", urlFragShader, false); vshaderRequest.send(null); source = vshaderRequest.responseText; } fragmentShader = this.buildShader(this.ctx.FRAGMENT_SHADER, source); } if (!vertexShader || !fragmentShader) { return null; } this.compiledShaders[t.vshader] = vertexShader; this.compiledShaders[t.fshader] = fragmentShader; // Create the program object var program = this.ctx.createProgram(); if (!program) { return null; } // Attach our two shaders to the program this.ctx.attachShader(program, vertexShader); this.ctx.attachShader(program, fragmentShader); // Bind attributes var idx = 0; t.attribPairs = []; for (var i in t.attributes) { t.attribPairs.push({ 'loc': idx, 'name': i }); this.ctx.bindAttribLocation(program, idx++, i); } // Link the program this.ctx.linkProgram(program); // Check the link status var linked = this.ctx.getProgramParameter(program, this.ctx.LINK_STATUS); if (!linked) { // failed to link var error = this.ctx.getProgramInfoLog(program); window.console.log("Error in program linking:" + error); this.ctx.deleteProgram(program); this.ctx.deleteProgram(fragmentShader); this.ctx.deleteProgram(vertexShader); return null; } return program; } /* * Set the light nodes used by this jshader * array item 0 corresponds to light 0, item 1 tp light 1 and so on * place null for lights that are not there */ this.setLightContext = function(lightRefArray) { for (t in this.technique) { var len = this.technique.passes.length; for (var i = 0; i < len; ++i) { this.technique.passes[i].lightContext = lightRefArray.slice(); } } } /* * Called by the system to add material textures settings to the jshader */ this.setTextureContext = function(textureList) { var passCount = this.technique.passes.length; var param = null; for (var t = 0, texCount = textureList.length; t < texCount; ++t) { for (var i = 0; i < passCount; ++i) { var param = textureList[t]; // set the rdge global parameters if the texture is in the list if (this.technique.passes[i].defParams[param.name]) this.technique.passes[i].defParams[param.name].set(param.handle); // and set the local parameters if the texture is in the list if (this.technique.passes[i].params[param.name]) this.technique.passes[i].params[param.name].set(param.data[0]); } } } this.setTechnique = function(name) { if (this[name] != undefined) { this.technique = this[name]; return true; } this.ctx.console.log("Failed to set technique:" + name); return false; } this.beginRenderState = function(i) { var states = this.technique.passes[i].states; if (states == undefined) { return; } // depth enabled by default. var depthEnable = states.depthEnable != undefined ? states.depthEnable : true; if (!depthEnable) { this.ctx.disable(this.ctx.DEPTH_TEST); var depthFunc = states.depthFunc != undefined ? states.depthFunc : "LESS"; this.ctx.depthFunc(this.ctx[states.depthFunc]); this.ctx.depthMask(true); } else { if (states.depthFunc) { this.ctx.depthFunc(this.ctx[states.depthFunc]); } if (states.offset) { this.ctx.enable(this.ctx.POLYGON_OFFSET_FILL); this.ctx.polygonOffset(states.offset[0], states.offset[1]); } // depth write if (states.depthWrite) { this.ctx.depthMask(states.depthWrite); } if (states.depthRangeMin) { this.ctx.depthRange(states.depthRangeMin); } if (states.depthRangeMax) { this.ctx.depthRange(states.depthRangeMax); } } // blend enabled by default. var blendEnabled = states.blendEnable != undefined ? states.blendEnable : false; if (blendEnabled) { var srcBlend = states.srcBlend != undefined ? states.srcBlend : "ONE"; // default src blend var dstBlend = states.dstBlend != undefined ? states.dstBlend : "ZERO"; // default dst blend this.ctx.enable(this.ctx.BLEND); this.ctx.blendFunc(this.ctx[srcBlend], this.ctx[dstBlend]); } if (states.culling) { if (states.culling) this.ctx.enable(this.ctx.CULL_FACE); else this.ctx.disable(this.ctx.CULL_FACE); } if (states.cullFace) { this.ctx.cullFace(this.ctx[states.cullFace]); } if (states.pointsprite) { if (states.pointsprite === true) this.renderer.enablePointSprites(); else this.renderer.disablePointSprites(); } this.resetRS = this.technique.passes[i].states.reset == undefined || this.technique.passes[i].states.reset == true; } this.endRenderState = function() { // restore render states to some default state. var ctx = this.ctx; if (this.resetRS) { ctx.enable(this.ctx.DEPTH_TEST); ctx.disable(this.ctx.BLEND); ctx.depthFunc(this.ctx.LESS); ctx.disable(this.ctx.POLYGON_OFFSET_FILL); ctx.disable(this.ctx.CULL_FACE); // this.renderer.disablePointSprites(); //ctx.enable(ctx.CULL_FACE); //ctx.cullFace(ctx.BACK); } } this.begin = function() { this.currentPass = null; if (this.def == null || this.technique == null) { return 0; } return this.technique.passes.length; } this.beginPass = function(i) { this.currentPass = this.technique.passes[i]; this.ctx.useProgram(this.currentPass.program); this.bindParameters(this.currentPass); this.beginRenderState(i); return this.currentPass; } this.endPass = function() { this.endRenderState(); this.ctx.useProgram(null); } this.end = function() { } this.exportShader = function() { for (t in this.def.techniques) { var numPasses = this[t].passes.length; for (var i = 0; i < numPasses; ++i) { this[t].passes[i].paramsList = []; this[t].passes[i].defParamsList = []; for (var p in this[t].passes[i].params) { var tech = this.def.techniques[t][i]; if (tech && this[t].passes[i].params[p].type != "tex2d" && this[t].passes[i].params[p] != "texCube") tech.params[p].data = this[t].passes[i].params[p].data; } } } return JSON.stringify(this.def); } }