在不同的移动设备/体系结构上测量Drawcall性能-带Discard()的着色器或将顶点放置在远平面之外?

时间:2019-05-02 08:01:32

标签: javascript performance graphics webgl shader

我希望在WebGL中测量移动设备/体系结构的着色器性能。

我知道在着色器中使用'discard()'并不是很有效,但是我想进行一些实验并获得一些关于着色器在绘制调用方面的表现的信息-主要标准之一是测量使用“ discard()”或仅将对象/顶点放置在视锥的远平面之外时,不同移动设备和体系结构(iPhone,iPad以及图块渲染和延迟渲染)的性能。

我对Javascript / WebGL还是很陌生,因此我想寻求某种指针,或者也许有人已经进行了类似的测试,可以以此为基础来获取一些数字。我还没有在互联网上遇到过任何这样的片段。使用THREE.js或typescript或纯js示例的任何东西都可以作为入门模板。

谢谢,任何指针将不胜感激。

谢谢

1 个答案:

答案 0 :(得分:0)

您可以也许像这样调用gl.readPixels来测量更快的速度

const startTime = performance.now();
drawLotsOfStuff();
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4));
const endTime = performance.now();
const elaspedTimeInMilliseconds = endTime - startTime();

这不会告诉您某些东西渲染的速度,但是它可以告诉您哪种方法更快。

WebGL(尤其是在Chrome中)是多进程的。默认情况下,当您仅在60fps应用中不断渲染时,所有内容都可以并行运行。 JavaScript正在调用gl.drawXXX,以前的绘制命令正在并行运行。当您调用gl.readPixels时,尽管必须停止整个并行部分,以便在读取数据之前执行所有先前的绘制命令。

这意味着使用gl.readPixels不会告诉您某事物的运行速度。它告诉您花费了多少时间

  1. 开始2或3个进程
  2. 协调他们彼此之间的生活
  3. 发出一些命令
  4. 等待这些命令执行
  5. 同时停止这两个过程
  6. 同步两个过程
  7. 将数据从一个过程转移到另一个过程

如果您想知道事物绘制的速度,您只想计时上面的第4步,但是基于事物并行化的事实,您有第1.,2.,3.,5.,6。和7.包含在您的时间安排中。

尽管如此,假设所有这些都是恒定不变的,那么您至少可以判断第3步比其他第3步快还是慢。

我说也许是因为浏览器中发生了很多事情。可能会有垃圾回收打ic或其他事情,这些事情甚至增加了更多步骤,从而使计时变糟。

另一个问题是浏览器确实或至少故意返回了低分辨率结果以进行计时。这是为了减轻Spectre issues。我认为Chrome现在已经将高分辨率结果重新启用,尽管他们不确定是否添加了进程隔离。

让我们测试一下,看看我们是否获得一致的结果

function main() {
  const gl = document.createElement('canvas').getContext('webgl');

  const vs = `
  attribute vec4 position;
  void main() {
    gl_PointSize = 128.0;
    gl_Position = position;
  }
  `;

  const fastFS = `
  precision highp float;
  void main() {
    gl_FragColor = vec4(1);
  }
  `;

  const slowFS = `
  precision highp float;
  // these are here to try to make sure the loop
  // is not optimized. (though it could still easily
  // be as it's really just color = junk * moreJunk * 100
  uniform vec4 junk;
  uniform vec4 moreJunk;

  void main() {
    vec4 color = vec4(0);
    for (int i = 0; i < 100; ++i) {
      color += junk * moreJunk;
    }
    gl_FragColor = color;
  }
  `;

  const locations = ['position']; // make position location 0
  const fastPrg = twgl.createProgram(gl, [vs, fastFS], locations);
  const slowPrg = twgl.createProgram(gl, [vs, slowFS], locations);

  const fastTime = time(gl, 'fast', fastPrg);
  const slowTime = time(gl, 'slow', slowPrg);

  // Because Safari is the new IE we can't not have attirbutes
  // as Safari fails the WebGL conformance tests for no attribute
  // situations.
  {
    const buf = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, buf);
    gl.bufferData(gl.ARRAY_BUFFER, 1000000, gl.STATIC_DRAW);

    const posLoc = 0;  // assigned in createProgramInfo
    gl.enableVertexAttribArray(posLoc);
    // only taking X from buffer
    gl.vertexAttribPointer(posLoc, 1, gl.FLOAT, false, 0, 0);
  }

  const fastX = slowTime / fastTime;
  console.log(`fast is maybe ${fastX.toFixed(4)}x faster than slow`);
  console.log(gl.getError());

  function time(gl, label, prg) {
    gl.enable(gl.BLEND);
    gl.blendFunc(gl.ONE, gl.ONE);
    gl.useProgram(prg);
    // use program once so any initialization behind the scenes
    // happens (voodoo)
    gl.drawArrays(gl.POINTS, 0, 1);
    sync(gl);
    const startTime = performance.now();
    for (let i = 0; i < 100; ++i) {
      gl.drawArrays(gl.POINTS, 0, 1000);
    }
    sync(gl);
    const endTime = performance.now();
    const elapsedTime = endTime - startTime;
    console.log(label, 'elapsedTime:', elapsedTime.toFixed(4));
    return elapsedTime;
  }
  
  function sync(gl) {
    gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4));
  }  
}
main();
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>

当我在上面运行时,相对于慢速着色器,快速着色器的结果是3.5倍至4.5倍,因此您可以看到结果不一致,但至少我们知道快速着色器实际上比慢速着色器快,这足以满足我们选择一种方法而不是另一种方法。

当然,在不同的GPU上结果可能有所不同。在iOS上尤其如此,因为iOS设备使用tiled renderer。即使我们要求系统每个绘制调用绘制100个四边形,也就是100个绘制调用,换句话说,就是10000个四边形,平铺渲染器将意识到它只需要绘制最后一个四边形。解决该问题的一种方法是打开

gl.enable(gl.BLEND);

这样,我相信平铺渲染器不仅可以渲染最后一个四边形。

不幸的是,当我在iOS上运行上面的示例时,我发现速度快慢于速度慢,这表明(1)浏览器中的定时分辨率有多差,或者(2)不同的平铺架构如何工作,或者(3)iOS驱动程序实际上确实优化了循环。

让我们通过在内部循环中使用纹理使慢速着色器变慢,这样我们实际上必须在循环的每次迭代中查找不同的结果。

function main() {
  const gl = document.createElement('canvas').getContext('webgl');

  const vs = `
  attribute vec4 position;
  void main() {
    gl_PointSize = 128.0;
    gl_Position = position;
  }
  `;

  const fastFS = `
  precision highp float;
  void main() {
    gl_FragColor = vec4(1);
  }
  `;

  const slowFS = `
  precision highp float;
  uniform vec4 junk;
  uniform vec4 moreJunk;
  uniform sampler2D tex;

  void main() {
    vec4 color = vec4(0);
    for (int i = 0; i < 100; ++i) {
      // AFAIK this can not be optimized too much as the inputs
      // change over the loop looking up different parts of the texture.
      color += texture2D(tex, fract(junk * moreJunk * float(i)).xy * gl_PointCoord.xy);
    }
    gl_FragColor = color;
  }
  `;

  const fastPrg = twgl.createProgram(gl, [vs, fastFS]);
  const slowPrg = twgl.createProgram(gl, [vs, slowFS]);

  // Because Safari is the new IE we can't not have attirbutes
  // as Safari fails the WebGL conformance tests for no attribute
  // situations.
  {
    const buf = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, buf);
    gl.bufferData(gl.ARRAY_BUFFER, 1000000, gl.STATIC_DRAW);

    const posLoc = 0;  // assigned in createProgramInfo
    gl.enableVertexAttribArray(posLoc);
    // only taking X from buffer
    gl.vertexAttribPointer(posLoc, 1, gl.FLOAT, false, 0, 0);
  }

  const tex = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, tex);
  const data = new Uint8Array(1024 * 1024 * 4);
  for (let i = 0; i < data.length; ++i) {
    data[i] = Math.random() * 256;
  }
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1024, 1024, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
  gl.generateMipmap(gl.TEXTURE_2D);

  const fastTime = time(gl, 'fast', fastPrg);
  const slowTime = time(gl, 'slow', slowPrg);

  const fastX = slowTime / fastTime;
  console.log(`fast is maybe ${fastX.toFixed(4)}x faster than slow`);

  function time(gl, label, prg) {
    gl.enable(gl.BLEND);
    gl.blendFunc(gl.ONE, gl.ONE);
    gl.useProgram(prg);
    // use program once so any initialization behind the scenes
    // happens (voodoo)
    gl.drawArrays(gl.POINTS, 0, 1);
    sync(gl);
    const startTime = performance.now();
    for (let i = 0; i < 100; ++i) {
      gl.drawArrays(gl.POINTS, 0, 1000);
    }
    sync(gl);
    const endTime = performance.now();
    const elapsedTime = endTime - startTime;
    console.log(label, 'elapsedTime:', elapsedTime.toFixed(4));
    return elapsedTime;
  }

  function sync(gl) {
    gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4));
  }

}
main();
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>

现在在我的iPhoneX上,我得到的快比慢得快。

那么,我们学到了什么?我们可能了解到,如果您的着色器具有相似的性能水平,将很难分辨出哪种着色器更快可靠。