15. Html5的局:WebGL的纹理格式的转换

紧接上文

WebKit为了统一WebGL的书写规范,对OpenGL的标准进行四书五入,推出了平台无关的API标准,同时为了简化底层硬件的差异,又新增了一些纹理格式的支持,由内核提供高性能的图像转换,扩展了OpenGL得标准。
那么,WebGL在底层做了些什么呢?复杂吗?可以自己实现吗?

纹理格式转换计算量大

这是WebGL为前端同学提供的福利,上层开发可以更加专注业务书写,充分挖掘C/C++语言的能力。

UNPACK_FLIP_Y_WEBGL

重新生成一张内存图片,将纹理像素上下颠倒的复制到新图片。
15. Html5的局:WebGL的纹理格式的转换
图片在内存的大小,与图片格式没有直接关系,主要取决于尺寸。以1024x1024的png为例,有R、G、B、A四个通道,每个像素占4字节,总内存:1024 x 1024 x 4 = 4M空间。
新图片上传到GPU后自动释放,不会影响到老图片的生命周期。
UNPACK_FLIP_Y_WEBGL的取值:true/false

UNPACK_PREMULTIPLY_ALPHA_WEBGL

对图片纹理的每个像素的R、G、B通道,乘以A的值后,并替换原先的值。
我们以一张白色图片,0.5的透明度为例。
每个像素的存储格式为:
15. Html5的局:WebGL的纹理格式的转换
R、G、B、A分别为unsigned char类型,俗称uint8_t,预算过程中自动进行浮点数转换,舍去末尾的精度:
R = R * A / 255.0f;
G = G * A / 255.0f;
B = B * A / 255.0f;
A = A;

每个像素都要算一次,1024 x 1024 大小的图片,计算量为1024 x 1024 x 10 约10M次。对于C/C++来说也比较耗时,如果放到JS中处理,后果不堪设想。
这也许是WebGL设计的初衷。

UNPACK_ALIGNMENT

上面我们提到的图片格式为RGBA,转换起来虽然计算量打,但算法比较直观。换成其他格式就不是这么直观了,比如:RGB、RGB565等等,不仅需要按byte进行转换,每个uint8_t都需要掰开使用。
以RGB为例,每个像素占用3个字节,考虑到GPU只能接受2的整数倍的尺寸。所以CPU需要为前端同学做格式转换。
这里我们定义Stride,并计算相应的像素对齐后的尺寸。

stride = (imageWidth * bytesPerPixel + unpackAlignment - 1) / unpackAlignment;

如果unpackAlignment = 1,那么新的图片尺寸是不变的,否则,新的图片尺寸就需要CPU处理做像素对齐,我们这里讨论特殊情况,就是 stride != width的时候。

从RGB到RGB

FireFox的做法比较简单,申请新的图片空间:

GLvoid* pixels = malloc(stride * height);

将每个像素从GL_RGB转换成GL_RGBA,保存中间pixel变量,再赋值到新的图片中:

······
GLubyte texel[4];
convert_rgb_rgba(image[i][j], texel];
conver_rgba_rgb(texel, pixels[i][j]);
······

问题来了,既然gl.texImage2D函数的internalFormat与format的格式必须是保持一致的,为什么不把函数直接写为:

memcpy(image, pixels, width * height);

理由就是为了做兼容,对比WebKit代码,我们可以找到FireFox这样做的原因。

WebKit做法复杂,但耐人寻味

申请新的图片内存空间:

GLuint bytesPerPiexl = formatSize(internalFormat, type);
GLvoid* pixels = malloc(width*height * bytesPerPiexl);

我们会发现,WebKit并不会按照stride来申请空间,也不会参考unpackAlignment,它使用了internalFormat和type来确定OpenGL中的纹理格式和空间结构。
这两个参数可以确定每个像素的bytes,参考14. Html5的局:WebGL的纹理格式提到的常见纹理格式。

到了转换这一步,WebKit采用函数模板为路由的方式转发到对应的转换API:

template<srcForamt, dstFormat, SrcType, DstType>
void convert(····) {
      switch(srcForamt) {
            case GL_RGB: {
                   switch(dstFormat) {
                         case GL_RGB: {
                                unpack_RGB_pack_RGB(image, pixels);
                                break;
                         }
                         case GL_RGBA: {
                                 unpack_RGB_pack_RGBA(image, pixiels);
                                 break;
                         }
                         ....
                   }
                   break;
            }
            .......
     }
}

这里WebKit做了大量的路由功能,提高了转换效率,但是代码复杂度很高。比如:
我们已知的常用纹理的Format为:

GL_RGB, GL_RGBA, GL_ALPHA, GL_LUMINANCE, GL_LUMINANCE_ALPHA

WebKit还支持了很多其他的Format:

GL_RGB16F, GL_RGBA16F...
GL_RGB32F, GL_RGBA32F...

下一篇

这篇内容太多了,我们放到下一篇继续讲,纹理格式转换。

上一篇:Hadoop科普文—常见的45个问题解答


下一篇:Sed&awk笔记之awk篇:快速了解Awk(三)