/* <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> */ // RDGE namespaces var RDGE = RDGE || {}; /* * jpass geometry set - determines the category(s) of geometry that a pass will render * can be OR'ed together */ RDGE.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 */ RDGE._jpassBaseClass = function () { this.context = RDGE.globals.engine.getContext(); this.renderer = RDGE.globals.engine.getContext().renderer; this.sortCats = RDGE.rdgeConstants.categoryEnumeration; this.bucketCount = RDGE.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_" + RDGE.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 = RDGE.globals.engine.getContext().renderer; //this.renderer = RDGE.globals.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 RDGE.rdgeGlobalParameters.u_inv_viewport_width.set([1.0 / renderer.vpWidth]); RDGE.rdgeGlobalParameters.u_inv_viewport_height.set([1.0 / renderer.vpHeight]); RDGE.rdgeGlobalParameters.u_farZ.set([activeCam.zFar()]); RDGE.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 = RDGE.mat4.mul4x3(node.world, activeCam.view); renderer.invMvMatrix = RDGE.mat4.inverse(renderer.mvMatrix); renderer.normalMatrix = RDGE.mat4.transpose(renderer.invMvMatrix); RDGE.rdgeGlobalParameters.u_mvMatrix.set(renderer.mvMatrix); RDGE.rdgeGlobalParameters.u_normalMatrix.set(renderer.normalMatrix); RDGE.rdgeGlobalParameters.u_worldMatrix.set(node.world); RDGE.rdgeGlobalParameters.u_viewMatrix.set(activeCam.view); RDGE.rdgeGlobalParameters.u_invViewMatrix.set(RDGE.mat4.inverse(activeCam.view)); RDGE.rdgeGlobalParameters.u_invMvMatrix.set(renderer.invMvMatrix); len = context.uniforms.length; for (paramIdx = 0; paramIdx < len; ++paramIdx) { RDGE.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 = RDGE.globals.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 */ RDGE.jpass = function (def) { // inherit from the base class this.inheritedFrom = RDGE._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 RDGE.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(RDGE.rdgeConstants.categoryEnumeration.MAX_CAT); renderList[RDGE.rdgeConstants.categoryEnumeration.BACKGROUND] = []; //BACKGROUND renderList[RDGE.rdgeConstants.categoryEnumeration.OPAQUE] = []; //OPAQUE renderList[RDGE.rdgeConstants.categoryEnumeration.TRANSPARENT] = []; //TRANSPARENT renderList[RDGE.rdgeConstants.categoryEnumeration.ADDITIVE] = []; //ADDITIVE renderList[RDGE.rdgeConstants.categoryEnumeration.TRANSLUCENT] = []; //TRANSLUCENT renderList[RDGE.rdgeConstants.categoryEnumeration.FOREGROUND] = []; //FOREGROUND for (var buckets in this[obj]) { // get the enumeration value (ie RDGE.rdgeConstants.categoryEnumeration.OPAQUE) var bucket = RDGE.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 RDGE.jshader(this[obj]); } else { var finalShader = new RDGE.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 |= RDGE.jpassGeoSet[sets[i]]; if (sets[i] === "SCREEN_QUAD") { this[obj] = RDGE.jpassGeoSet[sets[i]]; this.renderList[0] = [new renderObject(RDGE.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 */ RDGE.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 RDGE.jpass(def); } else { this.root = new RDGE.jpass({}); } this.find = null; this.insert = null; // an index to the opaque bucket this.OPAQUE = RDGE.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 RDGE.jpassGeoSet) { this.geoSetMap[geoSetIdx++] = RDGE.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) { var renderList = sceneGraph.renderList; var renderer = RDGE.globals.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 = RDGE.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 === RDGE.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 === RDGE.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 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; } } } } };