Android图形框架理解
1. Android图像框架简介
Android framework 有一系列与硬件抽象实现和图形驱动相关的2d和3d渲染api,所以重要的是如何对这些运行在更高层次的api有一个良好的理解。应用开发者有两种通用的方式可以绘制物体到屏幕,分别是canvas和opengl。
android.graphics.Canvas是一组被开发都广泛应用的2d图形api。在Android中,所有在android.view.Views和客户自定义android.view.Views上的绘制操作都是由Canvas完成的。在Android3.0之前,Canvas一直使用没有硬件加速的Skia 2D图形库绘制。
在Android 3.0引入了,为了方便一些canvas变换操作可以在gpu运行,Canvas APIs使用了一个叫做OpenGLRender的硬件加速库。之前开发者必须选择此功能,但是从android 4.0开始,硬件加速的canvas被默认使能。因此,在android4.0中的gpu必须支持opengles 2.0。
此外,硬件加速指导手册中解释了如何使用硬件加速绘制的方式和与软件绘制方式的差别。
另外的一个主要方式是,开发者使用OpenGL ES1.x或者2.0直接渲染到surface上。开发者可以透过Android SDK提供android.opengl包使用OpenGL ES接口,或者使用Android NDK提供的native APIs.
注意:
第三种选择是在Android 3.0中被引入的Renderscript,Renderscript是平台无关的图形API(使用OpenGL ES 2.0的包装),但将会在Android 4.1发布后被弃用。
2. Android如何渲染图形
开发者无论什么样的渲染api,每一个被渲染的像素数据buffer被叫做一个”surface”. Android platform创建的每一个窗口都是surface所支撑的。被渲染的所有可见的surfaces都被surfaceflinger(Android管理surfaces合并的系统服务)合并到显示器。自然,渲染过程还有其他组件的参与,其中主要组件会在下面进行描述。
Image Stream Producers
诸如OpenGL ES游戏,来自media server的视频buffer,普通的Canvas 2D应用,或者任何能生产图像buffer的程序,都可以看做是图像流生产者。
Image Stream Consumes
最常见的消费Image Stream的消费者是SurfaceFlinger,该系统服务消费当前可见的surfaces并且利用window manager提供的信息将他们合并到显示器上。SurfaceFlinger是唯一可以修改显示内容的服务。SurfaceFlinger使用OpenGL和hardware composer来合并一组surfaces。其他的OpenGL ES应用也能消费image streams,例如camera应用可以消费camera 预览图像数据。
SurfaceTexture
SurfaceTexture在逻辑上绑定了image stream的生产者和消费者,主要由SurfaceTextureClient,ISurfaceTexture和SurfaceTexture(在这里,SurfaceTexture是一个实际的C++类,并不上面所提到的组件)三部分组成。生产者(SurfaceTextureClient),Binder(ISurfaceTexture)和消费者(SurfaceTexture)模型为SurfaceTexture组件提供诸如从Gralloc中申请内存,跨进程的共享内存,同步访问缓冲区,配对适当的生产者消费者的操作接口。SurfaceTexture既可以工作在异步模式(生产者不阻塞等待消费者并且丢帧)也可以工作在同步模式(生产者等待消费者处理texture)。一些图像生产者的例子是camera HAL生成camera预览数据或者OpenGL ES游戏。一些消费者的例子是SurfaceFlinger或者其他一些需要显示OpenGL ES流的应用。例如camera应用显示camera取景。
Window Manager
Window Manager是一个管理着窗口(一些views的容器)的生命周期,输入,焦点事件,屏幕方向,过渡,动画,位置,变换,z-order和其他的系统服务。窗口总是由surface所支撑。WindowManager传递所有的窗口metadata给SurfaceFlinger,以便于SurfaceFlinger使用这些数据计算出在显示器上如何合并surfaces.
Hardware Composer
显示子系统的硬件抽象。Surfaceflinger可以从OpenGL和GPU分担一些合并工作给hardware composer。这样比surfaceflinger做全部的工作要来的更快。从Jellybean MR1开始,新版本的hardware composer被引进。更详细的信息可以参考hardware/libhardware/include/hardware/gralloc.h的Hardware composer节。
Gralloc
负责分配graphics缓冲的内存。如果你使用1.1版本及更新版本的hardware composer,这个HAL已经不再需要了。
下图展示了这些组件如何协同工作。
在这里插入图片描述
哪些是你需要提供的
在你的产品中支持android图形你应该提供下列的节的描述
OpenGL ES 1.x Driver
OpenGL ES 2.0 Driver
EGL Driver
Gralloc HAL implementation
Hardware Composer HAL implementation
Framebuffer HAL implementation
OpenGL 和EGL驱动
你必须提供OpenGL ES 1.x,OpenGL ES 2.0和EGL的驱动。提醒一下其中一些关键点:
GL的驱动应该是稳健且符合OpenGL ES标准。
不要限制GL contexts的个数。因为Android允许应用在后台运行,并且使GL contexts存活。在你的驱动里,你不应该限制contexts的个数。虽然一次有20-30个激活的GL contexts是罕见的,但是你同样应该注意每一个context的内存申请。
支持YV12图像格式和系统其他组件的YUV图像格式,比如media codecs与camera.
支持强制扩展:GL_OES_texture_external, EGL_ANDROID_image_native_buffer和EGL_ANDROID_recordable。我们也强烈推荐支持EGL_ANDROID_blob_cache和EGL_KHR_fance_sync。
注意:
暴露给应用开发者的OpenGL API不同于你实现的OpenGL接口。应用不应该访问GL驱动层,而必须通过APIs提供的接口。
预悬旋
多数时候硬件覆盖不支持悬旋,其中的一个解决方案是预先变换buffer在到达surfaceflinger之前。一个查询提示在ANativeWindow中被添加(NATIVE_WINDOW_TRANSFORM_HINT)代表了可能了转换被SurfaceFlinger被应用到缓冲区。你的GL驱动可以使用这个提示去预悬旋在缓冲区到达surfaceflinger之前,当buffer正常到达surfaceflinger时,他被正确变换了。
ANativeWindow接口定义的更多详细信息在system/core/include/system/window.h中。下面一些伪码在hardware composer中实现。
ANativeWindow->query(ANativeWindow, NATIVE_WINDOW_DEFAULT_WIDTH, &w);
ANativeWindow->query(ANativeWindow, NATIVE_WINDOW_DEFAULT_HEIGHT, &h);
ANativeWindow->query(ANativeWindow, NATIVE_WINDOW_TRANSFORM_HINT, &hintTransform);
if (hintTransform & HAL_TRANSFORM_ROT_90)
swap(w, h);
native_window_set_buffers_dimensions(anw, w, h);
ANativeWindow->dequeueBuffer(...);
// here GL driver renders content transformed by " hintTransform "
int inverseTransform;
inverseTransform = hintTransform;
if (hintTransform & HAL_TRANSFORM_ROT_90)
inverseTransform ^= HAL_TRANSFORM_ROT_180;
native_window_set_buffers_transform(anw, inverseTransform);
ANativeWindow->queueBuffer(...);
Gralloc HAL
Image producers中的SurfaceTextureClient中需要的显存分配器。你可以在 hardware/libhardware/modules/gralloc.h中找到关于这个HAL的一个空实现。
Hardware Composer HAL
SurfaceFlinger使用hardware composer合并surfaces到屏幕。Hardware composer抽象诸如overlays和2d blitters的硬件,并且帮助分担一些一般会用OpenGL的操作。
JB MR1引入了一个新版本的HAL。我们建议使用1.1版本,这个版本会提供最新的特色(显式同步,额外的显示器等)。请记住除了1.1版本,由于兼容性的原因我们还有1.0版本。并且1.2草案版本。我们建议你实现1.1版本直到1.2脱离草案模式。
由于hardware composer后面的物理显示器因设备而异,所以很难定义推荐功能,但是这里有一些指导。
Hardware composer手机上应该支持至少4个overlays(状态栏,系统通知栏,应用,和壁纸),平板上至少支持除状态栏的的3个overlays
图层应该比屏幕大,以便于hardware composer应该能处理比显示器大的图层(比如:墙纸)
同时支持多点和平面alpha blending操作。
Hardware composer应该能够消费由GPU,camera,video decoder, skia buffers生产的缓冲区,所以支持下列的图像属性是有用的:
RGBA打包格式
YUV格式
平铺,混合,跨越属性。
硬件方式保护视频播放必须支持如果你想支持受保护的内容。
当实现你的hardware composer时,通用的建议是先实现一个空操作。一旦你的框架完成,实现一个简单的算法来抽象hardware composer。例如,开始仅仅使用头三个或四个surfaces为代表。以后专注于更普适的应用。例如:
横屏和纵屏的全屏游戏
关闭字幕与播放控制条的全屏的视频
主页面(合并状态栏,系统栏,应用程序和墙纸)
多显示器支持
实现更普适的应用之后,你应该专注于优化诸如智能选择surfaces送到overlay硬件,以便减小gpu负载。另外的优化方式是检测屏幕是否更新。如果不更新,使用OpenGL合并替换hardware composer以节电。当屏幕重新更新,继续使用hardware composer减少合并的负载。
你可以找到hardware composer的HAL模块在
hardware/libhardware/include/hardware/hwcomposer.h 和
hardware/libhardware/include/hardware/hwcomposer_defs.h 文件.
一个demo实现在 hardware/libhardware/modules/hwcomposer目录是可用的。
VSYNC
VSYNC是同步更新显示器的某些事件。应用开始绘制与Surfaceflinger合并都是VSYNC触发的。
这样就消除了图像抖动和提高了图像质量。Hardware composer有一个函数指针。
int (waitForVsync *)(int64_t *timestamp);
这个指针指向一个你必须为VSYNC实现的函数。这个函数阻塞直到VSYNC信号发出,并且返回一个真实VSYNC信号的时间戳。客户端可以在特定的时间一次接收一个时间戳,也可以间隔时间为1这么收。你实现的VSYNC最大延时为1ms(推荐1/2ms或更短),并且返回的时间戳必须非常精准。
显式同步
显式同步被需要在JB MR1及以后版本,并且需要提供同步申请与释放gralloc buffers的机制。显式同步允许graphics buffers的生产者和消费者发一个信号当他们处理完一块buffer时。这使得android系统异步队列缓冲区读取或写入另外一个消费者或生产者的确定性目前并不需要它们。
这种通信机制为了便于使用同步栅栏,现在请求时需要消耗或者生产的缓冲区。同步框架主要包括三个部分:
sync_timeline: 一个单调减少的时间线,驱动应该单例实现。主要是特定硬件提交给内核的计数器。
sync_pt: 一个单独的值或指针在sync_timeline.指针有三种状态,激活,触发和错误。指针在激活态开始发触发态发送信号或者错误态。例如:当缓冲区不被消费者需要,sync_point被触发以便于image生产者知道可以重新写入buffer。
sync_fence: sync_pts的集合,通常有不同的sync_timeline双亲(例如显示控制器与GPU)。允许多个客户者与消费者同时触发缓冲区的使用,并且他们可以通过一个函数参数通信。Fences是一个可以从内核空间传递到用户空间的文件描述符。举例来说,一个fence可以包含两个sync_points,那意味着两个image消费者可以同时读取一段buffer.当fence被触发,图像生产者知道所以有的消费者已经处理完消费。
为了实现显式同步,你需要提供如下资源
为特定硬件实现同步时间线的内核空间驱动。驱动提供fence-aware通用的访问与通信机制为hardware composer。更多细节可以看 system/core/include/sync/sync.h文件,system/core/libsync 目录包含了一个与内核空间通信的库。
Hardware composer HAL模块(1.1版本及以后)支持新的同步函数。你可能会需要提供适当的同步参数给HAL中的set()和prepare()函数。最后一个,你可以传递-1给文件描述符如果你不能支持显式同步由于一些原因。尽管这并不被推荐。
两个GL特别的扩展与fences相关, EGL_ANDROID_native_fence_sync和EGL_ANDROID_wait_sync,将fences支持加入到你的图形驱动中。
3. 显示设备
这里所说的显示设备并不一定是指手机屏幕/显示器这种物理设备。
但是为什么不把显示设备写死成显示器,这样实现起来多简单啊。事实上真实的世界很复杂,我们没办法用一种实现包打天下。比如,我们显示在屏幕上绘制了一张图片,看着很不错,想用打印机输出;又或者,我有一个GTK+写得程序,我想同时在win32和x11都能运行;我有一个FTK程序,想在iphone上运行。怎么办?重做一遍?显然不合理。
实际上,我们可以这样理解这个需求:无论我们如何绘制,都是绘制到一块内存,不用关心这块内存由谁提供。
我们可以要求fb/gpu/printer/pdf/image/等等来提供这块内存。我们把数据给这块内存,然后物理设备/软件来处理。所以GTK+能在win32上运行,也能在X11上运行,也能FB上运行。
GDI+抽象了HDC,Skia抽象了Skdevice,FTK抽象了FtkDisplay,
具体做法C++用纯虚函数做为接口,C语言规定函数指针,(实际原理还是一样的)然后具体物理设备/软件来实现。其他图形库,想必也有类似的做法。
基本接口一般包括,全屏刷新/局部刷新。
4. Canvas的实现
上面说到了显示设备给了我一块内存,但是我如何操作他呢?我们需要一个Canvas。
Canvas的基本需求很明确,实现画点,画线,画图像的功能。
我们来看真实的世界:
画点,没啥,在屏幕选一个坐标,然后根据画笔的颜色,点亮一个点。
画线,貌似有点麻烦。标准的横竖问题不大,从开始坐标到结束坐标,根据画笔的属性(虚实/颜色。。。)一个点一个点的画。斜线呢?开始用到了一些微分方程了,呵呵。
画图片,比较麻烦。图像有不同的格式,bmp还好办,大部分都是原始图像,jpeg/png/gif/h264都是压缩过的数据,各不相同,怎么办?我们需要一个BitmapFactory,把图片文件放进去,出来就是合乎我们自己定义的Bitmap的数据块。基本实现原理依然是接口抽象,具体格式具体实现。
5. 事件的接收与派发
事件驱动模型,是很常见的GUI库模型。
如何让控件响应消息,自定义的一些消息如何派发与响应。
6.基本控件的实现,基类/派生类
有了抽象设备,有了canvas,如何实现一些基本控件呢?
控件是啥,一个简直直接的描述就是,绘图和事件响应的具体对象(这个对象不是面向对象的对象)。
首先依旧是设计基类,所有的控件都要做的事,包括绘图和事件响应。这样一样,我们就可以抽象基类了,所公共的属性放到一个类里面,大家都来继承他,这样就减少了重复代码,提高了代码复用度,也会少很多bug。
我们来看看现实的世界如何做的。
Ftk的控件的基类是FtkWidget,里面有两个接口值得注意,
FtkWidgetOnEvent
FtkWidgetOnPaint
一个响应绘图,一个响应事件,和我们上文所一致。
Skia
不那么直观,但是依然符合我们所说的规律。控件的基类是SkView。
首先这个类继承了SkEventSink,这个类就是事件响应的抽象的基类,规定了事件响应的虚函数virtual bool onEvent(const SkEvent&);。另外SkView里面有个虚函数规定了绘图了虚函数virtual void onDraw(SkCanvas*);
MFC是很不现代的图形库,找不到规律。。。。。
上面我们找到了控件类要实现的接口,具体实现就是一个一一对应的关系了。比如Button,ListView,TextView这种控件,他们一定要实现的两个接口就是onDraw和onevent。
如果标准控件库里面没有我们需要的控件,要自定义一个控件的话,如何做也很明显了,直接继承widget/view这种基类,实现要求的虚函数,绘制自己想要的形态。
7. 窗口管理
窗口管理是图形库最重要的功能。
我们不讨论windows这种复杂的窗口管理机制(多窗口,一个时间点,有多个窗口存在)。只针对嵌入式gui这种,当前屏幕上,一个时间点,只有一个窗口的模式,又叫单窗口模式。
什么叫窗口,一个直接与用户交互的界面,就叫窗口。
单一一个控件,没有任何意义,我们必须配合上一系列控件,放到一起,才能实现直接与用户交互。
一个消息(按键消息,触摸消息),只会派发给窗口,然后有窗口来找到当前焦点的控件,并激活该控件的事件响应函数,从而做出响应。
所谓窗口管理,就是窗口如何层叠分布的,比如我按了返回键,当前窗口销毁,应该显示哪个窗口呢?这个事是由窗口管理器决定的,一种比较简单的实现就是栈式窗口管理,就像盘子一样,一个一个叠上去,取得时候一个一个取下来(不考虑一堆一堆这么干的情况)。
8. 其他
动画/输入法/2d加速/gpu加速/特效实现
本文地址:https://blog.csdn.net/u011897062/article/details/109643545
下一篇: C++ 读入文件中的中文字符