Newer
Older
pixi.js / src / core / renderers / webgl / WebGLRenderer.js
@Mat Groves Mat Groves on 23 Mar 2017 9 KB Rename it all to systems!
import SystemRenderer from '../SystemRenderer';
import MaskSystem from './systems/MaskSystem';
import StencilSystem from './systems/StencilSystem';
import FilterSystem from './systems/FilterSystem';
import FramebufferSystem from './systems/FramebufferSystem';
import RenderTextureSystem from './systems/RenderTextureSystem';
import NewTextureSystem from './systems/NewTextureSystem';
import TextureSystem from './TextureManager';
import ProjectionSystem from './systems/ProjectionSystem';
import StateSystem from './systems/StateSystem';
import GeometrySystem from './systems/GeometrySystem';
import ShaderSystem from './systems/ShaderSystem';
import ContextSystem from './systems/ContextSystem';
import BatchSystem from './systems/BatchSystem';
import TextureGCSystem from './systems/TextureGCSystem';
import { pluginTarget } from '../../utils';
import glCore from 'pixi-gl-core';
import { RENDERER_TYPE } from '../../const';
import UniformGroup from '../../shader/UniformGroup';
import { Rectangle, Matrix } from '../../math';
import Runner from 'mini-runner';

/**
 * 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 PIXI
 * @extends PIXI.SystemRenderer
 */
export default class WebGLRenderer extends SystemRenderer
{
    /**
     *
     * @param {number} [screenWidth=800] - the width of the screen
     * @param {number} [screenHeight=600] - 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.transparent=false] - If the render view is transparent, default false
     * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false
     * @param {boolean} [options.antialias=false] - sets antialias. If not available natively then FXAA
     *  antialiasing is used
     * @param {boolean} [options.forceFXAA=false] - forces FXAA antialiasing to be used over native.
     *  FXAA is faster, but may not always look as great
     * @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.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.preserveDrawingBuffer=false] - enables drawing buffer preservation,
     *  enable this if you need to call toDataUrl on the webgl context.
     * @param {boolean} [options.roundPixels=false] - If true Pixi will Math.floor() x/y values when
     *  rendering, stopping pixel interpolation.
     * @param {boolean} [options.legacy=false] - If true Pixi will aim to ensure compatibility
     * with older / less advanced devices. If you experiance unexplained flickering try setting this to true.
     */
    constructor(screenWidth, screenHeight, options = {})
    {
        super('WebGL', screenWidth, screenHeight, options);

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

         // this will be set by the contextSystem (this.context)
        this.gl = null;
        this.CONTEXT_UID = 0;
        this.legacy = !!options.legacy;

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

        // runners!
        this.runners = {
            destroy:        new Runner('destroy'),
            contextChange:  new Runner('contextChange', 1),
            reset:          new Runner('reset'),
            update:         new Runner('update'),
            postrender:     new Runner('postrender'),
            prerender:      new Runner('prerender'),
            resize:         new Runner('resize', 2),
        };


        /**
         * The options passed in to create a new webgl context.
         *
         * @member {object}
         * @private
         */
        this._backgroundColorRgba[3] = this.transparent ? 0 : 1;

        this.globalUniforms = new UniformGroup({
            projectionMatrix:new Matrix()
        }, true)

        this.addSystem(MaskSystem)
        .addSystem(ContextSystem)
        .addSystem(StateSystem)
        .addSystem(ShaderSystem)
        .addSystem(NewTextureSystem, 'texture')
        .addSystem(GeometrySystem)
        .addSystem(FramebufferSystem)
        .addSystem(StencilSystem)
        .addSystem(ProjectionSystem)
        .addSystem(TextureGCSystem)
        .addSystem(FilterSystem)
        .addSystem(RenderTextureSystem)
        .addSystem(BatchSystem)

        this.initPlugins();

        if(options.context)
        {
            this.context.initFromContext(options.context);
        }
        else
        {
            this.context.initFromOptions({
                alpha: this.transparent,
                antialias: options.antialias,
                premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied',
                stencil: true,
                preserveDrawingBuffer: options.preserveDrawingBuffer,
            });
        }

        this.renderingToScreen = true;

        this._nextTextureLocation = 0;

        this._initContext();
    }

    addSystem(_class, name)
    {
        if(!name)
        {
            name = _class.name;
        }

        //TODO - read name from class.name..
        if(name.includes('System'))
        {
            name = name.replace('System', '');
            name = name.charAt(0).toLowerCase() + name.slice(1);
        }

        const system = new _class(this);

        if(this[name])
        {
            throw new Error('Whoops! ' + name + ' is already a manger');
            return;
        }

        this[name] = system;

        for(var i in this.runners)
        {
            this.runners[i].add(system);
        }

        return this;
    }

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

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

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

        const tempObj = { _glTextures: {} };

        for (let i = 0; i < maxTextures; i++)
        {
            this.boundTextures[i] = tempObj;
        }

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

    /**
     * Renders the object to its webGL view
     *
     * @param {PIXI.DisplayObject} displayObject - the object to be rendered
     * @param {PIXI.RenderTexture} renderTexture - The render texture to render to.
     * @param {boolean} [clear] - Should the canvas be cleared before the new render
     * @param {PIXI.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.runners.prerender.run();
        this.emit('prerender');

        // no point rendering if our context has been blown up!
        if(this.context.isLost)
        {
            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.renderTexture.bind(renderTexture);
        this.batch.currentRenderer.start();

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

        displayObject.renderWebGL(this);

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

        this.runners.postrender.run();
        this.emit('postrender');
    }

    /**
     * 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)
    {
        SystemRenderer.prototype.resize.call(this, screenWidth, screenHeight);
        this.runners.resize.run(screenWidth, screenHeight);
    }

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

    /**
     * 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)
    {
        // call base destroy
        super.destroy(removeView);

        this.destroyPlugins();
        this.runners.destroy.run();

        // TODO nullify all the managers..
        this.gl = null;
    }
}

pluginTarget.mixin(WebGLRenderer);