/** * @author Mat Groves http://matgroves.com/ @Doormat23 */ PIXI.glContexts = []; // this is where we store the webGL contexts for easy access. /** * the WebGLRenderer draws the stage and all its content onto a webGL enabled canvas. This renderer * should be used for browsers that support webGL. This Render works by automatically managing webGLBatch's. * So no need for Sprite Batch's or Sprite Cloud's * Dont forget to add the view to your DOM or you will not see anything :) * * @class WebGLRenderer * @constructor * @param width=0 {Number} the width of the canvas view * @param height=0 {Number} the height of the canvas view * @param view {HTMLCanvasElement} the canvas to use as a view, optional * @param transparent=false {Boolean} If the render view is transparent, default false * @param antialias=false {Boolean} sets antialias (only applicable in chrome at the moment) * */ PIXI.WebGLRenderer = function(width, height, view, transparent, antialias) { if(!PIXI.defaultRenderer)PIXI.defaultRenderer = this; this.type = PIXI.WEBGL_RENDERER; // do a catch.. only 1 webGL renderer.. /** * Whether the render view is transparent * * @property transparent * @type Boolean */ this.transparent = !!transparent; /** * The width of the canvas view * * @property width * @type Number * @default 800 */ this.width = width || 800; /** * The height of the canvas view * * @property height * @type Number * @default 600 */ this.height = height || 600; /** * The canvas element that everything is drawn to * * @property view * @type HTMLCanvasElement */ this.view = view || document.createElement( 'canvas' ); this.view.width = this.width; this.view.height = this.height; // deal with losing context.. this.contextLost = this.handleContextLost.bind(this); this.contextRestoredLost = this.handleContextRestored.bind(this); this.view.addEventListener('webglcontextlost', this.contextLost, false); this.view.addEventListener('webglcontextrestored', this.contextRestoredLost, false); this.options = { alpha: this.transparent, antialias:!!antialias, // SPEED UP?? premultipliedAlpha:!!transparent, stencil:true }; var gl = null; ['experimental-webgl', 'webgl'].forEach(function(name) { try { gl = gl || this.view.getContext(name, this.options); } catch(e) {} }, this); if (!gl) { // fail, not able to get a context throw new Error('This browser does not support webGL. Try using the canvas renderer' + this); } this.gl = gl; this.glContextId = gl.id = PIXI.WebGLRenderer.glContextId ++; PIXI.glContexts[this.glContextId] = gl; if(!PIXI.blendModesWebGL) { PIXI.blendModesWebGL = []; PIXI.blendModesWebGL[PIXI.blendModes.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; PIXI.blendModesWebGL[PIXI.blendModes.ADD] = [gl.SRC_ALPHA, gl.DST_ALPHA]; PIXI.blendModesWebGL[PIXI.blendModes.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; PIXI.blendModesWebGL[PIXI.blendModes.SCREEN] = [gl.SRC_ALPHA, gl.ONE]; PIXI.blendModesWebGL[PIXI.blendModes.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; PIXI.blendModesWebGL[PIXI.blendModes.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; PIXI.blendModesWebGL[PIXI.blendModes.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; PIXI.blendModesWebGL[PIXI.blendModes.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; PIXI.blendModesWebGL[PIXI.blendModes.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; PIXI.blendModesWebGL[PIXI.blendModes.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; PIXI.blendModesWebGL[PIXI.blendModes.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; PIXI.blendModesWebGL[PIXI.blendModes.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; PIXI.blendModesWebGL[PIXI.blendModes.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; PIXI.blendModesWebGL[PIXI.blendModes.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; PIXI.blendModesWebGL[PIXI.blendModes.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; PIXI.blendModesWebGL[PIXI.blendModes.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; PIXI.blendModesWebGL[PIXI.blendModes.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; } this.projection = new PIXI.Point(); this.projection.x = this.width/2; this.projection.y = -this.height/2; this.offset = new PIXI.Point(0, 0); this.resize(this.width, this.height); this.contextLost = false; // time to create the render managers! each one focuses on managine a state in webGL this.shaderManager = new PIXI.WebGLShaderManager(gl); // deals with managing the shader programs and their attribs this.spriteBatch = new PIXI.WebGLSpriteBatch(gl); // manages the rendering of sprites this.maskManager = new PIXI.WebGLMaskManager(gl); // manages the masks using the stencil buffer this.filterManager = new PIXI.WebGLFilterManager(gl, this.transparent); // manages the filters this.renderSession = {}; this.renderSession.gl = this.gl; this.renderSession.drawCount = 0; this.renderSession.shaderManager = this.shaderManager; this.renderSession.maskManager = this.maskManager; this.renderSession.filterManager = this.filterManager; this.renderSession.spriteBatch = this.spriteBatch; this.renderSession.renderer = this; gl.useProgram(this.shaderManager.defaultShader.program); gl.disable(gl.DEPTH_TEST); gl.disable(gl.CULL_FACE); gl.enable(gl.BLEND); gl.colorMask(true, true, true, this.transparent); }; // constructor PIXI.WebGLRenderer.prototype.constructor = PIXI.WebGLRenderer; /** * Renders the stage to its webGL view * * @method render * @param stage {Stage} the Stage element to be rendered */ PIXI.WebGLRenderer.prototype.render = function(stage) { if(this.contextLost)return; // if rendering a new stage clear the batches.. if(this.__stage !== stage) { if(stage.interactive)stage.interactionManager.removeEvents(); // TODO make this work // dont think this is needed any more? this.__stage = stage; } // update any textures this includes uvs and uploading them to the gpu PIXI.WebGLRenderer.updateTextures(); // update the scene graph stage.updateTransform(); // interaction if(stage._interactive) { //need to add some events! if(!stage._interactiveEventsAdded) { stage._interactiveEventsAdded = true; stage.interactionManager.setTarget(this); } } var gl = this.gl; // -- Does this need to be set every frame? -- // //gl.colorMask(true, true, true, this.transparent); gl.viewport(0, 0, this.width, this.height); // make sure we are bound to the main frame buffer gl.bindFramebuffer(gl.FRAMEBUFFER, null); if(this.transparent) { gl.clearColor(0, 0, 0, 0); } else { gl.clearColor(stage.backgroundColorSplit[0],stage.backgroundColorSplit[1],stage.backgroundColorSplit[2], 1); } gl.clear(gl.COLOR_BUFFER_BIT); this.renderDisplayObject( stage, this.projection ); // interaction if(stage.interactive) { //need to add some events! if(!stage._interactiveEventsAdded) { stage._interactiveEventsAdded = true; stage.interactionManager.setTarget(this); } } else { if(stage._interactiveEventsAdded) { stage._interactiveEventsAdded = false; stage.interactionManager.setTarget(this); } } /* //can simulate context loss in Chrome like so: this.view.onmousedown = function(ev) { console.dir(this.gl.getSupportedExtensions()); var ext = ( gl.getExtension("WEBGL_scompressed_texture_s3tc") // gl.getExtension("WEBGL_compressed_texture_s3tc") || // gl.getExtension("MOZ_WEBGL_compressed_texture_s3tc") || // gl.getExtension("WEBKIT_WEBGL_compressed_texture_s3tc") ); console.dir(ext); var loseCtx = this.gl.getExtension("WEBGL_lose_context"); console.log("killing context"); loseCtx.loseContext(); setTimeout(function() { console.log("restoring context..."); loseCtx.restoreContext(); }.bind(this), 1000); }.bind(this); */ }; /** * Renders a display Object * * @method renderDIsplayObject * @param displayObject {DisplayObject} The DisplayObject to render * @param projection {Point} The projection * @param buffer {Array} a standard WebGL buffer */ PIXI.WebGLRenderer.prototype.renderDisplayObject = function(displayObject, projection, buffer) { // reset the render session data.. this.renderSession.drawCount = 0; this.renderSession.currentBlendMode = 9999; this.renderSession.projection = projection; this.renderSession.offset = this.offset; // start the sprite batch this.spriteBatch.begin(this.renderSession); // start the filter manager this.filterManager.begin(this.renderSession, buffer); // render the scene! displayObject._renderWebGL(this.renderSession); // finish the sprite batch this.spriteBatch.end(); }; /** * Updates the textures loaded into this webgl renderer * * @static * @method updateTextures * @private */ PIXI.WebGLRenderer.updateTextures = function() { var i = 0; //TODO break this out into a texture manager... // for (i = 0; i < PIXI.texturesToUpdate.length; i++) // PIXI..updateWebGLTexture(PIXI.texturesToUpdate[i], this.gl); for (i=0; i < PIXI.Texture.frameUpdates.length; i++) PIXI.WebGLRenderer.updateTextureFrame(PIXI.Texture.frameUpdates[i]); for (i = 0; i < PIXI.texturesToDestroy.length; i++) PIXI.WebGLRenderer.destroyTexture(PIXI.texturesToDestroy[i]); PIXI.texturesToUpdate.length = 0; PIXI.texturesToDestroy.length = 0; PIXI.Texture.frameUpdates.length = 0; }; /** * Destroys a loaded webgl texture * * @method destroyTexture * @param texture {Texture} The texture to update * @private */ PIXI.WebGLRenderer.destroyTexture = function(texture) { //TODO break this out into a texture manager... for (var i = texture._glTextures.length - 1; i >= 0; i--) { var glTexture = texture._glTextures[i]; var gl = PIXI.glContexts[i]; if(gl && glTexture) { gl.deleteTexture(glTexture); } } texture._glTextures.length = 0; }; /** * * @method updateTextureFrame * @param texture {Texture} The texture to update the frame from * @private */ PIXI.WebGLRenderer.updateTextureFrame = function(texture) { texture.updateFrame = false; // now set the uvs. Figured that the uv data sits with a texture rather than a sprite. // so uv data is stored on the texture itself texture._updateWebGLuvs(); }; /** * resizes the webGL view to the specified width and height * * @method resize * @param width {Number} the new width of the webGL view * @param height {Number} the new height of the webGL view */ PIXI.WebGLRenderer.prototype.resize = function(width, height) { this.width = width; this.height = height; this.view.width = width; this.view.height = height; this.gl.viewport(0, 0, this.width, this.height); this.projection.x = this.width/2; this.projection.y = -this.height/2; }; /** * Creates a WebGL texture * * @method createWebGLTexture * @param texture {Texture} the texture to render * @param gl {webglContext} the WebGL context * @static */ PIXI.createWebGLTexture = function(texture, gl) { if(texture.hasLoaded) { texture._glTextures[gl.id] = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture._glTextures[gl.id]); gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.source); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, texture.scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); // reguler... if(!texture._powerOf2) { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); } else { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); } gl.bindTexture(gl.TEXTURE_2D, null); texture._dirty[gl.id] = false; } return texture._glTextures[gl.id]; }; /** * Updates a WebGL texture * * @method updateWebGLTexture * @param texture {Texture} the texture to update * @param gl {webglContext} the WebGL context * @private */ PIXI.updateWebGLTexture = function(texture, gl) { if( texture._glTextures[gl.id] ) { gl.bindTexture(gl.TEXTURE_2D, texture._glTextures[gl.id]); gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.source); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, texture.scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); // reguler... if(!texture._powerOf2) { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); } else { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); } texture._dirty[gl.id] = false; } }; /** * Handles a lost webgl context * * @method handleContextLost * @param event {Event} * @private */ PIXI.WebGLRenderer.prototype.handleContextLost = function(event) { event.preventDefault(); this.contextLost = true; }; /** * Handles a restored webgl context * * @method handleContextRestored * @param event {Event} * @private */ PIXI.WebGLRenderer.prototype.handleContextRestored = function() { //try 'experimental-webgl' try { this.gl = this.view.getContext('experimental-webgl', this.options); } catch (e) { //try 'webgl' try { this.gl = this.view.getContext('webgl', this.options); } catch (e2) { // fail, not able to get a context throw new Error(' This browser does not support webGL. Try using the canvas renderer' + this); } } var gl = this.gl; gl.id = PIXI.WebGLRenderer.glContextId ++; // need to set the context... this.shaderManager.setContext(gl); this.spriteBatch.setContext(gl); this.maskManager.setContext(gl); this.filterManager.setContext(gl); this.renderSession.gl = this.gl; gl.disable(gl.DEPTH_TEST); gl.disable(gl.CULL_FACE); gl.enable(gl.BLEND); gl.colorMask(true, true, true, this.transparent); this.gl.viewport(0, 0, this.width, this.height); for(var key in PIXI.TextureCache) { var texture = PIXI.TextureCache[key].baseTexture; texture._glTextures = []; } /** * Whether the context was lost * @property contextLost * @type Boolean */ this.contextLost = false; }; /** * Removes everything from the renderer (event listeners, spritebatch, etc...) * * @method destroy */ PIXI.WebGLRenderer.prototype.destroy = function() { // deal with losing context.. // remove listeners this.view.removeEventListener('webglcontextlost', this.contextLost); this.view.removeEventListener('webglcontextrestored', this.contextRestoredLost); PIXI.glContexts[this.glContextId] = null; this.projection = null; this.offset = null; // time to create the render managers! each one focuses on managine a state in webGL this.shaderManager.destroy(); this.spriteBatch.destroy(); this.maskManager.destroy(); this.filterManager.destroy(); this.shaderManager = null; this.spriteBatch = null; this.maskManager = null; this.filterManager = null; this.gl = null; // this.renderSession = null; }; PIXI.WebGLRenderer.glContextId = 0;