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

时间:2017-04-20 21:17:47

标签: webgl2

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

根据我的阅读,WebGL2能够使用PBO(Pixel Buffer Objects)以更有效的方式在GPU上创建纹理。但是,我无法在网上找到任何有关如何操作的例子。

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

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

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

我真的不知道我是否正确接近它,所以任何帮助都会非常感激。

1 个答案:

答案 0 :(得分:4)

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

原生OpenGL ES可以更快的原因是因为你可以调用glMapBufferRange将PBO映射到进程的内存中,但是没有办法在浏览器中高效安全地执行此操作,因此没有{{ WebGL2中的1}}

来自规范

gl.mapBufferRange

  

5.14没有MapBufferRange

     

从WebGL 2.0 API中删除// 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. MapBufferRangeFlushMappedBufferRange入口点。还删除了以下枚举值:UnmapBufferBUFFER_ACCESS_FLAGSBUFFER_MAP_LENGTHBUFFER_MAP_OFFSETMAP_READ_BITMAP_WRITE_BITMAP_INVALIDATE_RANGE_BITMAP_INVALIDATE_BUFFER_BITMAP_FLUSH_EXPLICIT_BIT

     

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

对于上传4096x4096纹理,可以考虑制作一个空纹理(将getBufferSubData传递给null然后使用texImage2D每帧上传一部分纹理以避免任何口吃?

至于问题本身,通过PBO上传纹理数据就是使用texSubImage2d将数据复制到PBO。

gl.bufferData
const vs = `#version 300 es
in vec4 position;
out vec2 v_texcoord;
void main() {
  gl_Position = position;  
  v_texcoord = position.xy * .5 + .5;
}
`;

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

in vec2 v_texcoord;

uniform sampler2D u_tex;

out vec4 outColor;

void main() {
  // twizzle colors to show we went through shader
  outColor = texture(u_tex, v_texcoord).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.translate(150, 75);
ctx.rotate(Math.PI * .25);
ctx.fillStyle = "red";
ctx.fillRect(-50, -50, 100, 100);
ctx.lineWidth = 20;
ctx.strokeStyle = "yellow";
ctx.strokeRect(-50, -50, 100, 100);

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);
              
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData for each array
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
 position: [
  -1, -1, 0,
   1, -1, 0,
  -1,  1, 0,
  -1,  1, 0,
   1, -1, 0,
   1,  1, 0,
 ],
});

gl.useProgram(programInfo.program)

// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);

// calls gl.activeTexture, gl.bindTexture, gl.uniform?
twgl.setUniforms(programInfo, {
  u_tex: tex,
});

// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, bufferInfo);
canvas { border: 1px solid black; }