作者 - Oasis 团队-月木
虽然 WebGL 支持 压缩纹理,上传 GPU 不存在解码耗时的问题,但日常应用中还是会用到 png/jpg/webp 等压缩过的图片格式。这些格式在 WebGL 中渲染需要转换成位图,即每个像素使用 RGB 或 RGBA 表示。这个过程称为图片解码。图片解码在渲染中是非常重要的一环,若直接使用 Image 对象上传(texImage2D)至 GPU,往往耗时较长,阻塞主线程,比如说会导致动画播放卡顿,影响用户体验。所以,在这里我们对浏览器中的一些 WebGL 中图片解码的方案做了一些研究和测试。 第一幅图是同步解码,第二幅图是异步解码,可以看到明显缓解动画的卡顿
本文重点测试的是 Image.decode 方法和 createImageBitmap 方法。
Image.decode
Image.decode
可以异步对 Image
进行解码,异步的解码不会阻塞主线程动画和交互。使用方法如下:
const img = new Image();
img.src = '...';
img.decode().then(function() {
document.body.appendChild(img);
});
createImageBitmap
ImageBitmap 是专门为 Canvas 和 WebGL 渲染使用的一种数据格式。createImageBitmap
会异步返回一个含 ImageBitmap 对象的 Promise。createImageBitmap
可以在 worker 中使用,ImageBitmap 也可以在 worker 之间传输。createImageBitmap 接受多种数据源,本文重点测试 Blob 和 HTMLImageElement,这两种对象在渲染引擎中最常使用。
// 使用 image 作为源
createImageBitmap(image).then((imageBitmap)=>{
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, imageBitmap);
})
// 使用 blob 作为源
createImageBitmap(blob).then((imageBitmap)=>{
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, imageBitmap);
})
性能测试
上面介绍完了两个异步解码 API 的基本使用,接下去我用 5 种方式对 100 张不同的 1024 * 1024 图(图片由脚本随机生成)进行解码测试,对比图片的解码时间和纹理上传时间。五种方式如下:
- 使用 Image 作为源用 createImageBitmap 方法。(示例)
- 使用 Blob 作为源使用 createImageBitmap 。(示例)
- 开启 5 个 worker 使用 createImageBitmap 方法。(示例)
- 使用 image.decode 进行解码。(示例)
- 使用 image 直接上传纹理。(示例)
进过上面几项测试得出结果(上下浮动 100ms 左右):
1. MacOS(2.6 GHz i7 chrome 87 降低 6 倍性能)
使用方法 | 解码时间(毫秒) | 纹理上传时间(毫秒) | 总时间 | 备注 | createImageBitmap(Image) | 2625 | 2967 | 5592 | 异步解码 | createImageBitmap(Blob) | 559 | 2180 | 2739 | 异步解码 | createImageBitmap(Blob) + worker | 210 | 2000 | 2210 | 异步 + 多线程解码 | image 直接上传 | 3020 | 3020 | 同步解码 | image.decode 后上传 | 210 | 4978 | 5188 | 异步解码 |
---|
2. Android U4(Mi 10 Pro U4 3.21.0.172)
使用方法 | 解码时间(毫秒) | 纹理上传时间(毫秒) | 总时间 | 备注 | createImageBitmap(Image) | 1540 | 878 | 2418 | 异步解码 | createImageBitmap(Blob) | 1096 | 129 | 1225 | 异步解码 | createImageBitmap(Blob) + worker | 715 | 142 | 857 | 异步 + 多线程解码 | image 直接上传 | 905 | 905 | 同步解码 | decode 报错,The source image cannot be decoded. | 异步解码 |
---|
3. Android Chrome(Mi 10 Pro Android Chrome 87)
使用方法 | 解码时间(毫秒) | 纹理上传时间(毫秒) | 总时间 | 备注 | createImageBitmap(Image) | 522 | 504 | 1026 | 异步解码 | createImageBitmap(Blob) | 310 | 135 | 445 | 异步解码 | createImageBitmap(Blob) + worker | 249 | 145 | 394 | 异步 + 多线程解码 | image 直接上传 | 510 | 510 | 同步解码 | decode 报错,The source image cannot be decoded. | 异步解码 |
---|
4. iOS safari(iPhone7 iOS 14.2)
使用方法 | 解码时间(毫秒) | 纹理上传时间(毫秒) | 总时间 | 备注 | 不支持 | 不支持 | 不支持 | image 直接上传 | 1076 | 1076 | 同步解码 | image.decode 后上传 | 2076 | 300 | 2376 | 异步解码 |
---|
结论
通过以上测试,可以得出以下结论:
- Android 和 Mac Chrome 推荐用
createImageBitmap
,数据源务必使用Blob
,解码可以提升 10% 左右的性能:- 若数据源使用
Blob
,无解码时间;若数据源使用Image
,有两次时间消耗,首先创建 bitmap 耗时很长,其次在 performance 里查看仍有解码时间(预期不该有解码时间,这是 Chrome 的 Bug,已经给 chromium 提了一个 issue,chrome 官方已经确认问题存在)。 - 在 worker 中调用
createImageBitmap
可以利用多线程能力,能进一步提升 15% 左右的性能。因为 worker 线程还不算特别稳定,是否开启 worker 解码交由用户配置决定,用户根据当前 cpu 负载及所需解码数量和业务场景去决定是否使用 worker 解码。
- 若数据源使用
- iOS 不要用任何异步解码方案:
- 不支持
createImageBitmap
; - 使用
Image.decode
的总时间是同步解码的两倍;
- 不支持
根据上面测试的结果以及推导的结论,在 WebGL 中采取的图片请求最佳解码方案是:
以上方案即将应用到 oasis-engine 中,欢迎大家在 PR 中讨论。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!