3

我想知道是否有任何可以利用的 WebGL 异步调用?

我研究了 Spec v1 和 Spec v2,他们没有提及任何内容。在 V2 中,有一个 WebGL 查询机制,我认为这不是我想要的。

在网络上搜索并没有得出任何确定的结果。有这个例子,并不清楚同步和异步版本有何不同。http://toji.github.io/shader-perf/

最终,我希望能够异步完成所有这些:

  • 读取像素
  • texSubImage2D 和 texImage2D
  • 着色器编译
  • 程序链接
  • 画???

有一个 glFinish 操作,它的文档说:“在所有以前调用的 GL 命令的效果完成之前不会返回。”。对我来说,这意味着可以通过调用 Finish() 来等待异步操作?

并且网络上的一些帖子表明调用 getError() 也会强制实现一些同步性,并且在每次调用后都不是非常希望做的事情。

4

1 回答 1

6

这取决于您对异步的定义。

在 Chrome 中(Firefox 现在也可能这样做?不确定)。Chrome 在与 JavaScript 不同的进程中运行所有 GPU 代码。这意味着您的命令正在异步运行。甚至 OpenGL 本身也被设计为异步的。函数 (WebGL/OpenGL) 将命令插入命令缓冲区。这些是由其他一些线程/进程执行的。你告诉 OpenGL “嘿,我有新的命令要你执行!” 通过调用gl.flush. 它异步执行这些命令。如果您不调用gl.flush它,则会在发出太多命令时定期为您调用。它也会在当前 JavaScript 事件退出时被调用,假设您调用了任何渲染命令到画布(gl.drawXXX、gl.clear)。

从这个意义上说,WebGL 的一切都是异步的。如果您不查询某些内容(gl.getXXX、gl.readXXX),那么处理(绘制)的内容与您的 JavaScript 不同步。WebGL 让您在与 CPU 分开运行之后访问 GPU。

知道在 Chrome 中利用它的一种方法是通过提交着色器来异步编译着色器

for each shader
  s = gl.createShader()
  gl.shaderSource(...);
  gl.compileShader(...);
  gl.attachShader(...);
gl.linkProgram(...)
gl.flush()

GPU 进程现在将编译您的着色器。因此,比如说,250 毫秒后,您才开始询问它是否成功并查询位置,然后如果编译和链接着色器花费的时间少于 250 毫秒,那么这一切都是异步发生的。

在 WebGL2 中,至少有一个更明确的异步操作,即遮挡查询,其中 WebGL2 可以告诉您为一组绘制调用绘制了多少像素。如果未绘制,则您的绘制被遮挡。为了得到答案,你会定期检查答案是否准备好。通常,您检查下一帧,实际上 WebGL 规范要求答案在下一帧之前不可用。

否则,目前(2018 年 8 月)没有明确的异步 API。

更新

HankMoody 提出了texImage2D同步的评论。同样,这取决于您对 async 的定义。添加命令及其数据需要时间。像这样的命令gl.enable(gl.DEPTH_TEST)只需添加 2-8 个字节。像这样的命令gl.texImage2D(..., width = 1024, height = 1024, RGBA, UNSIGNED_BYTE)必须添加 4meg!。一旦上传了 4meg,其余的都是异步的,但上传需要时间。这两个命令都是一样的,只是添加 2-8 个字节比添加 4meg 花费的时间要少得多。

更清楚的是,在上传 4 meg 之后,许多其他事情都是异步发生的。用 4 meg 调用驱动程序。驱动程序复制该 4meg。驱动程序计划稍后使用 4meg,因为如果纹理已在使用中,它无法立即上传数据。要么它立即将它上传到一个新区域,然后在实际使用该新数据的绘制调用之前交换纹理指向的内容。其他驱动程序只是复制数据并存储它,然后等到在绘制调用中使用纹理来实际更新纹理。这是因为 texImage2D 具有疯狂的语义,您可以按任何顺序上传不同大小的 mip,因此驱动程序在绘制时间之前无法知道 GPU 内存中实际需要什么,因为它不知道您要调用 texIamge2D 的顺序。

但这确实带来了更多信息。

gl.texImage2D和相关的命令必须做大量的工作。一个是他们必须尊重UNPACK_FLIP_Y_WEBGLUNPACK_PREMULTIPLY_ALPHA_WEBGL所以他们需要复制多兆数据来翻转它或预乘它。其次,如果您向他们传递视频、画布或图像,他们可能需要进行大量转换,甚至从源代码重新解析图像,尤其是在UNPACK_COLORSPACE_CONVERSION_WEBGL. 这是否以某种类似异步的方式发生取决于浏览器。由于您无法直接访问图像/视频/画布,因此浏览器可能会执行所有这些异步操作,但必须以一种或另一种方式完成所有工作。

为了使大部分工作异步,ImageBitmap添加了 API。像大多数 Web API 一样,它没有指定,但想法是你首先做一个fetch(这是异步的)。然后,您请求创建一个ImageBitmap并为其提供颜色转换、翻转、预乘 alpha 的选项。这也发生异步。然后,您将结果传递给gl.texImage2D,希望浏览器能够在进入最后一步之前完成所有繁重的部分。

例子:

// note: mode: 'cors' is because we are loading
// from a different domain

async function main() {
  const response = await fetch('https://i.imgur.com/TSiyiJv.jpg', {mode: 'cors'})
  if (!response.ok) {
    return console.error('response not ok?');
  }
  const blob = await response.blob();
  const bitmap = await createImageBitmap(blob, {
    premultiplyAlpha: 'none',
    colorSpaceConversion: 'none',
  });

  const gl = document.querySelector("canvas").getContext("webgl");

  const tex = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, tex);
  {
    const level = 0;
    const internalFormat = gl.RGBA;
    const format = gl.RGBA;
    const type = gl.UNSIGNED_BYTE;
    gl.texImage2D(gl.TEXTURE_2D, level, internalFormat,
                  format, type, bitmap);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  }

  const vs = `
  uniform mat4 u_worldViewProjection;
  attribute vec4 position;
  attribute vec2 texcoord;
  varying vec2 v_texCoord;

  void main() {
    v_texCoord = texcoord;
    gl_Position = u_worldViewProjection * position;
  }
  `;
  const fs = `
  precision mediump float;
  varying vec2 v_texCoord;
  uniform sampler2D u_tex;
  void main() {
    gl_FragColor = texture2D(u_tex, v_texCoord);
  }
  `;

  const m4 = twgl.m4;
  const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
  const bufferInfo = twgl.primitives.createCubeBufferInfo(gl, 2);
  const uniforms = {
    u_tex: tex,
  };

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

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

    const fov = 30 * Math.PI / 180;
    const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
    const zNear = 0.5;
    const zFar = 10;
    const projection = m4.perspective(fov, aspect, zNear, zFar);
    const eye = [1, 4, -6];
    const target = [0, 0, 0];
    const up = [0, 1, 0];

    const camera = m4.lookAt(eye, target, up);
    const view = m4.inverse(camera);
    const viewProjection = m4.multiply(projection, view);
    const world = m4.rotationY(time);

    uniforms.u_worldViewProjection = m4.multiply(viewProjection, world);

    gl.useProgram(programInfo.program);
    twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
    twgl.setUniforms(programInfo, uniforms);
    gl.drawElements(gl.TRIANGLES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0);

    requestAnimationFrame(render);
  }
  requestAnimationFrame(render);
}
main();
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>

不幸的是,这仅适用于 2018 年 8 月的 Chrome。Firefox错误在这里。其他浏览器不知道。

于 2018-08-07T00:17:21.503 回答