javascript - Float32Array 在与 WebGL (Chrome) 交互时不更新 ArrayBuffer 以反射(reflect)分配的值

标签 javascript arrays google-chrome webgl typed-arrays

为了使用 OES_texture_float 扩展在 chrome 中使用 WebGL 手动打包 RGBA 浮点分量纹理并将其绑定(bind)到 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 的强类型 float 组,我们只需使用 Float32Array 构造函数,它可以将普通 JS 数字数组作为输入:

pixels = new Float32Array(pixels);

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

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

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

问题

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

Once established, you can reference elements in the array using the object's methods, or using standard array index syntax (that is, using bracket notation).

问题发生在:

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

我们使用 [] 符号更改 Float32Array 中的一个或多个值,即:

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

我们使用 texImage2D 将 Float32Array 传递给 GPU,并使用上面提到的相同方法表明 Float32Array 的初始设置值以某种方式在没有将两个值更改为 420.4 的情况下将其传递给 GPU

WTF?

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

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

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

临时解决方案

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

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

似乎有很多外部装箱/转换来绕过惰性缓冲区,尤其是声称允许 [] 访问的缓冲区。

最佳答案

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

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

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

不过,您可以将多个 ArrayBuffer View 放入同一个 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] 

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

正如所指出的,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;

并创建新 View

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] 

您还可以创建覆盖缓冲区一部分的 View 。

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]

至于纹理和类型化数组,这里有一段使用 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,请为它创建一个 View ,以便调试器可以通过某种方式知道您想要显示的内容。

关于javascript - Float32Array 在与 WebGL (Chrome) 交互时不更新 ArrayBuffer 以反射(reflect)分配的值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20886257/

相关文章:

javascript - Babel 可以在生成的代码中包含 regeneratorRuntime 依赖项吗?

javascript - 来自多维数组的全日历事件数据

c - 现场程序错误但不在代码块上

javascript onclick 警报在 chrome 中不起作用

php - Chrome 上奇怪的 DIV 重新定义

javascript - 为什么数组未定义?

Javascript:循环后打开 URL 给出正确答案

javascript - 数组部分 JavaScript?

arrays - bash 。最快最有效的数组搜索

javascript - 目录中的文件夹未显示在本地服务器 html 中