题目
- 设计一个Android平台的Gallery组件,要求Gallery中每个item内的图片显示达成有效显示的最大精度,并保证Gallery在滚屏时能够全60FPS帧率地及时显示出加载的图片。请详细说明实现架构、关键技术点及APIs。
- 约束条件:GPU空间传输通道带宽较小,对于400*400pixel尺寸的图像而言,一次传输耗时超过30ms。
答案
当时设计的答案如下:
1. Gallery组件类图
Gallery组件类图设计如下图所示,其中:
- GalleryView为自定义Gallery组件视图;
- GalleryRenderer为GLSurfaceView的Renderer.
- AbstractImage为抽象的ImageView基类,子类继承实现loadGLTexture和Draw
- DataCache为图片缓存类,NetworkHelper为NetWork模块的管理类,负责图片的下载.
2. 关键技术点及相关API设计
2.1 最大精度下60fps帧率显示图片
方案1: VBO/FBO方案
VBO(Vertex Buffer Array): OpenGL存储顶点数据的高速缓存,可减少渲染时间,相关APIs如下(OpenGL ES 2.0以上)
1 2 3 4 | GLES20.glGenBuffers(1, vboId, 0);//申请 GLES20.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vboId[0]);//绑定 GLES20.glBufferData(GLES30.GL_ARRAY_BUFFER, vertexBuffer.capacity() * 4,vertexBuffer,GLES30.GL_STATIC_DRAW);//存储 GLES20.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0); |
PBO(Pixel Buffer Object):OpenGL存储像素数据的高速缓存,可实现快速的像素数据传递,减少数据的拷贝/传递时间,相关APIs如下(OpenGL ES 3.0以上)
1 2 3 4 5 6 7 | GLES30.glReadBuffer(GLES30.GL_BACK); //set framebuffer to read from GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, mPboHandleContainer[0]); // bind pbo GLES30.glReadPixels(0, 0, width, height, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, pboByteBuffer); // read pixels ByteBuffer byteBuffer =((ByteBuffer) GLES30.glMapBufferRange(GLES30.GL_PIXEL_PACK_BUFFER, 0, 4 * mWidth * mHeight, GLES30.GL_MAP_READ_BIT)).order(ByteOrder.nativeOrder()); // map pbo to bb GLES30.glUnmapBuffer(GLES30.GL_PIXEL_PACK_BUFFER);// unmap pbo GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, 0);// unbind pbo |
PBO DMA异步快速传递原理及多缓存对象设计
- 如下面两图所示,其中上图是传统的方法从图像源载入图像数据到纹理对象的过程,像素数据首先存到系统内存中,接着使用glTexImage2D将数据从系统内存拷贝到纹理对象,包含的两个子过程均需要有CPU执行;而下图中,像素数据是直接载入到PBO中,这个过程仍需要CPU来执行,但是从数据从PBO到纹理对象的过程则由GPU来执行DMA,不需要CPU参与。而且opengl可安排异步DMA,不必马上进行像素数据的传递。因此,相比而言,下图中的glTexImage2D立即返回而不是马上执行,这样CPU可以执行其它的操作而不需要等待像素数据传递的结束。
- 如下图所示表示同时使用了两个PBO。在glTexSubImage2D将像素数据从PBO拷贝出来的同时,另一份像素数据写进了另一个PBO。即在第n帧时,PBO1用于glTexSubImage2D,而PBO2用于生成一个新的纹理对象了。再到n+1帧时,两个PBO则互换了角色。由于异步DMA传递,像素数据的更新和拷贝过程可同时进行,即CPU将纹理源更新到PBO,同时GPU将从另一PBO中拷贝出纹理,从而提高处理速度。
参考(图片来源)
方案二:Triple-Buffering等
Triple-Buffering基本思路:
frame N正在显示时,frameN+1已经缓存并准备好(Only ready to show),frameN+2已经开始获取
纹理索引基本思路:
所有图片都对应一个索引值,cache on memory,preloading,滑动时快速切换索引
2.2 Gallery组件架构
Gallery组件是一个独立的模块,前期设计时需考虑后期的可维护性性及可可扩展性等进行综合考虑,
例如Gallery中图片的来源,可能来自本地或者网络下载,这样设计时需要将图片加载的模块单独抽离出来以便可扩展性等; 同时,从框架上考虑,Android组件的设计在具体情况下可考虑MVC/MVP等模式,这样不仅便于后期可维护可扩展,另外也能很好的将Model、View和Control/Presenter分离.
思考/讨论
-
由于该题是OpenGL题目,所以笔者在回答时重点侧重在图片的显示上,而对图片的加载直接忽略掉了,后面想一下,还是在上述答案中加上对Android中图片下载的多级缓存设计会比较好。
-
OpenGL渲染深入
大家有好的答案或建议可直接下面留言回复,一起讨论! ~
By SkySeraph-2016
原文地址:
http://skyseraph.com/2016/05/05/AR_VR/%E4%B8%80%E9%81%93Android%20OpenGL%E7%AC%94%E8%AF%95%E9%A2%98/