Webgl 在没有绘制调用的情况下将纹理数据上传到 gpu
Webgl Upload Texture Data to the gpu without a draw call
我正在使用 webgl 在自定义视频编解码器上将 YUV 转换为 RGB。
视频必须以每秒 30 帧的速度播放。为了实现这一点,我每隔一个 requestAnimationFrame 都会进行所有计算。
效果很好,但我在分析时注意到将纹理上传到 GPU 所花费的时间最长。
所以我分别上传了"Y"贴图和"UV"贴图。
现在第一个 "requestAnimationFrame" 将像这样上传 "Y" 纹理:
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, yTextureRef);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, textureWidth, textureHeight, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, yData);
第二个 "requestAnimationFrame" 将以相同的方式上传 "UV" 纹理,并对片段着色器进行绘制调用,在它们之间进行数学计算。
但这不会改变探查器中的任何内容。我在上传 "Y" 纹理的帧上显示的 gpu 时间几乎为 0,而在上传 "UV" 纹理的帧上显示的时间与之前相同。
但是,如果我向我的 "Y" 纹理上传函数添加绘图调用,那么探查器会显示预期的结果。每一帧都有将近一半的gpu时间。
由此我猜测 Y 纹理并没有真正使用 texImage2d 函数上传到 gpu。
但是我真的不想在屏幕上绘制 Y 纹理,因为它没有正确的 UV 纹理来做任何事情,直到一帧之后。那么有没有办法强制gpu上传这个纹理而不执行绘制调用?
更新
我mis-understood问题
这真的取决于driver。问题是 OpenGL/OpenGL ES/WebGL 的纹理 API 真的很糟糕。 Sucks 是 'has unintended consequences' 的技术术语。
问题是 driver 在您绘制之前无法真正完全上传数据,因为它不知道您要更改哪些内容。您可以按任何顺序和任何大小更改所有 mip 级别,然后将它们全部修复,直到您绘制它不知道您将调用哪些其他函数来操纵纹理。
考虑创建一个 4x4 级别 0 mip
gl.texImage2D(
gl.TEXTURE_2D,
0, // mip level
gl.RGBA,
4, // width
4, // height
...);
它应该分配什么内存? 4(宽度)* 4(高度)* 4(rgba)?但是如果你调用 gl.generateMipmap
呢?现在需要 4*4*4+2*2*4+1*1*4。好的,但是现在您在级别 3 上分配了一个 8x8 mip。您打算分别用 64x64、32x32、16x16 替换级别 0 到 2,但您首先执行了级别 3。如果你先把level 3换掉,再把上面的level换掉,应该怎么办?然后添加第 4 级 8x8、第 5 级为 4x4、第 6 级为 2x2、第 7 级为 1x1。
如您所见,API 允许您以任何顺序更改 mip。事实上,我可以将级别 7 分配为 723x234,然后稍后修复它。 API 设计为直到绘制时间才关心,此时所有 mip 必须是正确的大小,此时它们最终可以在 GPU 上分配内存并将 mip 复制进去。
你可以看到这个问题的演示和测试here。测试上传 mips 是为了验证 WebGL 实现正确地失败,因为它们的大小不正确,并且一旦它们的大小正确就正确地开始工作。
你可以看到这可以说是一个糟糕的 API 设计。
他们添加了 gl.texStorage2D
来修复它,但是 gl.texStorage2D
在 WebGL1 中不可用,仅在 WebGL2 中可用。 gl.texStorage2D
虽然有新问题:(
TLDR;当您调用 gl.texImage2D
时,纹理会上传到 driver,但 driver 直到绘制时间才能上传到 GPU。
可能的解决方案:使用 gl.texSubImage2D
因为它不分配内存所以 driver 可以更快地上传。我怀疑大多数 driver 不会,因为您可以在绘图前使用 gl.texSubImage2D
。还是值得一试
我还要补充一点,gl.LUMIANCE
也可能是一个瓶颈。 IIRC DirectX 没有相应的格式,OpenGL Core Profile 也没有。两者都支持仅 RED 格式,但 WebGL1 不支持。所以必须通过扩展上传数据来模拟 LUMIANCE。
旧答案
遗憾的是,除了通过 texImage2D
和 texSubImage2D
之外,无法将视频上传到 WebGL
一些浏览器试图使这种情况发生得更快。我注意到您正在使用 gl.LUMINANCE
。您可以尝试使用 gl.RGB
或 gl.RGBA
并查看速度是否加快。浏览器可能只针对更常见的情况进行优化。另一方面,他们可能根本没有优化。
已经提出了两个允许在没有副本的情况下使用视频的扩展,但 AFAIK 没有浏览器实现它们。
这实际上是一个比听起来更难的问题。
视频数据可以有多种格式。你提到了 YUV,但还有其他的。浏览器应该告诉应用程序格式还是浏览器应该转换为标准格式?
告诉的问题是很多开发者会弄错,然后用户会提供他们不支持的格式的视频
WEBGL_video_texture
扩展通过 re-writing 您的着色器转换为标准格式。你告诉它 uniform samplerVideoWEBGL video
然后它知道它可以 re-write 你的 color = texture2D(video, uv)
到 color = convertFromVideoFormatToRGB(texture(video, uv))
。这也意味着如果您播放不同格式的视频,他们必须 re-write 动态着色器。
同步
将视频数据传输到 WebGL 听起来很棒,但现在您遇到了一个问题,即在您获取数据并将其呈现到屏幕上时,您已经添加了几帧延迟,因此音频不再同步。
如何处理这个问题超出了 WebGL 的范围,因为 WebGL 与音频没有任何关系,但它确实指出它并不像仅向 WebGL 提供数据那么简单。一旦您使数据可用,人们就会要求更多 API 来获取音频和更多信息,以便他们可以延迟其中一个或两者并使它们保持同步。
TLDR;除了通过 texImage2D
和 texSubImage2D
之外,无法将视频上传到 WebGL
我正在使用 webgl 在自定义视频编解码器上将 YUV 转换为 RGB。
视频必须以每秒 30 帧的速度播放。为了实现这一点,我每隔一个 requestAnimationFrame 都会进行所有计算。
效果很好,但我在分析时注意到将纹理上传到 GPU 所花费的时间最长。
所以我分别上传了"Y"贴图和"UV"贴图。
现在第一个 "requestAnimationFrame" 将像这样上传 "Y" 纹理:
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, yTextureRef);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, textureWidth, textureHeight, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, yData);
第二个 "requestAnimationFrame" 将以相同的方式上传 "UV" 纹理,并对片段着色器进行绘制调用,在它们之间进行数学计算。
但这不会改变探查器中的任何内容。我在上传 "Y" 纹理的帧上显示的 gpu 时间几乎为 0,而在上传 "UV" 纹理的帧上显示的时间与之前相同。
但是,如果我向我的 "Y" 纹理上传函数添加绘图调用,那么探查器会显示预期的结果。每一帧都有将近一半的gpu时间。
由此我猜测 Y 纹理并没有真正使用 texImage2d 函数上传到 gpu。
但是我真的不想在屏幕上绘制 Y 纹理,因为它没有正确的 UV 纹理来做任何事情,直到一帧之后。那么有没有办法强制gpu上传这个纹理而不执行绘制调用?
更新
我mis-understood问题
这真的取决于driver。问题是 OpenGL/OpenGL ES/WebGL 的纹理 API 真的很糟糕。 Sucks 是 'has unintended consequences' 的技术术语。
问题是 driver 在您绘制之前无法真正完全上传数据,因为它不知道您要更改哪些内容。您可以按任何顺序和任何大小更改所有 mip 级别,然后将它们全部修复,直到您绘制它不知道您将调用哪些其他函数来操纵纹理。
考虑创建一个 4x4 级别 0 mip
gl.texImage2D(
gl.TEXTURE_2D,
0, // mip level
gl.RGBA,
4, // width
4, // height
...);
它应该分配什么内存? 4(宽度)* 4(高度)* 4(rgba)?但是如果你调用 gl.generateMipmap
呢?现在需要 4*4*4+2*2*4+1*1*4。好的,但是现在您在级别 3 上分配了一个 8x8 mip。您打算分别用 64x64、32x32、16x16 替换级别 0 到 2,但您首先执行了级别 3。如果你先把level 3换掉,再把上面的level换掉,应该怎么办?然后添加第 4 级 8x8、第 5 级为 4x4、第 6 级为 2x2、第 7 级为 1x1。
如您所见,API 允许您以任何顺序更改 mip。事实上,我可以将级别 7 分配为 723x234,然后稍后修复它。 API 设计为直到绘制时间才关心,此时所有 mip 必须是正确的大小,此时它们最终可以在 GPU 上分配内存并将 mip 复制进去。
你可以看到这个问题的演示和测试here。测试上传 mips 是为了验证 WebGL 实现正确地失败,因为它们的大小不正确,并且一旦它们的大小正确就正确地开始工作。
你可以看到这可以说是一个糟糕的 API 设计。
他们添加了 gl.texStorage2D
来修复它,但是 gl.texStorage2D
在 WebGL1 中不可用,仅在 WebGL2 中可用。 gl.texStorage2D
虽然有新问题:(
TLDR;当您调用 gl.texImage2D
时,纹理会上传到 driver,但 driver 直到绘制时间才能上传到 GPU。
可能的解决方案:使用 gl.texSubImage2D
因为它不分配内存所以 driver 可以更快地上传。我怀疑大多数 driver 不会,因为您可以在绘图前使用 gl.texSubImage2D
。还是值得一试
我还要补充一点,gl.LUMIANCE
也可能是一个瓶颈。 IIRC DirectX 没有相应的格式,OpenGL Core Profile 也没有。两者都支持仅 RED 格式,但 WebGL1 不支持。所以必须通过扩展上传数据来模拟 LUMIANCE。
旧答案
遗憾的是,除了通过 texImage2D
和 texSubImage2D
一些浏览器试图使这种情况发生得更快。我注意到您正在使用 gl.LUMINANCE
。您可以尝试使用 gl.RGB
或 gl.RGBA
并查看速度是否加快。浏览器可能只针对更常见的情况进行优化。另一方面,他们可能根本没有优化。
已经提出了两个允许在没有副本的情况下使用视频的扩展,但 AFAIK 没有浏览器实现它们。
这实际上是一个比听起来更难的问题。
视频数据可以有多种格式。你提到了 YUV,但还有其他的。浏览器应该告诉应用程序格式还是浏览器应该转换为标准格式?
告诉的问题是很多开发者会弄错,然后用户会提供他们不支持的格式的视频
WEBGL_video_texture
扩展通过 re-writing 您的着色器转换为标准格式。你告诉它uniform samplerVideoWEBGL video
然后它知道它可以 re-write 你的color = texture2D(video, uv)
到color = convertFromVideoFormatToRGB(texture(video, uv))
。这也意味着如果您播放不同格式的视频,他们必须 re-write 动态着色器。同步
将视频数据传输到 WebGL 听起来很棒,但现在您遇到了一个问题,即在您获取数据并将其呈现到屏幕上时,您已经添加了几帧延迟,因此音频不再同步。
如何处理这个问题超出了 WebGL 的范围,因为 WebGL 与音频没有任何关系,但它确实指出它并不像仅向 WebGL 提供数据那么简单。一旦您使数据可用,人们就会要求更多 API 来获取音频和更多信息,以便他们可以延迟其中一个或两者并使它们保持同步。
TLDR;除了通过 texImage2D
和 texSubImage2D