/* <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> */ /* * jpass geometry set - determines the category(s) of geometry that a pass will render * can be OR'ed together */ jpassGeoSet = { 'BACKGROUND' :1, 'OPAQUE' :2, 'TRANSPARENT' :4, 'ADDITIVE' :8, 'TRANSLUCENT' :16, 'FOREGROUND' :32, 'ALL' :127, 'SCREEN_QUAD' :128, // a screen aligned quad - for rendering a texture to screen 'SHADOW' :256, // the opaque geometry from shadow light's point of view 'MAXSETS' :9 }; /* * The abstract base class that defines a jpass * a jpass represents a single render pass of the scene graph */ _jpassBaseClass = function() { this.context = g_Engine.getContext(); this.renderer = g_Engine.getContext().renderer; this.sortCats = rdgeConstants.categoryEnumeration; this.bucketCount = rdgeConstants.categoryEnumeration.MAX_CAT; // render order this.renderOrder = []; this.renderOrder[this.sortCats.BACKGROUND] = 0; this.renderOrder[this.sortCats.OPAQUE] = 1; this.renderOrder[this.sortCats.TRANSPARENT] = 2; this.renderOrder[this.sortCats.ADDITIVE] = 3 this.renderOrder[this.sortCats.TRANSLUCENT] = 4; this.renderOrder[this.sortCats.FOREGROUND] = 5; // the name of this pass this.name = "renderPass_" + nodeIdGen.getId(); /* * if 0 this pass and children are culled from rendering */ this.visibility = 1; /* * called when the pass is hidden - override for customication */ this.onHide = function() { } /* * Called by the system to hide the pass and its children */ this.hidePass = function() { this.onHide(); for (var i = 0, len = this.children.length; i < len; ++i) { this.children[i].hidePass(); } } /* * the default output render targets that this pass will create */ this.defaultTargetOut = {}; /* * All the outputs required by the pass */ this.outputs = [ // example // {'name':"u_mainRT", 'type':"target", 'width':1024, 'height':1024, 'mips':false} ]; /* * notifies the the renderer that the viewport was modified and needs to be reset */ this.dirty = false; /* * Index of the currently selected output target */ this.outputIndex = 0; /* * outputs from the previous pass are set as inputs for this pass */ this.inputs = [ ]; /* * other textures requested for this pass */ this.textures = [ ]; /* * the flags that control how the pass is rendered */ this.frustum_culling = "enable"; // disable/enable frustum culling during the pass this.clear = null; // flags to clear the output target with before rendering this.clearColor = null; /* * Contains a list of geometry to be rendered, during a post process render pass this will usually by a screen quad */ this.renderList = [ // example // { 'name':'opaqeobjects', 'geo'{ 'OPAQUE':[ new renderObject(meshNode, transformNode, RenderContext)]} } ]; /* * The passes that will render after this pass */ this.children = []; /* * This shader will override all other shaders */ this.shader = null; /* * Technique of from shader to use, if null currently set technique is used */ this.technique = null; /* * determines the geometry that will be rendered during the pass */ this.geometrySet = "SCREEN_QUAD"; /* * A camera set here will override any camera active in the scene */ this.camera = null; /* * Initialize the pass */ this.init = function() { } /* * inserts a node into the child map using the pass name as the key */ this.insertChildPass = function(jpassObj) { this.children[jpassObj.name] = jpassObj; } /* * the scene-graph to process */ this.process = function() { // pre-defined local variables to prevent allocation var context; var shaderProg; var listCount; var len; var passes; var pass; var node; var mesh; var meshCount; var nodeIdx = 0; var meshIdx = 0; var paramIdx = 0; var passIdx = 0; var renderer = g_Engine.getContext().renderer; //this.renderer = g_Engine.getContext().renderer; // bind output target for rendering this.bindOutput(); // call custom pre-render step this.preRender(); var activeCam = renderer.cameraManager().getActiveCamera(); if (this.technique) { this.shader.setTechnique(this.technique); } renderer.projectionMatrix = activeCam.proj; // parameters that can be set once per pass rdgeGlobalParameters.u_inv_viewport_width.set([1.0 / renderer.vpWidth]); rdgeGlobalParameters.u_inv_viewport_height.set([1.0 / renderer.vpHeight]); rdgeGlobalParameters.u_farZ.set([activeCam.zFar()]); rdgeGlobalParameters.u_projMatrix.set(renderer.projectionMatrix); for (var bucketIdx = 0, bckCnt = this.renderList.length; bucketIdx < bckCnt; ++bucketIdx) { //var curList = this.renderList[bucketIdx]; listCount = this.renderList[bucketIdx].length; for (nodeIdx = 0; nodeIdx < listCount; ++nodeIdx) { node = this.renderList[bucketIdx][nodeIdx].node; if (node.world) { context = this.renderList[bucketIdx][nodeIdx].context; shaderProg = this.shader ? this.shader : context.shaderProg; renderer.mvMatrix = mat4.mul4x3(node.world, activeCam.view); renderer.invMvMatrix = mat4.inverse(renderer.mvMatrix); renderer.normalMatrix = mat4.transpose(renderer.invMvMatrix); rdgeGlobalParameters.u_mvMatrix.set(renderer.mvMatrix); rdgeGlobalParameters.u_normalMatrix.set(renderer.normalMatrix); rdgeGlobalParameters.u_worldMatrix.set(node.world); rdgeGlobalParameters.u_viewMatrix.set(activeCam.view); rdgeGlobalParameters.u_invViewMatrix.set(mat4.inverse(activeCam.view)); rdgeGlobalParameters.u_invMvMatrix.set(renderer.invMvMatrix); len = context.uniforms.length; for (paramIdx = 0; paramIdx < len; ++paramIdx) { rdgeGlobalParameters[context.uniforms[paramIdx].name].set(context.uniforms[paramIdx].value); } // augment the texture list with any post processing textures this.updateTextureContext(context.textureList); shaderProg.setLightContext(context.lights); shaderProg.setTextureContext(context.textureList); passes = shaderProg.begin(); pass = null; for (passIdx = 0; passIdx < passes; ++passIdx) { pass = shaderProg.beginPass(passIdx); meshCount = node.meshes.length; for (meshIdx = 0; meshIdx < meshCount; ++meshIdx) { mesh = g_meshMan.getModelByName(node.meshes[meshIdx].mesh.name); if (mesh) renderer.drawPrimitive(mesh.primitive, pass.program, pass.attributes); } shaderProg.endPass(); this.onPassEnd(); } shaderProg.end(); } } } // call custom post render step this.postRender(); // remove the bound render target if applicable this.unbindOutput(); } /* * handle any setup 'before' processing the geo/scenegraph * the first step in the pass */ this.preRender = function() { } /* * handle any setup 'after' processing the geo/scenegraph * the last step in the pass */ this.postRender = function() { } /* * Custom function to handle any processing in between jshader passes */ this.onPassEnd = function() { if (this.outputIndex + 1 < this.outputs.length) ++this.outputIndex; } /* * Set the list of objects to render */ this.setRenderList = function(contextList) { this.renderList = contextList; } /* * Set the render targets to use as input for the next pass */ this.setInputs = function(inputsArr) { this.inputs = inputsArr.slice(); } /* * Augment the textureList passed in with the input textures * this will cause the textures to be bound to the jshader */ this.updateTextureContext = function(textureList) { var inputs = this.inputs.slice(); for (var i = 0; i < this.inputs.length; ++i) { textureList.push(inputs[i]); } for (var i = 0; i < this.textures.length; ++i) { if (this.textures[i].enabled) textureList.push(this.textures[i]); } } /* * If there is an output surface this will bind it */ this.bindOutput = function() { this.outputIndex = 0; if (this.outputs[this.outputIndex]) { this.dirty = true; var oldClear = null; var fb = this.outputs[this.outputIndex].data[0].frameBuffer; this.renderer.ctx.bindFramebuffer(this.renderer.ctx.FRAMEBUFFER, fb); this.renderer.ctx.viewport(0, 0, fb.width, fb.height); if (this.clearColor) { oldClear = this.renderer.clearColor; this.renderer.setClearColor(this.clearColor); } if (this.clear) this.renderer.clear(this.clear); else this.renderer._clear(); if (this.clearColor) { this.renderer.setClearColor(oldClear); } } } /* * If an output surface was bound this will unbind it */ this.unbindOutput = function() { if (this.dirty) { this.dirty = false; this.renderer.ctx.bindFramebuffer(this.renderer.ctx.FRAMEBUFFER, null); this.renderer.setViewPort(0, 0, this.renderer.vpWidth, this.renderer.vpHeight); } } } /* * The concrete class to be used when creating a scene pass */ jpass = function( def ) { // inherit from the base class this.inheritedFrom = _jpassBaseClass; this.inheritedFrom(); // setup the jpass as defined, recursively instantiated the children as they are encountered for(var obj in def) { this[obj] = def[obj]; if(obj == "children") { var childCount = this[obj].length; for(var i = 0; i < childCount; ++i) { this[obj][i] = new jpass(this[obj][i]); } } // validate array and create the texture/render-target else if(obj == "inputs" || obj == "outputs") { // this makes sure the data type was passed as an array and converts it to an array if needed if(!this[obj].slice()) { this[obj] = [this[obj]]; } var count = this[obj].length; for(var i = 0; i < count; ++i) { // if the texture/render-target is not created yet do so now if(!this[obj][i].data) { if(this[obj][i].type == "target") { // setup the render target this[obj][i].data = [this.renderer.createRenderTargetTexture(this[obj][i].name, this[obj][i].width, this[obj][i].height, this[obj][i].mips)]; } else { // make sure data is defined this[obj][i].data = [null] } } if(!this[obj][i].handle) { this[obj][i].handle = this[obj][i].data[0]; } } } else if("renderList" == obj) { // put the items listed into their buckets var renderList = new Array(rdgeConstants.categoryEnumeration.MAX_CAT); renderList[rdgeConstants.categoryEnumeration.BACKGROUND] = []; //BACKGROUND renderList[rdgeConstants.categoryEnumeration.OPAQUE] = []; //OPAQUE renderList[rdgeConstants.categoryEnumeration.TRANSPARENT] = []; //TRANSPARENT renderList[rdgeConstants.categoryEnumeration.ADDITIVE] = []; //ADDITIVE renderList[rdgeConstants.categoryEnumeration.TRANSLUCENT] = []; //TRANSLUCENT renderList[rdgeConstants.categoryEnumeration.FOREGROUND] = []; //FOREGROUND for(var buckets in this[obj]) { // get the enumeration value (ie rdgeConstants.categoryEnumeration.OPAQUE) var bucket = rdgeConstants.categoryEnumeration[buckets]; // create the list for this bucket var len = this[obj][buckets].length; for(var i = 0; i < len; ++i) { // add the corresponding objects to the bucket renderList[bucket].push(this[obj][buckets][i]); } } // overwrite with the new list this[obj] = renderList; } else if("shader" == obj) { // setup the shader if(typeof this[obj] == "string") { this.shader = new jshader(this[obj]); } else { var finalShader = new jshader(); finalShader.def = this[obj]; finalShader.init(); this[obj] = finalShader; } } else if("geometrySet" == obj) { if( typeof this[obj] == "string") { var sets = this[obj].split("|"); var setValue = 0; for(var i = 0; i < sets.length; ++i) { setValue |= jpassGeoSet[sets[i]]; if(sets[i] === "SCREEN_QUAD") { this[obj] = jpassGeoSet[sets[i]]; this.renderList[0] = [new renderObject( createScreenQuadNode() )]; break; } } this[obj] = setValue; } } else if("textures" == obj) { for(var i = 0, len = this[obj].length; i < len; ++i) { if(typeof this[obj][i].data == "string") { this[obj][i].name = this[obj][i].data; this[obj][i].data = [this[obj][i].data]; this[obj][i].enabled = true; } else { this[obj][i].name = this.renderer.getTextureByName(this[obj][i].data[0].lookUpName); } } } } // call initialize code this.init(); } /* * a graph describing the hierarchy of scene pass objects to create the final composition */ jpassGraph = function( def ) { // the root pass this.root = null; // going through steps to build the pass graph if(def && typeof def == "string") { // make ajax request } else if(def) { this.root = new jpass(def); } else { this.root = new jpass({}); } this.find = null; this.insert = null; // an index to the opaque bucket this.OPAQUE = rdgeConstants.categoryEnumeration.OPAQUE; this.setPassRoot = function(jpassObj) { this.root = jpassObj; } /* * Helper function to generate the references to passes available as passGraph."passName" */ this._initHelper = function(node) { if(!node) return; this[node.name] = node; for(var child in node.children) { this._initHelper(node.children[child]); } } this._initHelper(this.root); /* * @param parentName - the name of the parent object to insert under * @param jpassObj - the jpass object to insert */ this.insertAsChild = function(parentName, jpassObj) { this.find = parentName; this.insert = jpassObj; // create ref for this object this[jpassObj.name] = jpassObj; this._insertHelper(this.root); this.find = null; this.insert = null; } /* * Recursive helper function for traversing the graph and insterting the node */ this._insertHelper = function(node) { if(!node) return false; if(node.name == this.find) { node.insertChildPass(this.insert); return true; } for(var child in node.children) { if(this._insertHelper(node.children[child])) { break; } } return true; } // maps jpassGeoSet values to indices this.geoSetMap = []; var geoSetIdx = 0; for(var geoSet in jpassGeoSet) { this.geoSetMap[geoSetIdx++] = jpassGeoSet[geoSet]; } /* * Traverse the render graph, breadth first to produce the final render output * note: breadth first is used to allow generation of an entire level at once, * the peers in one level could, in theory, be processed in parallel */ this.render = function( sceneGraph ) { g_renderStats.numPasses.value=0; var renderList = sceneGraph.renderList; var renderer = g_Engine.getContext().renderer; // set the first pass with scene geometry this.root.setRenderList(renderList); var curRenderList = null; var queue = [ {'parent':this.root } ]; var parent = null; var setCount = this.geoSetMap.length; var maxCats = rdgeConstants.categoryEnumeration.MAX_CAT; var idx = 0; var renderListIdx = 0; while(queue.length) { var parent = queue.shift().parent; // pull out the shadow geometry if(sceneGraph.shadowsEnabled && parent.geometrySet === jpassGeoSet.SHADOW) { parent.setRenderList( [ sceneGraph.shadowRenderList ] ); } // screen quad needs special prep since geometry is never re-assigned, lists needs to be cleaned up else if(parent.geometrySet === jpassGeoSet.SCREEN_QUAD) { parent.renderList[0][0].context.textureList = []; } // pull out all the sort categories that the pass requested else { curRenderList = []; renderListIdx = 0; // create the pass's render list for(idx = 0; idx < setCount; ++idx) { // assign the render list, checking to make sure the index is less than the total number of material categories if(parent.geometrySet & this.geoSetMap[idx]) { if(renderList[idx]) { curRenderList[renderListIdx++] = renderList[idx]; } } } parent.setRenderList(curRenderList); } // if this pass has a camera push it on the stack if(parent.camera) { renderer.cameraManager().pushCamera(parent.camera); } // process the geometry - all rendering occurs here g_renderStats.numPasses.value++; parent.process(); // if this pass had a camera pop it off the stack if(parent.camera) { renderer.cameraManager().popCamera(); } // push on the children for(idx = parent.children.length - 1; idx >= 0; --idx) { if(parent.children[idx].visibility === 1) { parent.children[idx].setInputs( parent.outputs ); queue.unshift({'parent': parent.children[idx] }); } else if(parent.children[idx].visibility === 0) { parent.children[idx].hidePass(); parent.children[idx].visibility = 2; } } } } }