var SystemRenderer = require('../SystemRenderer'), ShaderManager = require('./managers/ShaderManager'), MaskManager = require('./managers/MaskManager'), StencilManager = require('./managers/StencilManager'), FilterManager = require('./managers/FilterManager'), BlendModeManager = require('./managers/BlendModeManager'), RenderTarget = require('./utils/RenderTarget'), ObjectRenderer = require('./utils/ObjectRenderer'), math = require('../../math'), utils = require('../../utils'), CONST = require('../../const'); /** * The WebGLRenderer draws the scene 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 webGLBatchs. * So no need for Sprite Batches or Sprite Clouds. * Don't forget to add the view to your DOM or you will not see anything :) * * @class * @namespace PIXI * @param [width=0] {number} the width of the canvas view * @param [height=0] {number} the height of the canvas view * @param [options] {object} The optional renderer parameters * @param [options.view] {HTMLCanvasElement} the canvas to use as a view, optional * @param [options.transparent=false] {boolean} If the render view is transparent, default false * @param [options.autoResize=false] {boolean} If the render view is automatically resized, default false * @param [options.antialias=false] {boolean} sets antialias (only applicable in chrome at the moment) * @param [options.resolution=1] {number} the resolution of the renderer retina would be 2 * @param [options.clearBeforeRender=true] {boolean} This sets if the CanvasRenderer will clear the canvas or * not before the new render pass. * @param [options.preserveDrawingBuffer=false] {boolean} enables drawing buffer preservation, enable this if * you need to call toDataUrl on the webgl context. */ function WebGLRenderer(width, height, options) { SystemRenderer.call(this, 'WebGL', width, height, options); this.type = CONST.RENDERER_TYPE.WEBGL; this._boundUpdateTexture = this.updateTexture.bind(this); this._boundDestroyTexture = this.destroyTexture.bind(this); this._boundContextLost = this.handleContextLost.bind(this); this._boundContextRestored = this.handleContextRestored.bind(this); this.view.addEventListener('webglcontextlost', this._boundContextLost, false); this.view.addEventListener('webglcontextrestored', this._boundContextRestored, false); /** * The options passed in to create a new webgl context. * * @member {object} * @private */ this._contextOptions = { alpha: this.transparent, antialias: options.antialias, premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', stencil: true, preserveDrawingBuffer: options.preserveDrawingBuffer }; /** * Counter for the number of draws made each frame * * @member {number} */ this.drawCount = 0; /** * Deals with managing the shader programs and their attribs. * * @member {ShaderManager} */ this.shaderManager = new ShaderManager(this); /** * Manages the masks using the stencil buffer. * * @member {MaskManager} */ this.maskManager = new MaskManager(this); /** * Manages the stencil buffer. * * @member {StencilManager} */ this.stencilManager = new StencilManager(this); /** * Manages the filters. * * @member {FilterManager} */ this.filterManager = new FilterManager(this); /** * Manages the blendModes * @member {BlendModeManager} */ this.blendModeManager = new BlendModeManager(this); this.currentRenderTarget = this.renderTarget; /** * This temporary display object used as the parent of the currently being rendered item * @member DisplayObject * @private */ this._tempDisplayObjectParent = {worldTransform:new math.Matrix(), worldAlpha:1}; this.currentRenderer = new ObjectRenderer(this); this.initPlugins(); // initialize the context so it is ready for the managers. this._initContext(); // map some webGL blend modes.. this._mapBlendModes(); } // constructor WebGLRenderer.prototype = Object.create(SystemRenderer); WebGLRenderer.prototype.constructor = WebGLRenderer; module.exports = WebGLRenderer; WebGLRenderer.glContextId = 0; /** * * @private */ WebGLRenderer.prototype._initContext = function () { var gl = this.view.getContext('webgl', this._contextOptions) || this.view.getContext('experimental-webgl', this._contextOptions); this.gl = gl; if (!gl) { // fail, not able to get a context throw new Error('This browser does not support webGL. Try using the canvas renderer'); } this.glContextId = WebGLRenderer.glContextId++; gl.id = this.glContextId; gl.renderer = this; // set up the default pixi settings.. gl.disable(gl.DEPTH_TEST); gl.disable(gl.CULL_FACE); gl.enable(gl.BLEND); this.renderTarget = new RenderTarget(this.gl, this.width, this.height, null, true); this.emit('context', gl); // setup the width/height properties and gl viewport this.resize(this.width, this.height); }; /** * Renders the object to its webGL view * * @param object {DisplayObject} the object to be rendered */ WebGLRenderer.prototype.render = function (object) { // no point rendering if our context has been blown up! if (this.gl.isContextLost()) { return; } var cacheParent = object.parent; object.parent = this._tempDisplayObjectParent; // update the scene graph object.updateTransform(); object.parent = cacheParent; var gl = this.gl; // make sure we are bound to the main frame buffer gl.bindFramebuffer(gl.FRAMEBUFFER, null); if (this.clearBeforeRender) { if (this.transparent) { gl.clearColor(0, 0, 0, 0); } else { gl.clearColor(this._backgroundColorRgb[0], this._backgroundColorRgb[1], this._backgroundColorRgb[2], 1); } gl.clear(gl.COLOR_BUFFER_BIT); } this.renderDisplayObject(object, this.renderTarget);//this.projection); }; /** * Renders a Display Object. * * @param displayObject {DisplayObject} The DisplayObject to render * @param projection {Point} The projection * @param buffer {Array} a standard WebGL buffer */ WebGLRenderer.prototype.renderDisplayObject = function (displayObject, renderTarget)//projection, buffer) { this.blendModeManager.setBlendMode(CONST.blendModes.NORMAL); this.setRenderTarget(renderTarget); // reset the render session data.. this.drawCount = 0; // start the filter manager this.filterManager.begin(); // render the scene! displayObject.renderWebGL(this); // finish the sprite batch this.currentRenderer.flush(); }; WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) { if (this.currentRenderer === objectRenderer) { return; } this.currentRenderer.stop(); this.currentRenderer = objectRenderer; this.currentRenderer.start(); }; WebGLRenderer.prototype.setRenderTarget = function (renderTarget) { this.currentRenderTarget = renderTarget; this.currentRenderTarget.activate(); this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); }; /** * Resizes the webGL view to the specified width and height. * * @param width {number} the new width of the webGL view * @param height {number} the new height of the webGL view */ WebGLRenderer.prototype.resize = function (width, height) { SystemRenderer.prototype.resize.call(this, width, height); this.gl.viewport(0, 0, this.width, this.height); this.filterManager.resize(width, height); this.renderTarget.resize(width, height); }; /** * Updates and/or Creates a WebGL texture for the renderer's context. * * @param texture {BaseTexture|Texture} the texture to update */ WebGLRenderer.prototype.updateTexture = function (texture) { texture = texture.baseTexture || texture; if (!texture.hasLoaded) { return; } var gl = this.gl; if (!texture._glTextures[gl.id]) { texture._glTextures[gl.id] = gl.createTexture(); texture.on('update', this._boundUpdateTexture); texture.on('dispose', this._boundDestroyTexture); } gl.bindTexture(gl.TEXTURE_2D, texture._glTextures[gl.id]); gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultipliedAlpha); 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 === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); if (texture.mipmap && utils.isPowerOfTwo(texture.width, texture.height)) { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); gl.generateMipmap(gl.TEXTURE_2D); } else { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); } 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); } return texture._glTextures[gl.id]; }; WebGLRenderer.prototype.destroyTexture = function (texture) { texture = texture.baseTexture || texture; if (!texture.hasLoaded) { return; } if (texture._glTextures[this.gl.id]) { this.gl.deleteTexture(texture._glTextures[this.gl.id]); } }; /** * Handles a lost webgl context * * @param event {Event} * @private */ WebGLRenderer.prototype.handleContextLost = function (event) { event.preventDefault(); }; /** * Handles a restored webgl context * * @param event {Event} * @private */ WebGLRenderer.prototype.handleContextRestored = function () { this._initContext(); // empty all the old gl textures as they are useless now for (var key in utils.BaseTextureCache) { utils.BaseTextureCache[key]._glTextures = {}; } }; /** * Removes everything from the renderer (event listeners, spritebatch, etc...) * * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ WebGLRenderer.prototype.destroy = function (removeView) { // remove listeners this.view.removeEventListener('webglcontextlost', this._boundContextLost); this.view.removeEventListener('webglcontextrestored', this._boundContextRestored); // call base destroy SystemRenderer.prototype.destroy.call(this, removeView); this.uuid = 0; // destroy the managers this.shaderManager.destroy(); this.maskManager.destroy(); this.stencilManager.destroy(); this.filterManager.destroy(); this.shaderManager = null; this.maskManager = null; this.filterManager = null; this.blendModeManager = null; this._boundContextLost = null; this._boundContextRestored = null; this._contextOptions = null; this.drawCount = 0; this.gl = null; }; /** * Maps Pixi blend modes to WebGL blend modes. * * @private */ WebGLRenderer.prototype._mapBlendModes = function () { var gl = this.gl; if (!this.blendModes) { this.blendModes = {}; this.blendModes[CONST.blendModes.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; this.blendModes[CONST.blendModes.ADD] = [gl.SRC_ALPHA, gl.DST_ALPHA]; this.blendModes[CONST.blendModes.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; this.blendModes[CONST.blendModes.SCREEN] = [gl.SRC_ALPHA, gl.ONE]; this.blendModes[CONST.blendModes.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; this.blendModes[CONST.blendModes.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; this.blendModes[CONST.blendModes.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; this.blendModes[CONST.blendModes.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; this.blendModes[CONST.blendModes.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; this.blendModes[CONST.blendModes.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; this.blendModes[CONST.blendModes.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; this.blendModes[CONST.blendModes.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; this.blendModes[CONST.blendModes.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; this.blendModes[CONST.blendModes.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; this.blendModes[CONST.blendModes.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; this.blendModes[CONST.blendModes.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; this.blendModes[CONST.blendModes.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; } };