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。

旧答案

遗憾的是,除了通过 texImage2DtexSubImage2D

之外,无法将视频上传到 WebGL

一些浏览器试图使这种情况发生得更快。我注意到您正在使用 gl.LUMINANCE。您可以尝试使用 gl.RGBgl.RGBA 并查看速度是否加快。浏览器可能只针对更常见的情况进行优化。另一方面,他们可能根本没有优化。

已经提出了两个允许在没有副本的情况下使用视频的扩展,但 AFAIK 没有浏览器实现它们。

WEBGL_video_texture

WEBGL_texture_source_iframe

这实际上是一个比听起来更难的问题。

  • 视频数据可以有多种格式。你提到了 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;除了通过 texImage2DtexSubImage2D

之外,无法将视频上传到 WebGL