javascript - Chrome 和 Windows 的非最佳 WebGL 性能

标签 javascript performance google-chrome webgl

作为对自己的挑战,我正在使用 javascript 进行基本的 minecraft 重制,并使用 <canvas> 支持的 WebGL 库。标签。我在 youtube 上有一个演示视频 here .为了使世界易于编辑,我将世界几何体分成 block (16^3) 个区域,这意味着我需要为每个渲染 block 绘制调用。这就是问题所在。这不是显卡的性能问题,我的 Nvidia GeForce 980 甚至没有打开风扇,GPU 报告在最大时钟速度的一半时利用率仅为 25%,因此实际上更准确的数字是12.5% 的利用率。问题出在 CPU 上。


GPU Process在 google chrome 任务管理器中,我的 CPU 中的核心饱和度高出 15%。这是 GL 的调用记录器所说的:

GL drawElements: [4, 7680, 5123, 0]
GL drawElements: [4, 6144, 5123, 0]
GL drawElements: [4, 7866, 5123, 0]
GL drawElements: [4, 6618, 5123, 0]
GL drawElements: [4, 6144, 5123, 0]
GL drawElements: [4, 4608, 5123, 0]
GL uniformMatrix4fv: [[object WebGLUniformLocation], false, mat4(0.9999874830245972, -0.000033332948078168556, 0.004999868106096983, 0, 0, 0.9999777674674988, 0.006666617467999458, 0, -0.0049999793991446495, -0.00666653411462903, 0.999965250492096, 0, -127.43840026855469, -129.25619506835938, -113.50281524658203, 1)]
GL uniform2fv: [[object WebGLUniformLocation], vec2(-8, -7)]
GL drawElements: [4, 7680, 5123, 0]
GL drawElements: [4, 6144, 5123, 0]
GL drawElements: [4, 6210, 5123, 0]
GL drawElements: [4, 8148, 5123, 0]
GL drawElements: [4, 6144, 5123, 0]
GL drawElements: [4, 4608, 5123, 0]
GL uniformMatrix4fv: [[object WebGLUniformLocation], false, mat4(0.9999874830245972, -0.000033332948078168556, 0.004999868106096983, 0, 0, 0.9999777674674988, 0.006666617467999458, 0, -0.0049999793991446495, -0.00666653411462903, 0.999965250492096, 0, -127.51840209960938, -129.36285400390625, -97.50337219238281, 1)]
GL uniform2fv: [[object WebGLUniformLocation], vec2(-8, -6)]
GL drawElements: [4, 7680, 5123, 0]
GL drawElements: [4, 6144, 5123, 0]
GL drawElements: [4, 7842, 5123, 0]
GL drawElements: [4, 6144, 5123, 0]
GL drawElements: [4, 4608, 5123, 0]

我能够连续调用 drawElements 的原因是因为我使用的是 WebGL 扩展 OES_vertex_array_object所以这些调用不会被记录器记录下来,所以你看不到它们。

我有很多关于状态变化的故事非常昂贵,但因为我调用了很多 drawElements背靠背这应该不是问题?我还了解到,使用我的硬件类型的人可以通过考虑这些状态变化轻松地进行 4096 绘制调用。也许这是一个问题,因为 webgl 本身未从 ANGLE gl 到 Google Chrome 使用的 direct3D 调用进行优化。

还有一点要注意:如果我将几何构造大小从 16^3 调整为 16x16x128,将绘制调用计数减少 8,那么如果没有创建世界几何,我能够以稳定的 60FPS 运行游戏。如果有则游戏无法玩。

编辑:更多测试...所以我决定制作一个最小的 webgl 程序,结果证明它是一个非常酷的屏幕保护程序。在这里:

<html>
<body style="margin:0px">
    <canvas id="gl" style="width:100%;height:100%;">

    </canvas>
</body>

<script type="vertex" id="vertex">
    attribute vec2 pos;

    uniform mat4 matrix;

    uniform float time;
    uniform vec2 translate;

    varying vec3 color;

    void main (){
        gl_Position = matrix * vec4(pos + translate, (sin(time) + 1.5) * -10.0, 1.0);

        color = vec3((sin(time) + 1.0) / 2.0);
    }
</script>

<script type="frag", id="frag">
    precision mediump float;

    varying vec3 color;

    void main (){
        gl_FragColor = vec4(color, 1.0);
    }
</script>

<script>
    var canvas = document.getElementById("gl");
    var gl = canvas.getContext("webgl");

    canvas.width = canvas.clientWidth;
    canvas.height = canvas.clientHeight;

    gl.viewport(0, 0, canvas.width, canvas.height);

    var vertShader = gl.createShader(gl.VERTEX_SHADER);
    var fragShader = gl.createShader(gl.FRAGMENT_SHADER);
    gl.shaderSource(vertShader, "attribute vec2 pos;uniform mat4 matrix;uniform float time;uniform vec2 translate;varying vec3 color;void main(){gl_Position=matrix*vec4(pos+translate,(sin(time)+1.5)*-10.0,1.0);color=vec3((sin(time)+1.0)/2.0);}");
    gl.shaderSource(fragShader, "precision mediump float;varying vec3 color;void main(){gl_FragColor=vec4(color, 1.0);}");
    gl.compileShader(vertShader);
    gl.compileShader(fragShader);

    var shader = gl.createProgram();
    gl.attachShader(shader, vertShader);
    gl.attachShader(shader, fragShader);
    gl.linkProgram(shader);
    gl.useProgram(shader);

    gl.enableVertexAttribArray(0);

    var u_time = gl.getUniformLocation(shader, "time");
    var u_matrix = gl.getUniformLocation(shader, "matrix");
    var u_translate = gl.getUniformLocation(shader, "translate");

    (function (){
        var nearView = 0.1;
        var farView = 100;
        var f = 1 / Math.tan(60 / 180 * Math.PI / 2);
        var nf = nearView - farView;
        var aspectRatio = canvas.width / canvas.height;

        gl.uniformMatrix4fv(u_matrix, false, [
            f / aspectRatio, 0, 0, 0,
            0, f, 0, 0,
            0, 0, (farView + nearView) / nf, -1,
            0, 0, (2 * farView * nearView) / nf, 0
        ]);
    })();

    var buf = gl.createBuffer();
    gl.bindBuffer (gl.ARRAY_BUFFER, buf);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
        -1, -1,
         1,  1,
        -1,  1,
        -1, -1,
         1,  1,
         1, -1,
    ]), gl.STATIC_DRAW);

    gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);

    var time = 0;

    var translations = [];

    for (var i = 0; i < 4096; i++){
        translations.push(Math.random() * 10 - 5, Math.random() * 10 - 5);
    }

    var renderLoop = function (){
        gl.clear(gl.CLEAR_COLOR_BIT | gl.CLEAR_DEPTH_BIT);

        for (var i = 0; i < 4096; i++){

            gl.uniform1f(u_time, time + i / 100);
            gl.uniform2f(u_translate, translations[i * 2], translations[i * 2 + 1])

            gl.drawArrays(gl.TRIANGLES, 0, 6);
        }

        window.requestAnimationFrame(renderLoop);
    }

    window.setInterval(function (){
        time += 0.01;
    }, 10);

    window.requestAnimationFrame(renderLoop);
</script>

程序绘制了一堆正方形。在这种情况下,它是 4096 进行那么多的绘制调用。性能比我的主要项目好,但仍不是最佳的。 gpu 进程使用 ~13% 的 CPU,我以某种方式维持了售出的 60 FPS。当然,我所做的最多就是进行一些统一调用。我的真实项目使用了 5 个着色器程序,显然要处理更多的信息。我将尝试使用我用来渲染主游戏的 api 来编写它。也许还有改进的余地。

最佳答案

你有多少 block ?你说每个 block 是 16^3。所以这是 4096 个立方体或最多 49152 个三 Angular 形(如果通过某种魔法你可以显示每个立方体的每个面,我猜你不能)

我真的不知道怎么回答你的问题。我想首先要测试的是一个空程序运行多少 CPU

function render() {
  requestAnimationFrame(render);
}

requestAnimationFrame(render);

我看几乎没有时间做那个。

那么,最小的 WebGL 程序怎么样

var gl = document.createElement("canvas").getContext("webgl");
document.body.appendChild(gl.canvas);

function resize(canvas) {
  var width = canvas.clientWidth;
  var height = canvas.clientHeight;
  if (canvas.width !== width || canvas.height !== height) {
    canvas.width = width;
    canvas.height = height;
  }
}

function render() {
  resize(gl.canvas);
  gl.clearColor(Math.random(), 0, 0, 1);
  gl.clear(gl.COLOR_BUFFER_BIT);
  
  requestAnimationFrame(render);
}
requestAnimationFrame(render);
canvas { 
  width: 100%;
  height: 100%;
}
html, body {
  width: 100%;
  height: 100%;
  overflow: hidden;
  margin: 0
}
  

就这样我得到了一些相当高的数字

enter image description here

在绘图中添加 100 个 49k 多边形的球体(类似于您的 100 个 block )

"use strict";
// using twgl.js because I'm lazy
    twgl.setAttributePrefix("a_");
    var m4 = twgl.m4;
    var gl = twgl.getWebGLContext(document.getElementById("c"));
    var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);

    var shapes = [
      twgl.primitives.createSphereBufferInfo(gl, 1, 157, 157),
      twgl.primitives.createSphereBufferInfo(gl, 1, 157, 157),
      twgl.primitives.createSphereBufferInfo(gl, 1, 157, 157),
      twgl.primitives.createSphereBufferInfo(gl, 1, 157, 157),
      twgl.primitives.createSphereBufferInfo(gl, 1, 157, 157),
      twgl.primitives.createSphereBufferInfo(gl, 1, 157, 157),
      twgl.primitives.createSphereBufferInfo(gl, 1, 157, 157),
      twgl.primitives.createSphereBufferInfo(gl, 1, 157, 157),
      twgl.primitives.createSphereBufferInfo(gl, 1, 157, 157),
      twgl.primitives.createSphereBufferInfo(gl, 1, 157, 157),
      twgl.primitives.createSphereBufferInfo(gl, 1, 157, 157),
    ];

    function rand(min, max) {
      return min + Math.random() * (max - min);
    }

    // Shared values
    var lightWorldPosition = [1, 8, -10];
    var lightColor = [1, 1, 1, 1];
    var camera = m4.identity();
    var view = m4.identity();
    var viewProjection = m4.identity();

    var tex = twgl.createTexture(gl, {
      min: gl.NEAREST,
      mag: gl.NEAREST,
      src: [
        255, 255, 255, 255,
        192, 192, 192, 255,
        192, 192, 192, 255,
        255, 255, 255, 255,
      ],
    });
        
    var randColor = function() {
        var color = [Math.random(), Math.random(), Math.random(), 1];
        color[Math.random() * 3 | 0] = 1; // make at least 1 bright
        return color;
    };
                                 
    var r = function() {
      return Math.random() * 2 - 1;
    };

    var objects = [];
    var numObjects = 100;
    for (var ii = 0; ii < numObjects; ++ii) {
      var world = m4.translation([r(), r(), r()]);
      var uniforms = {
        u_lightWorldPos: lightWorldPosition,
        u_lightColor: lightColor,
        u_diffuseMult: randColor(),
        u_specular: [1, 1, 1, 1],
        u_shininess: 50,
        u_specularFactor: 1,
        u_diffuse: tex,
        u_viewInverse: camera,
        u_world: world,
        u_worldInverseTranspose: m4.transpose(m4.inverse(world)),
        u_worldViewProjection: m4.identity(),
      };
      objects.push({
        ySpeed: rand(0.1, 0.3),
        zSpeed: rand(0.1, 0.3),
        uniforms: uniforms,
        programInfo: programInfo,
        bufferInfo: shapes[ii % shapes.length],
      });
    }

    var showRenderingArea = false;

    function render(time) {
      time *= 0.001;
      twgl.resizeCanvasToDisplaySize(gl.canvas);
      gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);

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

      var eye = [Math.cos(time) * 8, 0, Math.sin(time) * 8];
      var target = [0, 0, 0];
      var up = [0, 1, 0];

      m4.lookAt(eye, target, up, camera);
      m4.inverse(camera, view);

      var projection = m4.perspective(
          30 * Math.PI / 180, gl.canvas.clientWidth / gl.canvas.clientHeight, 0.5, 100);
      m4.multiply(view, projection, viewProjection);

      
      objects.forEach(function(obj, ndx) {

        var uni = obj.uniforms;
        var world = uni.u_world;
        m4.multiply(uni.u_world, viewProjection, uni.u_worldViewProjection);

        gl.useProgram(obj.programInfo.program);
        twgl.setBuffersAndAttributes(gl, obj.programInfo, obj.bufferInfo);
        twgl.setUniforms(obj.programInfo, uni);
        twgl.drawBufferInfo(gl, gl.TRIANGLES, obj.bufferInfo);
      });
    }

        var renderContinuously = function(time) {
            render(time);
            requestAnimationFrame(renderContinuously);
        }
        requestAnimationFrame(renderContinuously);
* {
  box-sizing: border-box;
  -moz-box-sizing: border-box;
}
html, body {
  margin: 0px;
  width: 100%;
  height: 100%;
  font-family: monospace;
}
canvas {
  width: 100%;
  height: 100%;
}
#c {
  position: fixed;
}
<canvas id="c"></canvas>
<script src="//twgljs.org/dist/twgl-full.min.js"></script>
  <script id="vs" type="notjs">
uniform mat4 u_worldViewProjection;
uniform vec3 u_lightWorldPos;
uniform mat4 u_world;
uniform mat4 u_viewInverse;
uniform mat4 u_worldInverseTranspose;

attribute vec4 a_position;
attribute vec3 a_normal;
attribute vec2 a_texcoord;

varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;

void main() {
  v_texCoord = a_texcoord;
  v_position = (u_worldViewProjection * a_position);
  v_normal = (u_worldInverseTranspose * vec4(a_normal, 0)).xyz;
  v_surfaceToLight = u_lightWorldPos - (u_world * a_position).xyz;
  v_surfaceToView = (u_viewInverse[3] - (u_world * a_position)).xyz;
  gl_Position = v_position;
}
  </script>
  <script id="fs" type="notjs">
precision mediump float;

varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;

uniform vec4 u_lightColor;
uniform vec4 u_diffuseMult;
uniform sampler2D u_diffuse;
uniform vec4 u_specular;
uniform float u_shininess;
uniform float u_specularFactor;

vec4 lit(float l ,float h, float m) {
  return vec4(1.0,
              abs(l),//max(l, 0.0),
              (l > 0.0) ? pow(max(0.0, h), m) : 0.0,
              1.0);
}

void main() {
  vec4 diffuseColor = texture2D(u_diffuse, v_texCoord) * u_diffuseMult;
  vec3 a_normal = normalize(v_normal);
  vec3 surfaceToLight = normalize(v_surfaceToLight);
  vec3 surfaceToView = normalize(v_surfaceToView);
  vec3 halfVector = normalize(surfaceToLight + surfaceToView);
  vec4 litR = lit(dot(a_normal, surfaceToLight),
                    dot(a_normal, halfVector), u_shininess);
  vec4 outColor = vec4((
  u_lightColor * (diffuseColor * litR.y +
                u_specular * litR.z * u_specularFactor)).rgb,
      diffuseColor.a);
  gl_FragColor = outColor;
}
  </script>

我的 GPU 进程使用率仅略有增加

enter image description here

所以看起来大部分时间基本上都花在处理 WebGL 上了。换句话说,问题似乎不是您的代码?

让我们尝试一些可能的优化(没有 alpha,没有抗锯齿)

var gl = document.createElement("canvas").getContext("webgl", {alpha: false, antialias: false});
document.body.appendChild(gl.canvas);

function resize(canvas) {
  var width = canvas.clientWidth;
  var height = canvas.clientHeight;
  if (canvas.width !== width || canvas.height !== height) {
    canvas.width = width;
    canvas.height = height;
  }
}

function render() {
  resize(gl.canvas);
  gl.clearColor(Math.random(), 0, 0, 1);
  gl.clear(gl.COLOR_BUFFER_BIT);
  
  requestAnimationFrame(render);
}
requestAnimationFrame(render);
canvas { 
  width: 100%;
  height: 100%;
}
html, body {
  width: 100%;
  height: 100%;
  overflow: hidden;
  margin: 0
}

似乎已经下降了一点点。

enter image description here

看起来也许你应该 file a bug并询问为什么 Chrome 需要 2 个进程中的 20% 来显示 Canvas

注意:在 Chrome 的防御中,Firefox 也使用了大约相同数量的处理器能力(1 个处理器上 30-40%)。

另一方面,Safari 仅将 7% 用于最小的 WebGL 程序。

关于javascript - Chrome 和 Windows 的非最佳 WebGL 性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31106321/

相关文章:

sql - 事务脚本也是 NoSQL 数据库的反模式吗?

performance - 懒惰列表的 Haskell 性能不佳

html - nowrap 元素溢出它的盒子

html - 在 jsFiddle 中工作的 FileReader API,但不是来自本地文件

javascript - Angular-quickstart@1.0.0 构建脚本失败 'tsc -p src/'

javascript - 获取嵌套值的路径

php - IE 中表单中的元素为空

javascript - 在插入的 DOM 元素上搜索文本

sql - 如何知道何时使用索引以及使用哪种类型?

javascript - Chrome - 调试 WebGL 崩溃 ("Rats! WebGL hit a snag.")