内存优化: 纹理压缩技术

本文涉及的产品
可视分析地图(DataV-Atlas),3 个项目,100M 存储空间
简介: 相比普通格式图片,纹理压缩可以节省大量显存和 CPU 解码时间,且对 GPU 友好。

1.gif

相比普通格式图片,纹理压缩可以节省大量显存和 CPU 解码时间,且对 GPU 友好。


背景


游戏开发中纹理是内存占用大户,移动设备因为内存有限,问题更加明显。据统计,淘宝互动小程序性能卡口 70% 以上都是因为内存超标,而内存超标的主要原因则是图片素材过多、过大等。


我们知道传统的图片文件格式有 PNG 、 JPEG 等,这种类型的图片格式无法直接被 GPU 读取,需要先经过 CPU 解码后再上传到 GPU 使用,解码后的数据以 RGB(A) 形式存储,无压缩。


而纹理压缩顾名思义是一种压缩的纹理格式,它通常会将纹理划分为固定大小的块(block)或者瓦片(tile),每个块单独进行压缩,整体显存占用更低,并且能直接被 GPU 读取和渲染(无需 CPU 解码),举例来说,一张1024x1024 的 JPEG 图片,使用RGBA格式,显存占用在 4M~5.3M 左右,而如果采用 ASTC_4x4 纹理压缩格式后,理论内存占用约在1.3M左右,相比普通纹理,可以减少70%+内存,具体数据见本文第三部分。


除此之外纹理压缩支持随机访问,随机访问是很重要的特性,因为纹理访问的模式高度随机,只有在渲染时被用到的部分才需要访问到,且无法提前预知其顺序。而且在场景中相邻的像素在纹理中不一定是相邻的 ,因此图形渲染性能高度依赖于纹理访问的效率。综上,相比普通格式图片,纹理压缩可以节省大量显存和 CPU 解码时间,且对 GPU 友好。


在WebGL上,我们可以通过相关 Extension 使用纹理压缩。纹理压缩的格式有很多种(详见下文),并且不同的厂商和机型支持的格式也不完全一致,因此使用压缩纹理前,需要判断设备是否支持。实际开发中,一般不会直接使用WebGL API加载压缩纹理,而是使用游戏引擎,目前主流的游戏引擎如 pixi.js 等均支持纹理压缩,开发者可以不用关心其中细节,只需要跟普通图片一样传入素材地址,剩下的都交给引擎来做。

纹理压缩不是银弹,虽然优势很多,但是其自身也有一些使用限制,主要有:


  • 有损压缩。所有的压缩纹理均为有损压缩,因此需要开发者 or 设计师验证压缩效果是否符合预期;
  • 尺寸要求。部分压缩纹理要求宽高相等(PVRTC),或者宽高必须是2的幂次方,使用有些不便;
  • 体积。压缩纹理虽然显存占用小,但是文件体积通常会比 JPEG 更大(看具体压缩格式),IO时间会更长;
  • 格式&兼容性问题。压缩纹理格式多样,需要针对不同平台选用不同格式,意味着同一份素材可能需要存储多份格式;


因此,是否需要使用压缩纹理需要开发者进行权衡,比如,游戏首帧资源我们通常希望越快约好,这时可以使用普通纹理,而对于非首帧资源或者出现内存瓶颈时则可以考虑使用纹理压缩。


主流纹理压缩格式、原理及兼容性情况


 纹理压缩格式


格式 WebGL扩展名 简介
ETC1 WEBGL_compressed_texture_etc1 ETC(Ericsson Texture Compression)是Khronos 开放标准,专利来自于瑞典爱立信公司,它在移动平台中广泛采用,是一种为感知质量设计的有损算法,它基于人眼对亮度而不是色度更敏感这一事实,在每个block定义四种不同的亮度偏移,即四种不同的颜色可用,可以认为这些颜色就是一个局部调色板,ETC会把4x4的像素块压缩成一个64或128位的数据块。ETC有两种压缩格式:ETC1和ETC2。纹理长宽必须是2的幂次方;
  • ETC1基本所有Android机型都支持,但是缺陷是不支持Alpha通道;
  • ETC2 是ETC1的扩展,向下兼容ETC1。支持Alpha,但是需要开启OpenGL ES 3.x。
image.gif
ETC2 WEBGL_compressed_texture_etc
ASTC WEBGL_compressed_texture_astc ASTC(Adaptive Scalable Texture Compression)是目前最强大的纹理压缩格式,由ARM & AMD研发。ASTC同样是基于block的压缩方式,但块的大小却较支持多种尺寸,比如从基本的4x4到12x12;每个块内的内容用128bits来进行存储,因而不同的块就对应着不同的压缩率;相比ETC,ASTC不要求长宽是2的幂次方。image.gif
PVRTC WEBGL_compressed_texture_pvrtc PVRTC(PowerVR Texture Compression),专为 PowerVR 图形核心系列设计,IOS平台都支持,它使用2张双线性放大的低分辨率图,根据精度和每个像素的权重,融合到一起来呈现纹理;PVRTC 2-bpp把一个8×4的像素单元组压成一个64位的数据块,压缩效果比较差;PVRTC 4-bpp把一个4×4的像素单元组压成一个64位的数据块;PVRTC压缩要求图片的大小必需是正方形而且边长必需是2的幂次方。image.gif
S3TC WEBGL_compressed_texture_s3tc S3TC(S3 Texture Compression)基本思想是把4x4的像素块压缩成一个64或128位的数据块,有损压缩。S3TC算法有五种变化DXT1-DXT5,一般在桌面设备上面使用,详见wiki



 压缩纹理素材生产


图片.png


如上所示,设计师产出png/jpeg等素材后,可以通过工具生成.ktx格式的压缩纹理素材,随后就可以在项目中直接使用了。不同格式的压缩纹理生产工具也不一样(PVETextTool、Adreno Texture Tool、...),而为了在各个平台中都能使用,通常需要生成不同格式的压缩纹理,社区有一些工具做了二次封装,可以生成多种格式的压缩纹理,如texture-compressor等。


 KTX文件格式


KTX(Khronos texture)是一种通用的纹理压缩存储格式,OpenGL(ES)、Vulkan等均支持,KTX文件中包含了纹理加载所需的所有参数及数据,比如format 、type、宽高等等,更多信息见wiki。


图片.png


如下是一个ktx文件的内容:


图片.pngimage.gif


基于这个格式,可以实现KTX Loader,用于解析KTX资源,生成纹理(通常游戏引擎会自带)。如下所示,读取KTX文件到ArrayBuffer然后解析拿到元信息:



// KhronosTextureContainer
constructor(arrayBuffer, facesExpected, baseOffset = 0) {
  this.arrayBuffer = arrayBuffer;
  this.baseOffset = baseOffset;
  // Test that it is a ktx formatted file, based on the first 12 bytes, character representation is:
  // '´', 'K', 'T', 'X', ' ', '1', '1', 'ª', '\r', '\n', '\x1A', '\n'
  // 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A
  const identifier = new Uint8Array(this.arrayBuffer, this.baseOffset, 12);
  if (identifier[0] !== 0xAB
      || identifier[1] !== 0x4B
      || identifier[2] !== 0x54
      || identifier[3] !== 0x58
      || identifier[4] !== 0x20
      || identifier[5] !== 0x31
      || identifier[6] !== 0x31
      || identifier[7] !== 0xBB
      || identifier[8] !== 0x0D
      || identifier[9] !== 0x0A
      || identifier[10] !== 0x1A
      || identifier[11] !== 0x0A) {
      return;
  }
  // load the reset of the header in native 32 bit uint
  const dataSize = Uint32Array.BYTES_PER_ELEMENT;
  const headerDataView = new DataView(this.arrayBuffer, this.baseOffset + 12, 13 * dataSize);
  const endianness = headerDataView.getUint32(0, true);
  const littleEndian = endianness === 0x04030201;
  this.glType = headerDataView.getUint32(1 * dataSize, littleEndian); // must be 0 for compressed textures
  this.glTypeSize = headerDataView.getUint32(2 * dataSize, littleEndian); // must be 1 for compressed textures
  this.glFormat = headerDataView.getUint32(3 * dataSize, littleEndian); // must be 0 for compressed textures
  this.glInternalFormat = headerDataView.getUint32(4 * dataSize, littleEndian); // the value of arg passed to gl.compressedTexImage2D(,,x,,,,)
  this.glBaseInternalFormat = headerDataView.getUint32(5 * dataSize, littleEndian); // specify GL_RGB, GL_RGBA, GL_ALPHA, etc (un-compressed only)
  this.pixelWidth = headerDataView.getUint32(6 * dataSize, littleEndian); // level 0 value of arg passed to gl.compressedTexImage2D(,,,x,,,)
  this.pixelHeight = headerDataView.getUint32(7 * dataSize, littleEndian); // level 0 value of arg passed to gl.compressedTexImage2D(,,,,x,,)
  this.pixelDepth = headerDataView.getUint32(8 * dataSize, littleEndian); // level 0 value of arg passed to gl.compressedTexImage3D(,,,,,x,,)
  this.numberOfArrayElements = headerDataView.getUint32(9 * dataSize, littleEndian); // used for texture arrays
  this.numberOfFaces = headerDataView.getUint32(10 * dataSize, littleEndian); // used for cubemap textures, should either be 1 or 6
  this.numberOfMipmapLevels = headerDataView.getUint32(11 * dataSize, littleEndian); // number of levels; disregard possibility of 0 for compressed textures
  this.bytesOfKeyValueData = headerDataView.getUint32(12 * dataSize, littleEndian); // the amount of space after the header for meta-data
  ...
}


 使用压缩纹理(WebGL)


在 WebGL 上使用纹理压缩主要有如下步骤:


  1. 下载纹理压缩素材;
  2. 解析ktx文件;
  3. 判断设备支持的纹理压缩格式;
  4. 通过getExtension获取纹理压缩扩展;
  5. 上传纹理压缩数据到GPU;


其中上传纹理主要指compressedTexImage2DcompressedTexImage3D两个API,其入参均可以从KTX文件中拿


var ext = gl.getExtension('WEBGL_compressed_texture_etc');
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.compressedTexImage2D(gl.TEXTURE_2D, 0, ext.COMPRESSED_RGBA8_ETC2_EAC, 512, 512, 0, textureData);


 兼容性情况


  1. Android平台: Android平台由于机型、厂商众多,纹理压缩的支持情况较为复杂;其中ETC1支持的最为广泛,但是由于ETC1不支持Alpha通道,导致其使用场景有限,ETC2覆盖度也挺高但是需要启用OpenGL es 3.x;据google play统计,Android中高端机型对ASTC的支持度覆盖度有77%以上(具体到GPU型号上,高通骁龙415及以上(2015),ARM Mali T624(2012)及以上,NVIDIA Tegra k1(2014)及以上)
  2. iOS平台: iOS平台PVRTC格式支持最广泛,苹果也推荐使用此格式;在2013 A7芯片发布后,开始支持(ETC/ETC2)格式,2014 A8芯片及以上,开始支持ASTC格式;


图片.png


综上,Android平台选用ETC + ASTC,iOS平台高版本使用ASTC、低版本PVRTC兜底即可覆盖所有设备。开发者运行时可以通过API glgetString(GL_EXTENSIONS)获取当前设备支持的压缩纹理格式,WebGL通过getSupportedExtensions()API获得相同信息。


纹理压缩性能表现


 体积


素材大小1024x1024:


图片.png

image.gif图片.png


结论:


  1. 相比jpeg等图片格式,纹理压缩通常体积会更大,这会导致IO时间变长;
  2. 不同格式的纹理压缩体积也不一样,压缩率高体积虽然降下来,但是素材质量会降低,使用时需要权衡;
  3. 纹理压缩格式GZip压缩效果不明显;


 下载时间 & 内存


测试机型: pixel4、iPhone11ProMax

游戏引擎: pixi.js


图片.png

图片.png

图片.png


结论:纹理压缩格式相比普通纹理内存优势巨大,可以减少50%以上内存占用,但与此同时,素材下载时间会延长;


 纹理上传GPU时间


图片.png


图片.png


结论:纹理压缩格式GPU上传时间几乎可以忽略不记,相比普通纹理具有巨大的优势,也可以抵消一部分压缩纹理下载的耗时;


小程序Canvas纹理压缩实现方案


小程序下,我们是基于 OpenGL ES API 封装 WebGL API,纹理压缩也不例外,由于WebGL扩展中支持的纹理压缩格式在OpenGL ES中都有对应实现,比如 WEBGL_compressed_texture_astc扩展对应到GL的扩展名为 GL_KHR_texture_compression_astc_ldr等,因此只需要根据扩展名称映射到OpenGLES实现即可,比较简单,这里不再展开。


总结


纹理压缩在现代计算机图形中占据重要定位,现如今主流移动设备GPU都已支持纹理压缩,在实际场景中可以充分利用此能力优化游戏应用以带来更好的用户体验。


参考


  1. http://sv-journal.org/2014-1/06/en/index.php?lang=en#8
  2. https://developer.android.com/guide/playcore/asset-delivery/texture-compression
  3. https://docs.unity3d.com/es/2019.4/Manual/class-TextureImporterOverride.html
  4. https://blog.imaginationtech.com/pvrtc-the-most-efficient-texture-compression-standard-for-the-mobile-graphics-world/
  5. https://cesium.com/blog/2017/02/06/texture-compression/


团队介绍


阿里跨平台技术人才储备丰富,独行快,众行远,欢迎优秀的你加入【淘系终端体验平台-跨平台技术团队】,一起打造靠谱的跨平台方案!这里有H5容器、Weex、Flutter、小程序、游戏互动等诸多解决方案,既有技术深度也有广泛业务场景,欢迎优秀的小伙伴来一起搞事情,一起把技术做稳一起为业务提效,手淘跨平台技术团队欢迎你的加入!

相关实践学习
DataV Board用户界面概览
本实验带领用户熟悉DataV Board这款可视化产品的用户界面
阿里云实时数仓实战 - 项目介绍及架构设计
课程简介 1)学习搭建一个数据仓库的过程,理解数据在整个数仓架构的从采集、存储、计算、输出、展示的整个业务流程。 2)整个数仓体系完全搭建在阿里云架构上,理解并学会运用各个服务组件,了解各个组件之间如何配合联动。 3 )前置知识要求   课程大纲 第一章 了解数据仓库概念 初步了解数据仓库是干什么的 第二章 按照企业开发的标准去搭建一个数据仓库 数据仓库的需求是什么 架构 怎么选型怎么购买服务器 第三章 数据生成模块 用户形成数据的一个准备 按照企业的标准,准备了十一张用户行为表 方便使用 第四章 采集模块的搭建 购买阿里云服务器 安装 JDK 安装 Flume 第五章 用户行为数据仓库 严格按照企业的标准开发 第六章 搭建业务数仓理论基础和对表的分类同步 第七章 业务数仓的搭建  业务行为数仓效果图  
相关文章
|
4月前
|
KVM 虚拟化
KVM的热添加技术之内存
文章介绍了KVM虚拟化技术中如何通过命令行调整虚拟机内存配置,包括调小和调大内存的步骤,以及一些相关的注意事项。
99 4
KVM的热添加技术之内存
|
21天前
|
人工智能 物联网 C语言
SVDQuant:MIT 推出的扩散模型后训练的量化技术,能够将模型的权重和激活值量化至4位,减少内存占用并加速推理过程
SVDQuant是由MIT研究团队推出的扩散模型后训练量化技术,通过将模型的权重和激活值量化至4位,显著减少了内存占用并加速了推理过程。该技术引入了高精度的低秩分支来吸收量化过程中的异常值,支持多种架构,并能无缝集成低秩适配器(LoRAs),为资源受限设备上的大型扩散模型部署提供了有效的解决方案。
44 5
SVDQuant:MIT 推出的扩散模型后训练的量化技术,能够将模型的权重和激活值量化至4位,减少内存占用并加速推理过程
|
4月前
ARM64技术 —— MMU处于关闭状态时,内存访问是怎样的?
ARM64技术 —— MMU处于关闭状态时,内存访问是怎样的?
|
6月前
|
机器学习/深度学习 存储 缓存
操作系统中的内存管理技术
在数字世界的复杂架构中,操作系统扮演着枢纽的角色,其中内存管理作为其核心组件之一,保障了计算资源的高效利用与稳定运行。本文将深入探讨操作系统中内存管理的关键技术,包括虚拟内存、分页和分段机制,以及现代操作系统如何通过这些技术优化性能和提高系统稳定性。通过具体实例和数据分析,我们将揭示这些技术如何在实际应用中发挥作用,并讨论它们面临的挑战及未来发展方向。 【7月更文挑战第16天】
115 6
|
6月前
|
存储 缓存 Java
Android性能优化:内存管理与LeakCanary技术详解
【7月更文挑战第21天】内存管理是Android性能优化的关键部分,而LeakCanary则是进行内存泄漏检测和修复的强大工具。
|
6月前
|
物联网 云计算
操作系统中的内存管理技术解析
【7月更文挑战第13天】本文将深入探讨操作系统中至关重要的内存管理技术,包括虚拟内存、分页和分段机制等核心概念。我们将从内存管理的基本原理出发,逐步过渡到高级技术如交换空间和文件映射,最后讨论现代操作系统中内存管理面临的挑战与未来发展方向。文章旨在为读者提供对操作系统内存管理全面而深入的理解。
92 7
|
6月前
|
存储 缓存 NoSQL
Java中的内存数据库与缓存技术
Java中的内存数据库与缓存技术
|
6月前
|
存储 缓存 安全
操作系统中的内存管理:技术与挑战
在数字化时代,操作系统的内存管理成为计算机科学领域中一个至关重要的技术环节。本文将深入探讨现代操作系统中内存管理的基本原理、关键技术及其面临的挑战。通过对分页、分段、虚拟存储和缓存策略等核心概念的介绍,我们旨在揭示内存管理如何优化系统性能,保障数据安全,并提高资源利用率。同时,文章还将讨论内存泄漏、碎片化以及安全性问题等当前内存管理技术所面临的主要挑战。
84 0
|
6月前
|
存储 监控 安全
探索现代操作系统中的内存管理技术
【7月更文挑战第12天】在数字世界的心脏,操作系统扮演着至关重要的角色。本文将深入探讨内存管理技术,这一操作系统中的核心组件。我们将从基础的内存结构入手,逐步解析虚拟内存、分页和分段等高级概念。文章旨在为读者提供一个清晰的内存管理机制视图,并讨论其在性能优化和系统安全中的应用。通过分析现代操作系统如Linux和Windows的内存管理策略,我们可以更好地理解这些复杂系统的内部工作原理及其对计算领域的影响。
|
6月前
|
存储 算法 数据安全/隐私保护
探索现代操作系统的内存管理技术
【7月更文挑战第4天】本文将深入探讨现代操作系统中内存管理的关键技术和策略,包括虚拟内存、分页与分段、以及内存分配算法。我们将了解这些技术如何优化资源使用,提高系统性能,并确保数据安全和完整性。

热门文章

最新文章