/* Copyright (c) 2012, Motorola Mobility LLC. All Rights Reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Motorola Mobility LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // 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; } } } } };