如何使用像素缓冲区对象在 WebGL2 中上传纹理?

How can I upload a texture in WebGL2 using a Pixel Buffer Object?

目前,使用 texImage2d 上传大型 4096x4096 纹理非常慢,在将纹理发送到 GPU 时锁定主线程,最终导致卡顿。

据我了解,WebGL2 能够使用 PBO(像素缓冲区对象)以更高效的方式在 GPU 上创建纹理。但是,我无法在网上找到任何有关如何执行此操作的示例。

我在 OpenGL 中找到了关于如何实现这一点的很好的描述,但我不确定如何继续使用 WebGL API。

我想使用 CanvasImageBitmap 作为纹理数据的来源。

到目前为止,我通过将纹理绘制为 canvas,然后使用 canvas.toBlob() 将图像转换为 arrayBuffer 进行测试,然后使用 FileReaderreadAsArrayBuffer.然后,一旦我真正有了一个有效的缓冲区,我就会尝试创建 PBO 并上传它。

我的代码的相关部分如下所示:

  var buf = gl.createBuffer();
  var view = new Uint8Array(ArrayBuffer);

  gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, buf);
  gl.bufferData(gl.PIXEL_UNPACK_BUFFER, view, gl.STATIC_DRAW);

  gl.texImage2D(gl.TEXTURE_2D, 0, this.format, this.width, this.height, 0, this.format, this.type, 0);

但是这个returns错误:

GL_INVALID_OPERATION : glTexImage2D: pixel unpack buffer is not large enough

我真的不知道我是否正确地接近了它,所以任何帮助将不胜感激。

我可能是错的,但如果 WebGL 中用于上传数据的 PBO 比 texImage2D 快,我会感到惊讶。 PBO 本身存在于另一个进程中。要将数据传输到该进程,需要使用 gl.bufferData 将数据从 JavaScript 进程复制到 GPU 进程。在幕后,这两种方法的复制是相同的。

它在本机 OpenGL ES 中速度更快的原因是您可以调用 glMapBufferRange 将该 PBO 映射到您进程的内存中,但无法在浏览器中高效安全地执行此操作,因此没有gl.mapBufferRange WebGL2

来自规范

MapBufferRange, in particular its read-only and write-only modes, can not be exposed safely to JavaScript. GetBufferSubData replaces it for the purpose of fetching data back from the GPU.

5.14 No MapBufferRange

The MapBufferRange, FlushMappedBufferRange, and UnmapBuffer entry points are removed from the WebGL 2.0 API. The following enum values are also removed: BUFFER_ACCESS_FLAGS, BUFFER_MAP_LENGTH, BUFFER_MAP_OFFSET, MAP_READ_BIT, MAP_WRITE_BIT, MAP_INVALIDATE_RANGE_BIT, MAP_INVALIDATE_BUFFER_BIT, MAP_FLUSH_EXPLICIT_BIT, and MAP_UNSYNCHRONIZED_BIT.

Instead of using MapBufferRange, buffer data may be read by using the getBufferSubData entry point.

要上传 4096x4096 纹理,可以考虑制作一个空纹理(将 null 传递给 texImage2D,然后使用 texSubImage2d 每帧上传一部分纹理以避免任何卡顿?

就问题本身而言,通过PBO上传纹理数据就是使用gl.bufferData将数据复制到PBO。

const vs = `#version 300 es
void main() {
  gl_Position = vec4(0, 0, 0, 1);
  gl_PointSize = 128.0;
}
`;

const fs = `#version 300 es
precision mediump float;

uniform sampler2D u_tex;

out vec4 outColor;

void main() {
  // twizzle colors to show we went through shader
  outColor = texture(u_tex, gl_PointCoord.xy).gbra;
}
`;

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

// compiles shaders, links program, looks up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);

// make a 2d canvas
const ctx = document.createElement("canvas").getContext("2d");
ctx.arc(150, 75, 60, 0, Math.PI * 2);
ctx.fillStyle = "red";
ctx.fill();
ctx.lineWidth = 20;
ctx.strokeStyle = "yellow";
ctx.stroke();
ctx.fillStyle = "cyan";
ctx.font = "bold 48px sans-serif";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("FFFF", 150, 75);

const pbo = gl.createBuffer();
const data = ctx.getImageData(0, 0, 300, 150).data; 
gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, pbo);
gl.bufferData(gl.PIXEL_UNPACK_BUFFER, data, gl.STATIC_DRAW);
// data is now in PBO

const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
// take data from PBO
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 300, 150, 0, 
              gl.RGBA, gl.UNSIGNED_BYTE, 0);
gl.generateMipmap(gl.TEXTURE_2D);

// draw a single point, uniforms default to 0 so sampler
// will use texture unit 0
gl.useProgram(programInfo.program)
gl.drawArrays(gl.POINTS, 0, 1);
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas></canvas>