【问题标题】:Why does my WebGl framerate slowly drop in Chrome?为什么我的 WebGl 帧率在 Chrome 中缓慢下降?
【发布时间】:2014-06-10 17:39:37
【问题描述】:

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

我缩小了问题的范围: 它是由创建正在绘制的数据的函数引起的(在原始程序中)。 出于测试目的,我创建了一个将测试数据写入全局变量的函数。测试数据以任何方式使用。 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 和 8、Chrome 35 和 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;
};

西尔维斯特.js

下载地址:http://sylvester.jcoglan.com/#download

编辑:

使用 Chrome 时间轴和其他东西:

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

  • 每次 draw() 调用几乎都会发生一次 GC(清理 7.5mb)。大约每 20 次 draw() 调用没有 GC。大约 20 次没有 GC(约 400 次 draw() 调用)后,发生了更大的 GC(30-40mb)。

  • 堆分配快照:存在到最后的数据只在开始时分配一次。符合预期。

  • TestData 占堆的 94%。

所以,GC 出了点问题,但我仍然不知道是什么以及为什么。由于 94% 的测试数据,chrome 是否有可能过多地碎片化我的记忆?所以 GC 变慢了?

我会尽量熟悉这些工具,也许会发布更新,但我们仍将不胜感激。

【问题讨论】:

  • 您是否尝试过使用memoryprofiling 工具来查看问题可能出在哪里?还有timeline
  • 我不知道这些工具,谢谢。我编辑了我的帖子,但仍未解决。

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


【解决方案1】:

终于找到问题了: 大多数浏览器的垃圾收集,尤其是 Chrome 都存在处理类型数组的问题。

这是一个相关的 Chromium 错误: 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());
}

【讨论】:

  • 谢谢。我刚刚将 mat4.create() 移出主循环(一遍又一遍地重用相同的 Float32Array),滞后就消失了!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-23
  • 2011-05-07
  • 2014-07-16
  • 1970-01-01
  • 1970-01-01
  • 2012-03-01
相关资源
最近更新 更多