Source: tiny/core/renderers/webgl/WebGLRenderer.js

import SystemRenderer from '../SystemRenderer';
import MaskManager from './managers/MaskManager';
import StencilManager from './managers/StencilManager';
import FilterManager from './managers/FilterManager';
import RenderTarget from './utils/RenderTarget';
import ObjectRenderer from './utils/ObjectRenderer';
import TextureManager from './TextureManager';
import BaseTexture from '../../textures/BaseTexture';
import TextureGarbageCollector from './TextureGarbageCollector';
import WebGLState from './WebGLState';
import mapWebGLDrawModesToTiny from './utils/mapWebGLDrawModesToTiny';
import validateContext from './utils/validateContext';
import {pluginTarget} from '../../utils';
import glCore from 'pixi-gl-core';
import {RENDERER_TYPE} from '../../const';

let CONTEXT_UID = 0;

/**
 * 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
 * @memberof Tiny
 * @extends Tiny.SystemRenderer
 */
export default class WebGLRenderer extends SystemRenderer {
  /**
   *
   * @param {number}            width                               - the width of the screen
   * @param {number}            height                              - the height of the screen
   * @param {object}            options                             - The optional renderer parameters
   * @param {HTMLCanvasElement} options.view                        - the canvas to use as a view, optional
   * @param {boolean}           options.antialias=false             - sets antialias. If not available natively then FXAA antialiasing is used
   * @param {boolean}           options.autoResize=false            - If the render view is automatically resized, default false
   * @param {boolean}           options.clearBeforeRender=true      - This sets if the CanvasRenderer will clear the canvas or not before the new render pass. If you wish to set this to false, you *must* set preserveDrawingBuffer to `true`.
   * @param {boolean}           options.legacy=false                - If true Tiny will aim to ensure compatibility with older / less advanced devices. If you experiance unexplained flickering try setting this to true.
   * @param {boolean}           options.preserveDrawingBuffer=false - enables drawing buffer preservation, enable this if you need to call toDataUrl on the webgl context.
   * @param {number}            options.resolution=1                - The resolution / device pixel ratio of the renderer. The resolution of the renderer retina would be 2.
   * @param {boolean}           options.roundPixels=false           - If true Tiny will Math.floor() x/y values when rendering, stopping pixel interpolation.
   * @param {boolean}           options.transparent=false           - If the render view is transparent, default false
   */
  constructor(width, height, options) {
    super('WebGL', width, height, options);

    this.legacy = this.options.legacy;

    if (this.legacy) {
      glCore.VertexArrayObject.FORCE_NATIVE = true;
    }

    /**
     * The type of this renderer as a standardised const
     *
     * @member {number}
     * @see Tiny.RENDERER_TYPE
     */
    this.type = RENDERER_TYPE.WEBGL;

    this.handleContextLost = this.handleContextLost.bind(this);
    this.handleContextRestored = this.handleContextRestored.bind(this);

    this.view.addEventListener('webglcontextlost', this.handleContextLost, false);
    this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false);

    /**
     * The options passed in to create a new webgl context.
     *
     * @member {object}
     * @private
     */
    this._contextOptions = {
      alpha: this.transparent,
      antialias: this.options.antialias,
      premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied',
      stencil: true,
      preserveDrawingBuffer: this.options.preserveDrawingBuffer,
    };

    this._backgroundColorRgba[3] = this.transparent ? 0 : 1;

    /**
     * Manages the masks using the stencil buffer.
     *
     * @member {Tiny.MaskManager}
     */
    this.maskManager = new MaskManager(this);

    /**
     * Manages the stencil buffer.
     *
     * @member {Tiny.StencilManager}
     */
    this.stencilManager = new StencilManager(this);

    /**
     * An empty renderer.
     *
     * @member {Tiny.ObjectRenderer}
     */
    this.emptyRenderer = new ObjectRenderer(this);

    /**
     * The currently active ObjectRenderer.
     *
     * @member {Tiny.ObjectRenderer}
     */
    this.currentRenderer = this.emptyRenderer;

    this.initPlugins();

    /**
     * The current WebGL rendering context, it is created here
     *
     * @member {WebGLRenderingContext}
     */
    // initialize the context so it is ready for the managers.
    if (this.options.context) {
      // checks to see if a context is valid..
      validateContext(this.options.context);
    }

    this.gl = this.options.context || glCore.createContext(this.view, this._contextOptions);

    this.CONTEXT_UID = CONTEXT_UID++;

    /**
     * The currently active ObjectRenderer.
     *
     * @member {Tiny.WebGLState}
     */
    this.state = new WebGLState(this.gl);

    this.renderingToScreen = true;

    /**
     * Holds the current state of textures bound to the GPU.
     * @type {Array}
     */
    this.boundTextures = null;

    /**
     * Holds the current shader
     *
     * @member {Tiny.Shader}
     */
    this._activeShader = null;

    this._activeVao = null;

    /**
     * Holds the current render target
     *
     * @member {Tiny.RenderTarget}
     */
    this._activeRenderTarget = null;

    this._initContext();

    /**
     * Manages the filters.
     *
     * @member {Tiny.FilterManager}
     */
    this.filterManager = new FilterManager(this);
    // map some webGL blend and drawmodes..
    this.drawModes = mapWebGLDrawModesToTiny(this.gl);

    this._nextTextureLocation = 0;

    this.setBlendMode(0);

    /**
     * Fired after rendering finishes.
     *
     * @event Tiny.WebGLRenderer#postrender
     */

    /**
     * Fired before rendering starts.
     *
     * @event Tiny.WebGLRenderer#prerender
     */

    /**
     * Fired when the WebGL context is set.
     *
     * @event Tiny.WebGLRenderer#context
     * @param {WebGLRenderingContext} gl - WebGL context.
     */
  }

  /**
   * Creates the WebGL context
   *
   * @private
   */
  _initContext() {
    const gl = this.gl;

    // restore a context if it was previously lost
    if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) {
      gl.getExtension('WEBGL_lose_context').restoreContext();
    }

    const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);

    this._activeShader = null;
    this._activeVao = null;

    this.boundTextures = new Array(maxTextures);
    this.emptyTextures = new Array(maxTextures);

    // create a texture manager...
    this.textureManager = new TextureManager(this);
    this.textureGC = new TextureGarbageCollector(this);

    this.state.resetToDefault();

    this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true);
    this.rootRenderTarget.clearColor = this._backgroundColorRgba;

    this.bindRenderTarget(this.rootRenderTarget);

    // now lets fill up the textures with empty ones!
    const emptyGLTexture = new glCore.GLTexture.fromData(gl, null, 1, 1); // eslint-disable-line

    const tempObj = {_glTextures: {}};

    tempObj._glTextures[this.CONTEXT_UID] = {};

    for (let i = 0; i < maxTextures; i++) {
      const empty = new BaseTexture();

      empty._glTextures[this.CONTEXT_UID] = emptyGLTexture;

      this.boundTextures[i] = tempObj;
      this.emptyTextures[i] = empty;
      this.bindTexture(null, i);
    }

    this.emit('context', gl);

    // setup the width/height properties and gl viewport
    this.resize(this.screen.width, this.screen.height);
  }

  /**
   * Renders the object to its webGL view
   *
   * @param {Tiny.DisplayObject} displayObject - the object to be rendered
   * @param {Tiny.RenderTexture} renderTexture - The render texture to render to.
   * @param {boolean} [clear] - Should the canvas be cleared before the new render
   * @param {Tiny.Transform} [transform] - A transform to apply to the render texture before rendering.
   * @param {boolean} [skipUpdateTransform] - Should we skip the update transform pass?
   */
  render(displayObject, renderTexture, clear, transform, skipUpdateTransform) {
    // can be handy to know!
    this.renderingToScreen = !renderTexture;

    this.emit('prerender');

    // no point rendering if our context has been blown up!
    if (!this.gl || this.gl.isContextLost()) {
      return;
    }

    this._nextTextureLocation = 0;

    if (!renderTexture) {
      this._lastObjectRendered = displayObject;
    }

    if (!skipUpdateTransform) {
      // update the scene graph
      const cacheParent = displayObject.parent;

      displayObject.parent = this._tempDisplayObjectParent;
      displayObject.updateTransform();
      displayObject.parent = cacheParent;
      // displayObject.hitArea = //TODO add a temp hit area
    }

    this.bindRenderTexture(renderTexture, transform);

    this.currentRenderer.start();

    if (clear !== undefined ? clear : this.clearBeforeRender) {
      this._activeRenderTarget.clear();
    }

    displayObject.renderWebGL(this);

    // apply transform..
    this.currentRenderer.flush();

    // this.setObjectRenderer(this.emptyRenderer);

    this.textureGC.update();

    this.emit('postrender');
  }

  /**
   * Changes the current renderer to the one given in parameter
   *
   * @param {Tiny.ObjectRenderer} objectRenderer - The object renderer to use.
   */
  setObjectRenderer(objectRenderer) {
    if (this.currentRenderer === objectRenderer) {
      return;
    }

    this.currentRenderer.stop();
    this.currentRenderer = objectRenderer;
    this.currentRenderer.start();
  }

  /**
   * This should be called if you wish to do some custom rendering
   * It will basically render anything that may be batched up such as sprites
   *
   */
  flush() {
    this.setObjectRenderer(this.emptyRenderer);
  }

  /**
   * Resizes the webGL view to the specified width and height.
   *
   * @param {number} screenWidth - the new width of the screen
   * @param {number} screenHeight - the new height of the screen
   */
  resize(screenWidth, screenHeight) {
    //  if(width * this.resolution === this.width && height * this.resolution === this.height)return;

    SystemRenderer.prototype.resize.call(this, screenWidth, screenHeight);

    this.rootRenderTarget.resize(screenWidth, screenHeight);

    if (this._activeRenderTarget === this.rootRenderTarget) {
      this.rootRenderTarget.activate();

      if (this._activeShader) {
        this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true);
      }
    }
  }

  /**
   * Resizes the webGL view to the specified width and height.
   *
   * @param {number} blendMode - the desired blend mode
   */
  setBlendMode(blendMode) {
    this.state.setBlendMode(blendMode);
  }

  /**
   * Erases the active render target and fills the drawing area with a colour
   *
   * @param {number} [clearColor] - The colour
   */
  clear(clearColor) {
    this._activeRenderTarget.clear(clearColor);
  }

  /**
   * Sets the transform of the active render target to the given matrix
   *
   * @param {Tiny.Matrix} matrix - The transformation matrix
   */
  setTransform(matrix) {
    this._activeRenderTarget.transform = matrix;
  }

  /**
   * Erases the render texture and fills the drawing area with a colour
   *
   * @param {Tiny.RenderTexture} renderTexture - The render texture to clear
   * @param {number} [clearColor] - The colour
   * @return {Tiny.WebGLRenderer} Returns itself.
   */
  clearRenderTexture(renderTexture, clearColor) {
    const baseTexture = renderTexture.baseTexture;
    const renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID];

    if (renderTarget) {
      renderTarget.clear(clearColor);
    }

    return this;
  }

  /**
   * Binds a render texture for rendering
   *
   * @param {Tiny.RenderTexture} renderTexture - The render texture to render
   * @param {Tiny.Transform} transform - The transform to be applied to the render texture
   * @return {Tiny.WebGLRenderer} Returns itself.
   */
  bindRenderTexture(renderTexture, transform) {
    let renderTarget;

    if (renderTexture) {
      const baseTexture = renderTexture.baseTexture;

      if (!baseTexture._glRenderTargets[this.CONTEXT_UID]) {
        // bind the current texture
        this.textureManager.updateTexture(baseTexture, 0);
      }

      this.unbindTexture(baseTexture);

      renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID];
      renderTarget.setFrame(renderTexture.frame);
    } else {
      renderTarget = this.rootRenderTarget;
    }

    renderTarget.transform = transform;
    this.bindRenderTarget(renderTarget);

    return this;
  }

  /**
   * Changes the current render target to the one given in parameter
   *
   * @param {Tiny.RenderTarget} renderTarget - the new render target
   * @return {Tiny.WebGLRenderer} Returns itself.
   */
  bindRenderTarget(renderTarget) {
    if (renderTarget !== this._activeRenderTarget) {
      this._activeRenderTarget = renderTarget;
      renderTarget.activate();

      if (this._activeShader) {
        this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true);
      }

      this.stencilManager.setMaskStack(renderTarget.stencilMaskStack);
    }

    return this;
  }

  /**
   * Changes the current shader to the one given in parameter
   *
   * @param {Tiny.Shader} shader - the new shader
   * @param {boolean} [autoProject=true] - Whether automatically set the projection matrix
   * @return {Tiny.WebGLRenderer} Returns itself.
   */
  bindShader(shader, autoProject) {
    // TODO cache
    if (this._activeShader !== shader) {
      this._activeShader = shader;
      shader.bind();

      // `autoProject` normally would be a default parameter set to true
      // but because of how Babel transpiles default parameters
      // it hinders the performance of this method.
      if (autoProject !== false) {
        // automatically set the projection matrix
        shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true);
      }
    }

    return this;
  }

  /**
   * Binds the texture. This will return the location of the bound texture.
   * It may not be the same as the one you pass in. This is due to optimisation that prevents
   * needless binding of textures. For example if the texture is already bound it will return the
   * current location of the texture instead of the one provided. To bypass this use force location
   *
   * @param {Tiny.Texture} texture - the new texture
   * @param {number} location - the suggested texture location
   * @param {boolean} forceLocation - force the location
   * @return {Tiny.WebGLRenderer} Returns itself.
   */
  bindTexture(texture, location, forceLocation) {
    texture = texture || this.emptyTextures[location];
    texture = texture.baseTexture || texture;
    texture.touched = this.textureGC.count;

    if (!forceLocation) {
      // TODO - maybe look into adding boundIds.. save us the loop?
      for (let i = 0; i < this.boundTextures.length; i++) {
        if (this.boundTextures[i] === texture) {
          return i;
        }
      }

      if (location === undefined) {
        this._nextTextureLocation++;
        this._nextTextureLocation %= this.boundTextures.length;
        location = this.boundTextures.length - this._nextTextureLocation - 1;
      }
    } else {
      location = location || 0;
    }

    const gl = this.gl;
    const glTexture = texture._glTextures[this.CONTEXT_UID];

    if (!glTexture) {
      // this will also bind the texture..
      this.textureManager.updateTexture(texture, location);
    } else {
      // bind the current texture
      this.boundTextures[location] = texture;
      gl.activeTexture(gl.TEXTURE0 + location);
      gl.bindTexture(gl.TEXTURE_2D, glTexture.texture);
    }

    return location;
  }

  /**
   * unbinds the texture ...
   *
   * @param {Tiny.Texture} texture - the texture to unbind
   * @return {Tiny.WebGLRenderer} Returns itself.
   */
  unbindTexture(texture) {
    const gl = this.gl;

    texture = texture.baseTexture || texture;

    for (let i = 0; i < this.boundTextures.length; i++) {
      if (this.boundTextures[i] === texture) {
        this.boundTextures[i] = this.emptyTextures[i];

        gl.activeTexture(gl.TEXTURE0 + i);
        gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[i]._glTextures[this.CONTEXT_UID].texture);
      }
    }

    return this;
  }

  /**
   * Creates a new VAO from this renderer's context and state.
   *
   * @return {VertexArrayObject} The new VAO.
   */
  createVao() {
    return new glCore.VertexArrayObject(this.gl, this.state.attribState);
  }

  /**
   * Changes the current Vao to the one given in parameter
   *
   * @param {Tiny.VertexArrayObject} vao - the new Vao
   * @return {Tiny.WebGLRenderer} Returns itself.
   */
  bindVao(vao) {
    if (this._activeVao === vao) {
      return this;
    }

    if (vao) {
      vao.bind();
    } else if (this._activeVao) {
      // TODO this should always be true i think?
      this._activeVao.unbind();
    }

    this._activeVao = vao;

    return this;
  }

  /**
   * Resets the WebGL state so you can render things however you fancy!
   *
   * @return {Tiny.WebGLRenderer} Returns itself.
   */
  reset() {
    this.setObjectRenderer(this.emptyRenderer);

    this._activeShader = null;
    this._activeRenderTarget = this.rootRenderTarget;

    // bind the main frame buffer (the screen);
    this.rootRenderTarget.activate();

    this.state.resetToDefault();

    return this;
  }

  /**
   * Handles a lost webgl context
   *
   * @private
   * @param {WebGLContextEvent} event - The context lost event.
   */
  handleContextLost(event) {
    event.preventDefault();
  }

  /**
   * Handles a restored webgl context
   *
   * @private
   */
  handleContextRestored() {
    this.textureManager.removeAll();
    this._initContext();
  }

  /**
   * Removes everything from the renderer (event listeners, spritebatch, etc...)
   *
   * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM.
   *  See: https://github.com/pixijs/pixi.js/issues/2233
   */
  destroy(removeView) {
    this.destroyPlugins();

    // remove listeners
    this.view.removeEventListener('webglcontextlost', this.handleContextLost);
    this.view.removeEventListener('webglcontextrestored', this.handleContextRestored);

    this.textureManager.destroy();

    // call base destroy
    super.destroy(removeView);

    this.uid = 0;

    // destroy the managers
    this.maskManager.destroy();
    this.stencilManager.destroy();
    this.filterManager.destroy();

    this.maskManager = null;
    this.filterManager = null;
    this.textureManager = null;
    this.currentRenderer = null;

    this.handleContextLost = null;
    this.handleContextRestored = null;

    this._contextOptions = null;
    this.gl.useProgram(null);

    if (this.gl.getExtension('WEBGL_lose_context')) {
      this.gl.getExtension('WEBGL_lose_context').loseContext();
    }

    this.gl = null;

    // this = null;
  }
}

/**
 * Collection of installed plugins. These are included by default in Tiny, but can be excluded
 * by creating a custom build. Consult the README for more information about creating custom
 * builds and excluding plugins.
 * @name Tiny.WebGLRenderer#plugins
 * @type {object}
 * @readonly
 * @property {Tiny.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements.
 * @property {Tiny.extract.WebGLExtract} extract Extract image data from renderer.
 * @property {Tiny.interaction.InteractionManager} interaction Handles mouse, touch and pointer events.
 * @property {Tiny.prepare.WebGLPrepare} prepare Pre-render display objects.
 */

/**
 * Adds a plugin to the renderer.
 *
 * @method Tiny.WebGLRenderer#registerPlugin
 * @param {string} pluginName - The name of the plugin.
 * @param {Function} ctor - The constructor function or class for the plugin.
 */

pluginTarget.mixin(WebGLRenderer);
Documentation generated by JSDoc 3.4.3 on Thu May 31 2018 14:40:21 GMT+0800 (CST)