简体   繁体   中英

Why does my WebGl framerate slowly drop in Chrome?

In my WebGl program framerate starts high, then slowly decreases, memory usage increases equivalently over time. Framerate does not drop infinitely, but stays consistent at some point. The problem is negligible in IE & FF, but in Chrome my framerate drops from 30 to 10.

I narrowed down the problem: It is caused by a function creating data that is being drawn (in the original program). For testing purpose i created a function that writes test data into a global variable. The test data is NOT being used in any way. The fps decrease still occures.

var testData;

function createTestData() {
    testData = [];
    for (var i = 0; i < 5000; i++) {
        testData[i] = [];
        for (var j = 0; j < 1000; j++) {
            testData[i][j] = 1;
        }
    }
}

If i comment out the one call to that function, everything works as intended without framerate drops.

Calling createTestData() during runtime causes fps to go up like at the start and then slowly down again.

Chrome memory usage for the process of my program is less than 200mb, nowhere close to where Chrome should have problems.

I used Win 7 & 8, Chrome 35 & 36 and multiple pc-setups.

It feels like a Chrome bug, but i can't find other people having this issue, so it's probably some dumb mistake on my side.

Full Code of a strongly simplified version:

index.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>WebGl Framedrop Test</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

        <script type="text/javascript" src="sylvester.js"></script>
        <script type="text/javascript" src="glpsutilskap8.js"></script>
        <script type="text/javascript" src="JS_Main.js"></script>       

        <script id="shader-vs" type="x-shader/x-vertex">
            attribute vec3 aVertexPosition;
            uniform mat4 mvpMatrix;

            void main(void)
            {
                gl_Position = mvpMatrix * vec4(aVertexPosition, 1.0);   
            }
        </script>

        <script id="shader-fs" type="x-shader/x-fragment">          
            void main(void)
            {           
            }
        </script>       

        <style type="text/css">
            body {
                margin-left: 0px;
                margin-top: 0px;
            }

            canvas {
                margin-left: 0px;
                margin-top: 0px;
            }

        </style>
    </head>
        <body onload="Main()">
            <table>         
                <td style="vertical-align: top;">
                    <canvas id="WebGL-canvas" style="border: none;" width="800" height="600" ></canvas> 
                    <input type="button" style="margin-left:5px;" value="call createTestData() again" onclick="createTestData()" />
                    <p>current fps:</p>
                    <p id="fpsDisplay" style="color:red">default</p>

                    <p>fps last min:</p>
                    <p id="fpsMinuteDisplay" style="color:red">default</p>
            </table>
        </body>
</html>

JS_Main.js

var testData;

function createTestData() {
    testData = [];
    for (var i = 0; i < 5000; i++) {
        testData[i] = [];
        for (var j = 0; j < 1000; j++) {
            testData[i][j] = 1;
        }
    }
}

var fZnear = 0.1;
var fZfar = 3000;
var g_fOpenViewAngle = 45;

var gl;
var shaderProgram;

var mMatrix;
var vMatrix;
var pMatrix;
var mvMatrix;
var mvpMatrix;

var requestAnimationFrame = window.requestAnimationFrame
        || window.mozRequestAnimationFrame
        || window.webkitRequestAnimationFrame
        || window.msRequestAnimationFrame
        || window.oRequestAnimationFrame
        ;

var wall = {};
wall.vertices = new Array();
wall.triangles = new Array();

var g_nCanvasWidth = 800;
var g_nCanvasHeight = 600;

var fpsCounter;

function Main() {
    initGL();
    initShaders();
    gl.clearColor(0.5, 0.75, 1.0, 1.0);
    initBuffers();

    createTestData();

    fpsCounter = new FpsCounter();
    setInterval(setIntervalLoop, 0);
}

function drawScene() {
    fpsCounter.update();
    document.getElementById('fpsDisplay').innerHTML = fpsCounter.getCountPerSecond();
    document.getElementById('fpsMinuteDisplay').innerHTML = fpsCounter.getCountPerMinute();

    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    pMatrix = createPerspectiveMatrix(g_fOpenViewAngle, g_nCanvasWidth / g_nCanvasHeight, fZnear, fZfar);
    vMatrix = translationMatrix(-100, -100, -100);

    drawWall();
}

function drawWall() {
    gl.bindBuffer(gl.ARRAY_BUFFER, wall.vertexPositionBufferID);
    gl.vertexAttribPointer(vertexPositionAttribute, wall.vertexPositionBufferID.itemSize, gl.FLOAT, false, 0, 0);

    for (var i = 0; i < 1000; i++) {
        mMatrix = translationMatrix(Math.random() * 200, Math.random() * 200, Math.random() * 200).x(Matrix.I(4));
        mvMatrix = vMatrix.x(mMatrix);
        mvpMatrix = pMatrix.x(mvMatrix);

        setMatrixUniforms();
        gl.drawArrays(gl.TRIANGLES, 0, wall.vertexPositionBufferID.numItems);
    }
}

function translationMatrix(x, y, z) {
    var ret = Matrix.I(4);

    ret.elements[0][3] = x;
    ret.elements[1][3] = y;
    ret.elements[2][3] = z;

    return ret;
}
;

function setIntervalLoop() {
    drawScene();
}

function initBuffers() {
    wall.vertexPositionBufferID = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, wall.vertexPositionBufferID);

    wall.vertices[0] = Vector.create([1.0, -0.25, 1.0]);
    wall.vertices[1] = Vector.create([-1.0, -0.25, 1.0]);
    wall.vertices[2] = Vector.create([-1.0, 0.25, 1.0]);
    wall.vertices[3] = Vector.create([1.0, 0.25, 1.0]);

    wall.vertices[4] = Vector.create([1.0, -0.25, -1.0]);
    wall.vertices[5] = Vector.create([-1.0, -0.25, -1.0]);
    wall.vertices[6] = Vector.create([-1.0, 0.25, -1.0]);
    wall.vertices[7] = Vector.create([1.0, 0.25, -1.0]);

    wall.triangles[0] = new IndexedVertexTriangle(wall.vertices, 4, 0, 1);
    wall.triangles[1] = new IndexedVertexTriangle(wall.vertices, 4, 1, 5);
    wall.triangles[2] = new IndexedVertexTriangle(wall.vertices, 3, 7, 6);
    wall.triangles[3] = new IndexedVertexTriangle(wall.vertices, 3, 6, 2);
    wall.triangles[4] = new IndexedVertexTriangle(wall.vertices, 7, 3, 0);
    wall.triangles[5] = new IndexedVertexTriangle(wall.vertices, 7, 0, 4);
    wall.triangles[6] = new IndexedVertexTriangle(wall.vertices, 5, 1, 2);
    wall.triangles[7] = new IndexedVertexTriangle(wall.vertices, 5, 2, 6);
    wall.triangles[8] = new IndexedVertexTriangle(wall.vertices, 0, 3, 2);
    wall.triangles[9] = new IndexedVertexTriangle(wall.vertices, 0, 2, 1);
    wall.triangles[10] = new IndexedVertexTriangle(wall.vertices, 7, 4, 5);
    wall.triangles[11] = new IndexedVertexTriangle(wall.vertices, 7, 5, 6);

    var vertices = [];

    for (var i = 0; i < wall.triangles.length; i++) {
        vertices[9 * i] = wall.vertices[wall.triangles[i].index1].elements[0];
        vertices[9 * i + 1] = wall.vertices[wall.triangles[i].index1].elements[1];
        vertices[9 * i + 2] = wall.vertices[wall.triangles[i].index1].elements[2];
        vertices[9 * i + 3] = wall.vertices[wall.triangles[i].index2].elements[0];
        vertices[9 * i + 4] = wall.vertices[wall.triangles[i].index2].elements[1];
        vertices[9 * i + 5] = wall.vertices[wall.triangles[i].index2].elements[2];
        vertices[9 * i + 6] = wall.vertices[wall.triangles[i].index3].elements[0];
        vertices[9 * i + 7] = wall.vertices[wall.triangles[i].index3].elements[1];
        vertices[9 * i + 8] = wall.vertices[wall.triangles[i].index3].elements[2];
    }

    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);

    wall.vertexPositionBufferID.itemSize = 3;
    wall.vertexPositionBufferID.numItems = wall.triangles.length * 3;
}

function FpsCounter() {
    this.count = 0;
    this.fps = 0;
    this.prevSecond;
    this.minuteBuffer = new OverrideRingBuffer(60);
}

FpsCounter.prototype.update = function() {
    if (!this.prevSecond) {
        this.prevSecond = new Date().getTime();
        this.count = 1;
    }
    else {
        var currentTime = new Date().getTime();
        var difference = currentTime - this.prevSecond;
        if (difference > 1000) {
            this.prevSecond = currentTime;
            this.fps = this.count;
            this.minuteBuffer.push(this.count);
            this.count = 0;
        }
        else {
            this.count++;
        }
    }
};

FpsCounter.prototype.getCountPerMinute = function() {
    return this.minuteBuffer.getAverage();
};

FpsCounter.prototype.getCountPerSecond = function() {
    return this.fps;
};

function OverrideRingBuffer(size) {
    this.size = size;
    this.head = 0;
    this.buffer = new Array();
}
;

OverrideRingBuffer.prototype.push = function(value) {
    if (this.head >= this.size)
        this.head -= this.size;
    this.buffer[this.head] = value;
    this.head++;
};

OverrideRingBuffer.prototype.getAverage = function() {
    if (this.buffer.length === 0)
        return 0;

    var sum = 0;

    for (var i = 0; i < this.buffer.length; i++) {
        sum += this.buffer[i];
    }

    return (sum / this.buffer.length).toFixed(1);
};

glpsutilskap8.js

function createPerspectiveMatrix(fFoVVy, 
        fAspect, 
        fZnear, 
        fZfar) {   
    var test = (Matrix.create([
        [fAspect / Math.tan(fFoVVy * Math.PI / 180.0), 0, 0, 0],
        [0, 1 / Math.tan(fFoVVy * Math.PI / 180.0), 0, 0],
        [0, 0, (fZnear + fZfar) / (fZnear - fZfar), 2 * fZnear * fZfar / (fZnear - fZfar)],
        [0, 0, -1, 0]]));

    return test;
    return test;
}
;

Matrix.prototype.flatten = function() {
    var result = [];
    if (this.elements.length == 0)
        return [];

    for (var j = 0; j < this.elements[0].length; j++)
        for (var i = 0; i < this.elements.length; i++)
            result.push(this.elements[i][j]);
    return result;
};


function initGL() {
    canvas = document.getElementById("WebGL-canvas"); 
    try {
        gl = canvas.getContext("experimental-webgl");
    }
    catch (e) {
    }

    if (!gl) {
        try {
            gl = canvas.getContext("webgl");
        }
        catch (e) {
        }
    }

    if (!gl) {
        try {
            gl = canvas.getContext("webkit-3d");
        }
        catch (e) {
        }
    }

    if (!gl) {
        try {
            gl = canvas.getContext("moz-webgl");
        }
        catch (e) {
        }
    }
    if (!gl) {
        alert("WebGL not found. Please use an up to date browser and update your graphics driver.");
    }
}

function getShader(gl, id) {
    var shaderScript = document.getElementById(id);
    if (!shaderScript)
        return null;

    var str = "";
    var k = shaderScript.firstChild;
    while (k) {

        if (k.nodeType == 3)
            str += k.textContent;
        k = k.nextSibling;
    }

    var shader;
    if (shaderScript.type == "x-shader/x-fragment") {

        shader = gl.createShader(gl.FRAGMENT_SHADER);
    }
    else if (shaderScript.type == "x-shader/x-vertex") {

        shader = gl.createShader(gl.VERTEX_SHADER);
    }
    else {

        return null;
    }

    gl.shaderSource(shader, str);
    gl.compileShader(shader);

    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {

        alert(gl.getShaderInfoLog(shader));
        return null;
    }

    return shader;
}

function initShaders() {
    var fragmentShader = getShader(gl, "shader-fs");
    var vertexShader = getShader(gl, "shader-vs");

    shaderProgram = gl.createProgram();
    gl.attachShader(shaderProgram, vertexShader);
    gl.attachShader(shaderProgram, fragmentShader);
    gl.linkProgram(shaderProgram);

    if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
        alert("Could not initialise shaders");
    }

    gl.useProgram(shaderProgram);

    // attributes
    vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
    gl.enableVertexAttribArray(vertexPositionAttribute);
}

function setMatrixUniforms() {
    var mvpUniform = gl.getUniformLocation(shaderProgram, "mvpMatrix");
    gl.uniformMatrix4fv(mvpUniform, false, new Float32Array(mvpMatrix.flatten()));
}

function IndexedVertexTriangle(vectorArray, index1, index2, index3)
{
    this.index1 = index1;
    this.index2 = index2;
    this.index3 = index3;
    this.vectorArray = vectorArray;
    this.normal = null;
    this.getNormal = function()
    {
        if (this.normal == null)
        {
            var sideOne = this.vectorArray[this.index2].subtract(this.vectorArray[this.index1]);
            var sideTwo = this.vectorArray[this.index3].subtract(this.vectorArray[this.index1]);
            this.normal = sideOne.cross(sideTwo);
            this.normal = this.normal.toUnitVector();
        }

        return(this.normal);
    };

    this.getVertex = function(localIndex)
    {
        if (localIndex > 3)
        {
            return(0);
        }
        if (localIndex == 1)
            return(this.vectorArray[index1]);
        if (localIndex == 2)
            return(this.vectorArray[index2]);
        if (localIndex == 3)
            return(this.vectorArray[index3]);
    };
}

Vector.prototype.flatten = function()
{
    var result = [];
    if (this.elements.length == 0)
        return [];


    for (var i = 0; i < this.elements.length; i++)
        result.push(this.elements[i]);
    return result;
};

Sylvester.js

Downloadable at: http://sylvester.jcoglan.com/#download

Edit:

Using Chrome Timeline and stuff:

  • At first Garbage Collection (GC) takes 25% CPU, later it's 60%.

  • GC takes place almost once for each draw() call (cleaning 7.5mb). About every 20th draw() call there is no GC. After about 20 times no GC (~400 draw() calls) a bigger GC takes place (30-40mb).

  • Heap Allocation Snapshot: Data that exists till the end only gets allocated once, at the start. As intended.

  • TestData makes up 94% of the heap.

So, something's wrong with the GC, but i still have no clue what and why. Is it possible that chrome fragments my memory too much because of the 94% TestData? So the GC slows down?

I'll try to get more accustomed with those tools and maybe post an update, but help would still be appreciated.

Finally found the problem: Garbage collection of most browsers, especially Chrome has problems handling typed arrays.

Here's a related Chromium bug: https://code.google.com/p/chromium/issues/detail?id=232415

To "fix" this problem, simply use normal arrays instead of typed arrays:

Old code:

function setMatrixUniforms() {
    var mvpUniform = gl.getUniformLocation(shaderProgram, "mvpMatrix");
    gl.uniformMatrix4fv(mvpUniform, false, new Float32Array(mvpMatrix.flatten()));
}

New code:

function setMatrixUniforms() {
    var mvpUniform = gl.getUniformLocation(shaderProgram, "mvpMatrix");
    gl.uniformMatrix4fv(mvpUniform, false, mvpMatrix.flatten());
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM