Source: scene/immediate.js

Object.assign(pc.Application.prototype, function () {

    var tempGraphNode = new pc.GraphNode();
    var identityGraphNode = new pc.GraphNode();
    var meshInstanceArray = [];

    var _deprecationWarning = false;


    var ImmediateData = function (device) {
        this.lineVertexFormat = new pc.VertexFormat(device, [
            { semantic: pc.SEMANTIC_POSITION, components: 3, type: pc.TYPE_FLOAT32 },
            { semantic: pc.SEMANTIC_COLOR, components: 4, type: pc.TYPE_UINT8, normalize: true }
        ]);
        this.lineBatches = [];
        this.layers = [];
        this.layerToBatch = {};
        this.quadMesh = null;
        this.cubeLocalPos = null;
        this.cubeWorldPos = null;
        this.identityGraphNode = new pc.GraphNode();
    };

    ImmediateData.prototype.addLayer = function (layer) {
        if (this.layers.indexOf(layer) < 0) {
            this.layers.push(layer);
        }
    };

    ImmediateData.prototype.getLayerIdx = function (layer) {
        return this.layerToBatch[layer.id];
    };

    ImmediateData.prototype.addLayerIdx = function (idx, layer) {
        this.layerToBatch[layer.id] = idx;
    };

    var LineBatch = function () {
        // Sensible default value; buffers will be doubled and reallocated when it's not enough
        this.numLinesAllocated = 128;

        this.vb = null;
        this.vbRam = null;
        this.mesh = null;
        this.linesUsed = 0;
        this.material = null;
        this.meshInstance = null;

        this.layer = null;
    };

    Object.assign(LineBatch.prototype, {
        init: function (device, vertexFormat, layer, linesToAdd) {
            // Allocate basic stuff once per batch
            if (!this.mesh) {
                this.mesh = new pc.Mesh();
                this.mesh.primitive[0].type = pc.PRIMITIVE_LINES;
                this.mesh.primitive[0].base = 0;
                this.mesh.primitive[0].indexed = false;

                this.material = new pc.BasicMaterial();
                this.material.vertexColors = true;
                this.material.blend = true;
                this.material.blendType = pc.BLEND_NORMAL;
                this.material.update();
            }

            this.layer = layer;

            // Increase buffer size, if it's not enough
            while ((this.linesUsed + linesToAdd) > this.numLinesAllocated) {
                if (this.vb) {
                    this.vb.destroy();
                    this.vb = null;
                }
                this.numLinesAllocated *= 2;
            }

            this.vertexFormat = vertexFormat;

            // (Re)allocate line buffer
            if (!this.vb) {
                this.vb = new pc.VertexBuffer(device, vertexFormat, this.numLinesAllocated * 2, pc.BUFFER_DYNAMIC);
                this.mesh.vertexBuffer = this.vb;
                this.vbRam = new DataView(this.vb.lock());

                if (!this.meshInstance) {
                    identityGraphNode.worldTransform = pc.Mat4.IDENTITY;
                    identityGraphNode._dirtyWorld = identityGraphNode._dirtyNormal = false;
                    this.meshInstance = new pc.MeshInstance(identityGraphNode, this.mesh, this.material);
                    this.meshInstance.cull = false;
                }
            }
        },

        addLines: function (position, color) {
            // Append lines to buffer
            var multiColor = !!color.length;
            var offset = this.linesUsed * 2 * this.vertexFormat.size;
            var clr;
            for (var i = 0; i < position.length; i++) {
                this.vbRam.setFloat32(offset, position[i].x, true); offset += 4;
                this.vbRam.setFloat32(offset, position[i].y, true); offset += 4;
                this.vbRam.setFloat32(offset, position[i].z, true); offset += 4;
                clr = multiColor ? color[i] : color;
                this.vbRam.setUint8(offset, clr.r * 255); offset += 1;
                this.vbRam.setUint8(offset, clr.g * 255); offset += 1;
                this.vbRam.setUint8(offset, clr.b * 255); offset += 1;
                this.vbRam.setUint8(offset, clr.a * 255); offset += 1;
            }
            this.linesUsed += position.length / 2;
        },

        finalize: function () {
            // Update batch vertex buffer/issue drawcall if there are any lines
            if (this.linesUsed > 0) {
                this.vb.setData(this.vbRam.buffer);
                this.mesh.primitive[0].count = this.linesUsed * 2;
                meshInstanceArray[0] = this.meshInstance;
                this.layer.addMeshInstances(meshInstanceArray, true);
                this.linesUsed = 0;
            }
        }
    });

    function _initImmediate() {
        // Init global line drawing data once
        if (!this._immediateData) {
            this._immediateData = new ImmediateData(this.graphicsDevice);

            this.on('prerender', this._preRenderImmediate, this);
            this.on('postrender', this._postRenderImmediate, this);
        }
    }

    function _addLines(position, color, options) {
        if (options.layer === undefined) options.layer = this.scene.layers.getLayerById(pc.LAYERID_IMMEDIATE);
        if (options.depthTest === undefined) options.depthTest = true;

        this._initImmediate();

        var layer = options.layer;
        this._immediateData.addLayer(layer);

        var idx = this._immediateData.getLayerIdx(layer);
        if (idx === undefined) {
            // Init used batch once
            var batch = new LineBatch();
            batch.init(this.graphicsDevice, this._immediateData.lineVertexFormat, layer, position.length / 2);
            batch.material.depthTest = options.depthTest;
            if (options.mask) batch.meshInstance.mask = options.mask;

            idx = this._immediateData.lineBatches.push(batch) - 1; // push into list and get index
            this._immediateData.addLayerIdx(idx, layer);
        } else {
            // Possibly reallocate buffer if it's small
            this._immediateData.lineBatches[idx].init(this.graphicsDevice, this._immediateData.lineVertexFormat, layer, position.length / 2);
            this._immediateData.lineBatches[idx].material.depthTest = options.depthTest;
            if (options.mask) this._immediateData.lineBatches[idx].meshInstance.mask = options.mask;
        }
        // Append
        this._immediateData.lineBatches[idx].addLines(position, color);
    }

    /**
     * @function
     * @name pc.Application#renderLine
     * @description Renders a line. Line start and end coordinates are specified in
     * world-space. If a single color is supplied, the line will be flat-shaded with
     * that color. If two colors are supplied, the line will be smooth shaded between
     * those colors. It is also possible to control which scene layer the line is
     * rendered into. By default, lines are rendered into the immediate layer
     * {@link pc.LAYERID_IMMEDIATE}.
     * @param {pc.Vec3} start - The start world-space coordinate of the line.
     * @param {pc.Vec3} end - The end world-space coordinate of the line.
     * @param {pc.Color} color - The start color of the line.
     * @param {pc.Color} [endColor] - The end color of the line.
     * @param {Object} [options] - Options to set rendering properties
     * @param {pc.Layer} [options.layer] - The layer to render the line into. Defaults
     * to {@link pc.LAYERID_IMMEDIATE}.
     * @example
     * // Render a 1-unit long white line
     * var start = new pc.Vec3(0, 0, 0);
     * var end = new pc.Vec3(1, 0, 0);
     * var color = new pc.Color(1, 1, 1);
     * app.renderLine(start, end, color);
     * @example
     * // Render a 1-unit long line that is smooth-shaded from white to red
     * var start = new pc.Vec3(0, 0, 0);
     * var end = new pc.Vec3(1, 0, 0);
     * var startColor = new pc.Color(1, 1, 1);
     * var endColor = new pc.Color(1, 0, 0);
     * app.renderLine(start, end, startColor, endColor);
     * @example
     * // Render a 1-unit long white line into the world layer
     * var start = new pc.Vec3(0, 0, 0);
     * var end = new pc.Vec3(1, 0, 0);
     * var color = new pc.Color(1, 1, 1);
     * var worldLayer = app.scene.layers.getLayerById(pc.LAYERID_WORLD);
     * app.renderLine(start, end, color, {
     *     layer: worldLayer
     * });
     * @example
     * // Render a 1-unit long line that is smooth-shaded from white to red into the world layer
     * var start = new pc.Vec3(0, 0, 0);
     * var end = new pc.Vec3(1, 0, 0);
     * var startColor = new pc.Color(1, 1, 1);
     * var endColor = new pc.Color(1, 0, 0);
     * var worldLayer = app.scene.layers.getLayerById(pc.LAYERID_WORLD);
     * app.renderLine(start, end, color, {
     *     layer: worldLayer
     * });
     */
    function renderLine(start, end, color) {
        var endColor = color;
        var options;

        var arg3 = arguments[3];
        var arg4 = arguments[4];

        if (arg3 instanceof pc.Color) {
            // passed in end color
            endColor = arg3;

            if (typeof arg4 === 'number') {
                if (!_deprecationWarning) {
                    console.warn("lineBatch argument is deprecated for renderLine. Use options.layer instead");
                    _deprecationWarning = true;
                }
                // compatibility: convert linebatch id into options
                if (arg4 === pc.LINEBATCH_OVERLAY) {
                    options = {
                        layer: this.scene.layers.getLayerById(pc.LAYERID_IMMEDIATE),
                        depthTest: false
                    };
                } else {
                    options = {
                        layer: this.scene.layers.getLayerById(pc.LAYERID_IMMEDIATE),
                        depthTest: true
                    };
                }
            } else {
                // use passed in options
                options = arg4;
            }
        } else if (typeof arg3 === 'number') {
            if (!_deprecationWarning) {
                console.warn("lineBatch argument is deprecated for renderLine. Use options.layer instead");
                _deprecationWarning = true;
            }

            endColor = color;

            // compatibility: convert linebatch id into options
            if (arg3 === pc.LINEBATCH_OVERLAY) {
                options = {
                    layer: this.scene.layers.getLayerById(pc.LAYERID_IMMEDIATE),
                    depthTest: false
                };
            } else {
                options = {
                    layer: this.scene.layers.getLayerById(pc.LAYERID_IMMEDIATE),
                    depthTest: true
                };
            }
        } else if (arg3) {
            // options passed in
            options = arg3;
        } else {
            // no arg3, use default options
            options = {
                layer: this.scene.layers.getLayerById(pc.LAYERID_IMMEDIATE),
                depthTest: true
            };
        }

        this._addLines([start, end], [color, endColor], options);
    }

    /**
     * @function
     * @name pc.Application#renderLines
     * @description Draw an array of lines.
     * @param {pc.Vec3[]} position An array of points to draw lines between
     * @param {pc.Color[]} color An array of colors to color the lines. This must be the same size as the position array
     * @param {Object} [options] Options to set rendering properties
     * @param {pc.Layer} [options.layer] The layer to render the line into
     * @example
     * var points = [new pc.Vec3(0,0,0), new pc.Vec3(1,0,0), new pc.Vec3(1,1,0), new pc.Vec3(1,1,1)];
     * var colors = [new pc.Color(1,0,0), new pc.Color(1,1,0), new pc.Color(0,1,1), new pc.Color(0,0,1)];
     * app.renderLines(points, colors);
     */
    function renderLines(position, color, options) {
        if (!options) {
            // default option
            options = {
                layer: this.scene.layers.getLayerById(pc.LAYERID_IMMEDIATE),
                depthTest: true
            };
        } else if (typeof options === 'number') {
            if (!_deprecationWarning) {
                console.warn("lineBatch argument is deprecated for renderLine. Use options.layer instead");
                _deprecationWarning = true;
            }

            // backwards compatibility, LINEBATCH_OVERLAY lines have depthtest disabled
            if (options === pc.LINEBATCH_OVERLAY) {
                options = {
                    layer: this.scene.layers.getLayerById(pc.LAYERID_IMMEDIATE),
                    depthTest: false
                };
            } else {
                options = {
                    layer: this.scene.layers.getLayerById(pc.LAYERID_IMMEDIATE),
                    depthTest: true
                };
            }
        }

        var multiColor = !!color.length;
        if (multiColor) {
            if (position.length !== color.length) {
                pc.log.error("renderLines: position/color arrays have different lengths");
                return;
            }
        }
        if (position.length % 2 !== 0) {
            pc.log.error("renderLines: array length is not divisible by 2");
            return;
        }
        this._addLines(position, color, options);
    }

    // Draw lines forming a transformed unit-sized cube at this frame
    // lineType is optional
    function renderWireCube(matrix, color, options) {
        // if (lineType===undefined) lineType = pc.LINEBATCH_WORLD;

        var i;

        this._initImmediate();

        // Init cube data once
        if (!this._immediateData.cubeLocalPos) {
            var x = 0.5;
            this._immediateData.cubeLocalPos = [new pc.Vec3(-x, -x, -x), new pc.Vec3(-x, x, -x), new pc.Vec3(x, x, -x), new pc.Vec3(x, -x, -x),
                new pc.Vec3(-x, -x, x), new pc.Vec3(-x, x, x), new pc.Vec3(x, x, x), new pc.Vec3(x, -x, x)];
            this._immediateData.cubeWorldPos = [new pc.Vec3(), new pc.Vec3(), new pc.Vec3(), new pc.Vec3(),
                new pc.Vec3(), new pc.Vec3(), new pc.Vec3(), new pc.Vec3()];
        }

        var cubeLocalPos = this._immediateData.cubeLocalPos;
        var cubeWorldPos = this._immediateData.cubeWorldPos;

        // Transform and append lines
        for (i = 0; i < 8; i++) {
            matrix.transformPoint(cubeLocalPos[i], cubeWorldPos[i]);
        }
        this.renderLines([
            cubeWorldPos[0], cubeWorldPos[1],
            cubeWorldPos[1], cubeWorldPos[2],
            cubeWorldPos[2], cubeWorldPos[3],
            cubeWorldPos[3], cubeWorldPos[0],

            cubeWorldPos[4], cubeWorldPos[5],
            cubeWorldPos[5], cubeWorldPos[6],
            cubeWorldPos[6], cubeWorldPos[7],
            cubeWorldPos[7], cubeWorldPos[4],

            cubeWorldPos[0], cubeWorldPos[4],
            cubeWorldPos[1], cubeWorldPos[5],
            cubeWorldPos[2], cubeWorldPos[6],
            cubeWorldPos[3], cubeWorldPos[7]
        ], color, options);
    }

    function _preRenderImmediate() {
        for (var i = 0; i < this._immediateData.lineBatches.length; i++) {
            if (this._immediateData.lineBatches[i]) {
                this._immediateData.lineBatches[i].finalize();
            }
        }
    }

    function _postRenderImmediate() {
        for (var i = 0; i < this._immediateData.layers.length; i++) {
            this._immediateData.layers[i].clearMeshInstances(true);
        }

        this._immediateData.layers.length = 0;
    }

    // Draw meshInstance at this frame
    function renderMeshInstance(meshInstance, options) {
        if (!options) {
            options = {
                layer: this.scene.layers.getLayerById(pc.LAYERID_IMMEDIATE)
            };
        }

        this._initImmediate();

        this._immediateData.addLayer(options.layer);

        meshInstanceArray[0] = meshInstance;
        options.layer.addMeshInstances(meshInstanceArray, true);
    }

    // Draw mesh at this frame
    function renderMesh(mesh, material, matrix, options) {
        if (!options) {
            options = {
                layer: this.scene.layers.getLayerById(pc.LAYERID_IMMEDIATE)
            };
        }

        this._initImmediate();
        tempGraphNode.worldTransform = matrix;
        tempGraphNode._dirtyWorld = tempGraphNode._dirtyNormal = false;

        var instance = new pc.MeshInstance(tempGraphNode, mesh, material);
        instance.cull = false;

        if (options.mask) instance.mask = options.mask;
        this._immediateData.addLayer(options.layer);

        meshInstanceArray[0] = instance;
        options.layer.addMeshInstances(meshInstanceArray, true);
    }

    // Draw quad of size [-0.5, 0.5] at this frame
    function renderQuad(matrix, material, options) {
        if (!options) {
            options = {
                layer: this.scene.layers.getLayerById(pc.LAYERID_IMMEDIATE)
            };
        }

        this._initImmediate();

        // Init quad data once
        if (!this._immediateData.quadMesh) {
            var format = new pc.VertexFormat(this.graphicsDevice, [
                { semantic: pc.SEMANTIC_POSITION, components: 3, type: pc.TYPE_FLOAT32 }
            ]);
            var quadVb = new pc.VertexBuffer(this.graphicsDevice, format, 4);
            var iterator = new pc.VertexIterator(quadVb);
            iterator.element[pc.SEMANTIC_POSITION].set(-0.5, -0.5, 0);
            iterator.next();
            iterator.element[pc.SEMANTIC_POSITION].set(0.5, -0.5, 0);
            iterator.next();
            iterator.element[pc.SEMANTIC_POSITION].set(-0.5, 0.5, 0);
            iterator.next();
            iterator.element[pc.SEMANTIC_POSITION].set(0.5, 0.5, 0);
            iterator.end();
            this._immediateData.quadMesh = new pc.Mesh();
            this._immediateData.quadMesh.vertexBuffer = quadVb;
            this._immediateData.quadMesh.primitive[0].type = pc.PRIMITIVE_TRISTRIP;
            this._immediateData.quadMesh.primitive[0].base = 0;
            this._immediateData.quadMesh.primitive[0].count = 4;
            this._immediateData.quadMesh.primitive[0].indexed = false;
        }

        // Issue quad drawcall
        tempGraphNode.worldTransform = matrix;
        tempGraphNode._dirtyWorld = tempGraphNode._dirtyNormal = false;

        var quad = new pc.MeshInstance(tempGraphNode, this._immediateData.quadMesh, material);
        quad.cull = false;
        meshInstanceArray[0] = quad;

        this._immediateData.addLayer(options.layer);

        options.layer.addMeshInstances(meshInstanceArray, true);
    }

    return {
        renderMeshInstance: renderMeshInstance,
        renderMesh: renderMesh,
        renderLine: renderLine,
        renderLines: renderLines,
        renderQuad: renderQuad,
        renderWireCube: renderWireCube,
        _addLines: _addLines,
        _initImmediate: _initImmediate,
        _preRenderImmediate: _preRenderImmediate,
        _postRenderImmediate: _postRenderImmediate
    };
}());