如何使用像素缓冲区对象在 WebGL2 中上传纹理?
How can I upload a texture in WebGL2 using a Pixel Buffer Object?
目前,使用 texImage2d
上传大型 4096x4096 纹理非常慢,在将纹理发送到 GPU 时锁定主线程,最终导致卡顿。
据我了解,WebGL2 能够使用 PBO(像素缓冲区对象)以更高效的方式在 GPU 上创建纹理。但是,我无法在网上找到任何有关如何执行此操作的示例。
我在 OpenGL 中找到了关于如何实现这一点的很好的描述,但我不确定如何继续使用 WebGL API。
我想使用 Canvas
或 ImageBitmap
作为纹理数据的来源。
到目前为止,我通过将纹理绘制为 canvas,然后使用 canvas.toBlob()
将图像转换为 arrayBuffer
进行测试,然后使用 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);
但是这个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>
目前,使用 texImage2d
上传大型 4096x4096 纹理非常慢,在将纹理发送到 GPU 时锁定主线程,最终导致卡顿。
据我了解,WebGL2 能够使用 PBO(像素缓冲区对象)以更高效的方式在 GPU 上创建纹理。但是,我无法在网上找到任何有关如何执行此操作的示例。
我在 OpenGL 中找到了关于如何实现这一点的很好的描述,但我不确定如何继续使用 WebGL API。
我想使用 Canvas
或 ImageBitmap
作为纹理数据的来源。
到目前为止,我通过将纹理绘制为 canvas,然后使用 canvas.toBlob()
将图像转换为 arrayBuffer
进行测试,然后使用 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);
但是这个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
, andUnmapBuffer
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
, andMAP_UNSYNCHRONIZED_BIT
.Instead of using
MapBufferRange
, buffer data may be read by using thegetBufferSubData
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>