为什么我的WebGl帧速率在Chrome中慢慢下降?

时间:2014-05-29 10:04:09

标签: javascript google-chrome garbage-collection webgl typed-arrays

在我的WebGl程序中,帧率开始高,然后慢慢减少,内存使用量随着时间的推移而等效增加。帧率不会无限下降,但在某些时候保持一致。 IE和IE中的问题可以忽略不计。 FF,但在Chrome中,我的帧率从30降至10。

我缩小了问题的范围: 它是由创建正在绘制的数据的函数(在原始程序中)引起的。 出于测试目的,我创建了一个将测试数据写入全局变量的函数。测试数据 NOT 以任何方式使用。 fps减少仍然存在。

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;
        }
    }
}

如果我注释掉对该函数的一次调用,那么一切都按预期工作,没有帧率下降。

在运行时调用createTestData()会导致fps像开始时一样上升,然后再慢慢下降。

我的程序流程的Chrome内存使用量不到200mb,远不及Chrome应该遇到的问题。

我使用的是Win 7&amp; 8,Chrome 35&amp; 36和多个电脑设置。

这感觉就像是一个Chrome错误,但我找不到其他人遇到这个问题,所以这可能是我身上的一些愚蠢错误。

强烈简化版本的完整代码:

的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

可从http://sylvester.jcoglan.com/#download

下载

修改

使用Chrome时间轴和内容:

  • 首先垃圾收集(GC)占用25%的CPU,后来占60%。

  • 对于每次draw()调用,GC几乎发生一次(清理7.5mb)。关于每20次抽奖()呼叫,没有GC。没有GC(约400次抽取()调用)约20次后,会发生更大的GC(30-40mb)。

  • 堆分配快照:存在直到最后的数据仅在开始时分配一次。如预期的那样。

  • TestData占堆的94%。

所以,GC出了问题,但我仍然不知道为什么和为什么。由于94%的TestData,镀铬是否有可能使我的记忆过多?那么GC放慢了速度?

我会尝试更熟悉这些工具,也许会发布更新,但仍然会感谢您的帮助。

1 个答案:

答案 0 :(得分:3)

终于找到了问题: 大多数浏览器的垃圾收集,特别是Chrome在处理类型数组时遇到问题。

这是一个相关的Chromium bug: https://code.google.com/p/chromium/issues/detail?id=232415

要“修复”此问题,只需使用普通数组而不是类型化数组:

旧代码:

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

新代码:

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