微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

上传大 JPEG 到 WebGL 纹理而不停止主线程

如何解决上传大 JPEG 到 WebGL 纹理而不停止主线程

我正在从服务器下载 JPEG 图像,我想将它们加载到 GPU 纹理中。如何在不停止主线程的情况下执行此操作?

我的代码如下:

  1. 使用 HTMLImageElement 下载 JPEG
  2. 使用 decode() 回调来确保在后台进行尽可能多的 JPEG 解码
  3. 创建一个画布,并将图像绘制到画布中
  4. 使用 getimageData() 从画布中提取 RGBA 缓冲区
  5. 使用 glTexImage2D 上传 RGBA 缓冲区(我使用的是 emscripten)

对于大纹理(在本例中为 5184 x 3456),纹理上传相对较快(例如 20 毫秒),但 getimageData() 非常慢(例如 400 毫秒)。

解决方法

以下解决方案适用于 Chrome。其他浏览器要么缺少 OffscreenCanvas,要么它们的 OffscreenCanvas 不支持“2d”上下文(至少在 2021 年 7 月 3 日是这样)。

您可以使用 Web Workers 解码 JPEG 并获取 ImageData 对象,这样主线程上唯一剩下的工作就是通过 glTexImage2D 上传到 GPU。

Web Worker 代码如下所示:

onmessage = function (ev) {
    let data = ev.data;
    if (data.type == "downloadBitmap") {
        fetch(data.url)
            .then(response => response.blob())
            .then(blob => createImageBitmap(blob))
            .then(bmp => {
                let canvas = new OffscreenCanvas(bmp.width,bmp.height);
                let cx = canvas.getContext('2d');
                cx.drawImage(bmp,0);
                let imgData = cx.getImageData(0,bmp.width,bmp.height);
                postMessage({ type: "downloadBitmap",url: data.url,imgData: imgData });
            },err => {
                postMessage({ type: "downloadBitmap",error: err });
            });
    }
}

如果您使用的是 emscripten,那么不幸的是,您需要在调用 glTexImage2D 之前将 ImageData 复制到 emscripten 堆中。但是,如果您有幸使用 Javascript webgl 界面,那么您可以上传 ImageData 对象而无需此额外副本。

有趣的一点是,理论上您可以直接从 ImageBitmap 转到 WebGL 纹理,但是在我的测试中,这仍然会导致主线程出现大量暂停。所以将ImageBitmap绘制到canvas中,然后使用getImageData()提取RGBA缓冲区的过程确实是很有必要的。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。