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



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

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

我已经找到了如何在OpenGL中实现这一目标的良好描述,但不确定如何使用WebGL API。

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

到目前为止,我正在通过将纹理绘制到画布上进行测试,然后使用canvas.toBlob()后跟FileReaderreadAsArrayBuffer将图像转换为arrayBuffer。 然后,一旦我实际拥有有效的缓冲区,我就会尝试创建 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);

但这会返回错误:

GL_INVALID_OPERATION : glTexImage2D: pixel unpack buffer is not large enough

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

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

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

。从规格

MapBufferRange,特别是其只读和只写模式, 不能安全地暴露在 JavaScript 中。GetBufferSubData替换它以从 GPU 取回数据。

5.14 无地图缓冲区范围

MapBufferRangeFlushMappedBufferRangeUnmapBuffer入口点将从WebGL 2.0 API中删除。以下枚举值也将被删除:BUFFER_ACCESS_FLAGSBUFFER_MAP_LENGTHBUFFER_MAP_OFFSETMAP_READ_BITMAP_WRITE_BITMAP_INVALIDATE_RANGE_BITMAP_INVALIDATE_BUFFER_BITMAP_FLUSH_EXPLICIT_BITMAP_UNSYNCHRONIZED_BIT

缓冲区数据可以使用getBufferSubData入口点读取,而不是使用MapBufferRange

要上传 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>

最新更新