代码之家  ›  专栏  ›  技术社区  ›  Jeff Saremi

异步操作?

  •  2
  • Jeff Saremi  · 技术社区  · 8 年前

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

    我已经查过规范1和规范2,他们什么都没提。在V2中,有一个WebGL查询机制,我认为这不是我想要的。

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

    最终,我希望能够以异步方式完成这些任务:

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

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

    web上的一些帖子建议,调用getError()也会强制一些同步性,并且不是每次调用后都要做的事情。

    1 回复  |  直到 8 年前
        1
  •  3
  •   gman    7 年前

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

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

    从这个意义上讲,WebGL的一切都是异步的。如果你不查询一些东西(gl.getXXX,gl.readXXX),那么这些东西的处理(绘制)与你的JavaScript不同步。WebGL允许您访问一个GPU,毕竟它是独立于CPU运行的。

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

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

    GPU进程现在将编译着色器。所以,比方说,250毫秒之后,你才开始询问它是否成功并查询位置,然后如果编译和链接着色器所用的时间少于250毫秒,这一切都是异步的。

    在WebGL2中,至少有一个更清晰的异步操作occlusion查询,WebGL2可以告诉您为一组draw调用绘制了多少像素。如果没有被画出来,那么你的画就被遮挡了。为了得到答案,你需要周期性地撑杆,看看答案是否准备好了。通常您检查下一帧,实际上WebGL规范要求答案在下一帧之前不可用。

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

    更新

    汉克穆迪在评论中提到 texImage2D 是同步的。再说一遍,它 取决于您对异步的定义 . 添加命令及其数据需要时间。像这样的命令 gl.enable(gl.DEPTH_TEST) 只需添加2-8个字节。像这样的命令 gl.texImage2D(..., width = 1024, height = 1024, RGBA, UNSIGNED_BYTE) 必须加4毫克!。一旦上传了4meg,其余的是异步的,但上传需要时间。这两个命令都是一样的,只是添加2-8字节比添加4meg要花费更少的时间。

    更清楚的是,在4meg被上传之后,还有很多事情是异步发生的。司机被叫4兆欧。司机复制了那个4meg。驱动程序安排4meg稍后使用,因为如果纹理已经在使用中,它无法立即上载数据。或者直接上传到一个新的区域,然后在真正使用新数据的draw调用之前交换纹理指向的内容。其他驱动程序只需复制数据并存储它,然后等到纹理在draw调用中被使用来实际更新纹理。这是因为texImage2D具有疯狂的语义,你可以按任意顺序上传不同大小的mips,因此驱动程序在绘制时间之前无法知道GPU内存中实际需要什么,因为它不知道你将调用texiamge2的顺序。本段中提到的所有这些都是异步发生的。

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

    gl.texImage2D 相关的指挥部要做大量的工作。一是他们必须尊重 UNPACK_FLIP_Y_WEBGL UNPACK_PREMULTIPLY_ALPHA_WEBGL 所以他们需要复制多兆的数据来翻转或预乘。其次,如果你传给他们一个视频、画布或图像,他们可能需要进行大量的转换,甚至重新分析源代码中的图像,特别是在 UNPACK_COLORSPACE_CONVERSION_WEBGL . 这是否以某种类似异步的方式发生取决于浏览器。由于您没有直接访问图像/视频/画布的权限,因此浏览器可以执行所有这些异步操作,但必须以某种方式执行所有操作。

    为了充分利用这项工作 ImageBitmap 添加了API。与大多数Web api一样,它没有被指定,但想法是首先 fetch (这是异步的)。然后请求创建 图像位图 并为它提供颜色转换、翻转、预乘alpha的选项。这也会异步发生。然后将结果传递给 纹理图像2d 希望浏览器能够在进入最后一步之前完成所有繁重的工作。

    例子:

    // 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。火狐 bug is here . 其他浏览器我不知道。