Chrome和Windows的非最佳WebGL性能

Ale*_*ski 5 javascript performance google-chrome webgl

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


GPU Process谷歌浏览器的任务管理器是在15%以上,然后我的CPU在饱和核心.这就是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]
Run Code Online (Sandbox Code Playgroud)

我能够进行背靠背drawElements调用的原因是因为我正在使用WebGL扩展,OES_vertex_array_object因此记录器不会记录这些调用,因此您看不到它们.

我发现状态变化非常昂贵的故事但是因为我背叛了很多,drawElements这不应该成为一个问题吗?此外,我认为拥有我的硬件类型的人可以通过考虑这些状态变化轻松地进行4096次绘制调用.也许这是一个问题,因为从谷歌Chrome使用的ANGLE gl到direct3D调用,webgl本身没有得到优化.

还有一点需要注意:如果我将几何构造大小从16 ^ 3减少到16x16x128,那么绘制调用会减少8,如果没有创建世界几何,我可以以60FPS的速度运行游戏.如果有游戏无法播放.

编辑:一些更多的测试...所以我决定制作一个最小的webgl程序,结果是一个preaty酷屏幕保护程序.这里是:

<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>
Run Code Online (Sandbox Code Playgroud)

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

gma*_*man 0

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

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

function render() {
  requestAnimationFrame(render);
}

requestAnimationFrame(render);
Run Code Online (Sandbox Code Playgroud)

我看几乎没有时间做那件事。

那么,最小的 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);
Run Code Online (Sandbox Code Playgroud)
canvas { 
  width: 100%;
  height: 100%;
}
html, body {
  width: 100%;
  height: 100%;
  overflow: hidden;
  margin: 0
}
Run Code Online (Sandbox Code Playgroud)
  
Run Code Online (Sandbox Code Playgroud)

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

在此输入图像描述

添加绘制 100 个球体,每个球体有 49k 个多边形(类似于 100 个块)

"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);
Run Code Online (Sandbox Code Playgroud)
* {
  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;
}
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

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

在此输入图像描述

所以看起来大部分时间都花在了处理 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);
Run Code Online (Sandbox Code Playgroud)
canvas { 
  width: 100%;
  height: 100%;
}
html, body {
  width: 100%;
  height: 100%;
  overflow: hidden;
  margin: 0
}
Run Code Online (Sandbox Code Playgroud)

好像下降了一点点。

在此输入图像描述

看起来也许你应该提交一个错误并询问为什么 Chrome 需要 2 个进程的 20% 才能显示画布

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

另一方面,Safari 浏览器只使用 7% 的最小 WebGL 程序。