一道Android OpenGL笔试题

Catalogue
  1. 1. 题目
  2. 2. 答案
  3. 3. 思考/讨论
  4. 4. 后记

题目

  • 设计一个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渲染深入

大家有好的答案或建议可直接下面留言回复,一起讨论! ~

后记

本文首发于skyseraph.com“一道Android OpenGL笔试题”
同步发表/转载 cnBlogs / CSDN /



By SkySeraph-2016

版权声明


SkySeraph by SkySeraph is licensed under a Creative Commons BY-NC-ND 4.0 International License.
Bob创作并维护的SkySeraph博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证.
本文首发于SkySeraph博客( http://skyseraph.com ),版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

微信扫码打赏SkySeraph

如果您愿意捐助其它金额请戳我~~,扫码支付宝/微信

本文永久链接:http://skyseraph.com/2016/05/05/AR_VR/一道Android OpenGL笔试题/

Comments