【问题标题】:Float32Array not updating ArrayBuffer to reflect assigned values when interacting with WebGL (Chrome)与 WebGL (Chrome) 交互时,Float32Array 未更新 ArrayBuffer 以反映分配的值
【发布时间】:2014-01-02 15:34:57
【问题描述】:

为了使用 OES_texture_float 扩展在 Chrome 中使用 WebGL 手动打包和绑定 RGBA 浮点组件纹理到 GPU,像素组件数据必须存储在 Float32Array 中。

例如,对于一个简单的 3 像素纹理,每个有 4 个浮点组件,首先要声明一个普通的 JS 数组:

var pixels = [1.01, 1.02, 1.03, 1.04, 2.01, 2.02, 2.03, 2.04, 3.01, 3.02, 3.03];

然后,为了将普通的 JS 数组转换为可以提供给 GPU 的强类型浮点数组,我们只需使用 Float32Array 构造函数,它可以将普通的 JS 数字数组作为输入:

pixels = new Float32Array(pixels);

现在我们已经将我们的纹理表示为一个强类型的浮点数组,我们可以使用 texImage2D 使用已经建立的 WebGL 上下文(它正在工作并且超出了这个问题的范围)将它提供给 GPU:

gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 3, 1, 0, gl.RGBA, gl.FLOAT, pixels);

进行适当的渲染调用表明这些浮点数被传入和传出(通过将输出浮点数编码为片段颜色)GPU 没有错误(尽管由于转换导致精度略有损失)。

问题

从一个普通的 JS 数组转换为 Float32Array 实际上是一个非常昂贵的操作,而且在 Float32Array 中操作已经转换的浮点数要快得多——根据大多数 JS 规范似乎都支持这种操作:https://developer.mozilla.org/en-US/docs/Web/API/Float32Array

一旦建立,您可以使用对象的方法或使用标准数组索引语法(即使用括号表示法)引用数组中的元素。

问题发生在以下情况:

一个 Float32Array 是用一个带有预设值的普通 JS 数组创建的

我们使用 [] 表示法更改 Float32Array 中的一个或多个值,即:

pixels[0] = 420.4;
pixels[1] = 420.4;

我们使用 texImage2D 将 Float32Array 传递给 GPU,并且使用上面提到的相同方法表明,最初为 Float32Array 设置的值以某种方式到达了 GPU,而这两个值没有更改为 420.4

WTF?

我最好的猜测是,因为强类型数组(通常)在内部表示为缓冲区和视图,所以我正在更新视图,而缓冲区没有反映更改。将 Float32Array 记录到浏览器控制台表明,在这种情况下,两个更改的数字似乎确实被更改了。但是,由于无法通过 Chrome 中的控制台读取 ArrayBuffer 的内容,因此就我的技能而言,这是一个调试死胡同。

使用 NodeJS REPL 尝试相同的创建、转换、更新和检查方法(不涉及 GPU),发现缓冲区值在评估 pixels[0] = 420.4; 期间更新,并且在以下情况下不会以“惰性”方式更新缓冲区被读取。

Chrome 可能会延迟更新底层缓冲区,但将该数据复制到 GPU 不会触发 getter,而是从内存中原始复制。

临时解决方案

在发现并解决潜在问题(如果适用)之前,Float32Arrays 似乎在 WebGL 纹理的上下文中创建后本质上是不可变的(无法更改)。似乎还有一个 .set() 方法附加到类型化数组,但是:

 pixels.set(new Float32Array([420.4]), index);

似乎需要进行大量外部装箱/转换来绕过惰性缓冲区,尤其是声称允许 [] 访问的缓冲区。

【问题讨论】:

    标签: javascript arrays google-chrome webgl typed-arrays


    【解决方案1】:

    包括 Float32Arrays 在内的类型化数组只是压缩数组(想想 C/C++)。更新它们是即时的。如果您想在 GPU 上查看数据,则必须使用 texImage2D 再次上传数据,否则,没有魔法,没有疯狂的缓冲,非常简单。如果您了解 C/C++,它在功能上等同于

    void* arrayBuffer = malloc(sizeOfBuffer);
    float* viewAsFloat = (float*)arrayBuffer;
    

    类型化数组不是 JS 数组的视图。使用原生 JS 数组来初始化类型化数组只是初始化类型化数组的一种便捷方式。一旦创建,TypedArray 就是一个新数组。

    你可以在同一个 ArrayBuffer 中获取多个 ArrayBuffer 视图。

    例子

    var b = new ArrayBuffer(16);  // make a 16 byte ArrayBuffer
    var bytes = new Uint8Array(b);  // make a Uint8Array view into ArrayBuffer
    var longs = new Uint32Array(b);  // make a Uint32Array view into ArrayBuffer
    var floats = new Float32Array(b);  // make a Float32Array view into ArrayBuffer
    
    // print the contents of the views
    console.log(bytes);
    console.log(longs);
    console.log(floats);
    
    // change a byte using one of the views
    bytes[1] = 255;
    
    // print the contents again
    console.log(bytes);
    console.log(longs);
    console.log(floats);
    

    将所有代码复制并粘贴到您的 JavaScript 控制台中。您应该会看到类似

    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 
    [0, 0, 0, 0] 
    [0, 0, 0, 0] 
    [0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 
    [65280, 0, 0, 0] 
    [9.147676375112406e-41, 0, 0, 0] 
    

    注意:在同一个数组缓冲区上使用不同类型的多个视图不是跨平台兼容的。换句话说,你会在大端平台和小端平台上得到不同的结果。目前还没有流行的大端平台支持 TypedArrays 的浏览器,所以你可以忽略这个问题,尽管你的页面可能会在未来的某个平台上中断。如果您想以独立于平台的方式读取/写入数据,您应该使用DataView。否则,在同一个缓冲区上使用多个视图的主要目的是上传打包的顶点数据,例如用 uint32 RGBA 颜色打包的浮点位置。在这种情况下,它将跨平台工作,因为您不会使用视图读取/写入相同的数据。

    正如所指出的,JS 原生数组和 TypedArrays 没有关系,只是你可以使用 JS 原生数组来初始化 TypedArray

    var jsArray = [1, 2, 3, 4];
    var floats = new Float32Array(jsArray);  // this is a new array, not a view.
    
    console.log(jsArray);
    console.log(floats);
    
    jsArray[1] = 567;  // effects only the JS array
    
    console.log(jsArray);
    console.log(floats);
    
    floats[2] = 89;  // effects only the float array
    
    console.log(jsArray);
    console.log(floats);
    

    我得到的粘贴到控制台

    [1, 2, 3, 4] 
    [1, 2, 3, 4] 
    [1, 567, 3, 4] 
    [1, 2, 3, 4] 
    [1, 567, 3, 4] 
    [1, 2, 89, 4] 
    

    请注意,您可以从任何类型的数组中获取底层 ArrayBuffer。

    var buffer = floats.buffer;
    

    并创建新视图

    var longs = new Uint8Array(buffer);
    console.log(longs);
    
    // prints [0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 178, 66, 0, 0, 128, 64] 
    

    您还可以创建覆盖部分缓冲区的视图。

    var offset = 8; // Offset is in bytes
    var length = 2; // Length is in units of type
    
    // a buffer that looks at the last 2 floats
    var f2 = new Float32Array(buffer, offset, length); 
    console.log(f2);
    
    // prints [89, 4]
    

    至于纹理和类型化数组,这里有一个 sn-p,它使用 Float32Array 来更新浮点纹理。

    main();
    function main() {
      var canvas = document.getElementById("canvas");
      var gl = canvas.getContext("webgl");
      if (!gl) {
        alert("no WebGL");
        return;
      }
      var f = gl.getExtension("OES_texture_float");
      if (!f) {
        alert("no OES_texture_float");
        return;
      }
    
      var program = twgl.createProgramFromScripts(
        gl, ["2d-vertex-shader", "2d-fragment-shader"]);
      gl.useProgram(program);
    
      var positionLocation = gl.getAttribLocation(program, "a_position");
      var resolutionLocation = gl.getUniformLocation(program, "u_resolution");
      gl.uniform2f(resolutionLocation, canvas.width, canvas.height);
    
      var buffer = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
        -1, -1, 1, -1, -1, 2,
        -1,  1, 1, -1,  1, 1]), gl.STATIC_DRAW);
      gl.enableVertexAttribArray(positionLocation);
      gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
    
      var tex = gl.createTexture();
      gl.bindTexture(gl.TEXTURE_2D, tex);
      var width = 64;
      var height = 64;
      var pixels = new Float32Array(width * height * 4);
      for (var y = 0; y < height; ++y) {
        for (var x = 0; x < width; ++x) {
          var offset = (y * width + x) * 4;
          pixels[offset + 0] = (x * 256 / width) * 1000;
          pixels[offset + 1] = (y * 256 / height) * 1000;
          pixels[offset + 2] = (x * y / (width * height)) * 1000;
          pixels[offset + 3] = 256000;
        }
      }
      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.FLOAT,
                    pixels);
      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.NEAREST);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    
      function randInt(range) {
        return Math.floor(Math.random() * range);
      }
    
      function render() {
        // update a random pixel
        var x = randInt(width);
        var y = randInt(height);
        var offset = (y * width + x) * 4;
        pixels[offset + 0] = randInt(256000);
        pixels[offset + 1] = randInt(256000);
        pixels[offset + 2] = randInt(256000);
    
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.FLOAT,
                      pixels);
    
        gl.drawArrays(gl.TRIANGLES, 0, 6);
        requestAnimationFrame(render);
      }
      render();
    }
    <script src="https://twgljs.org/dist/2.x/twgl.min.js"></script>
    <script id="2d-vertex-shader" type="x-shader/x-vertex">
    attribute vec2 a_position;
    void main() {
        gl_Position = vec4(a_position, 0, 1);
    }
    </script>
    <script id="2d-fragment-shader" type="x-shader/x-fragment">
    precision mediump float;
    uniform vec2 u_resolution;
    uniform sampler2D u_tex;
    void main() {
        vec2 texCoord = gl_FragCoord.xy / u_resolution;
        vec4 floatColor = texture2D(u_tex, texCoord);
        gl_FragColor = floatColor / 256000.0;  
    }
    </script>
    <canvas id="canvas" width="400" height="300"></canvas>

    所有这些都表明您看到的问题可能与其他问题有关?至于调试,正如上面指出的,ArrayBuffers 非常简单,没有惰性缓冲或任何东西。因此,如果您想查看 ArrayBuffer,请为它创建一个视图,以便调试器通过某种方式知道您想要显示的内容。

    【讨论】:

    • 回答得很漂亮,非常感谢!在重构为几个独立的测试之后,它似乎不是有问题的类型化数组,而是我可能在某处有一个错误的顶点或纹理坐标 - 导致 p0.r 的更改反映在 p345808.r 附近的某处
    • @gman: ..no popular little endian platforms with browsers that support TypeArrays... 你是说这里的大端吗?
    • 哇!哈哈,是的?
    猜你喜欢
    • 2019-03-29
    • 1970-01-01
    • 2017-07-13
    • 1970-01-01
    • 2020-07-17
    • 1970-01-01
    • 1970-01-01
    • 2020-11-16
    • 1970-01-01
    相关资源
    最近更新 更多