前言
在上一篇文章《Android图形渲染原理(上)》中,详细的讲解了图像消费者,我们已经了解了Android中的图像元数据是如何被SurfaceFlinger,HWComposer或者OpenGL ES消费的,那么,图像元数据又是怎么生成的呢?这一篇文章就来详细介绍Android中的图像生产者——SKIA,OPenGL ES,Vulkan,他们是Android中最重要的三支画笔。
图像生产者
OpenGL ES
什么是OpenGL呢?OpenGL是一套图像编程接口,对于开发者来说,其实就是一套C语言编写的API接口,通过调用这些函数,便可以调用显卡来进行计算机的图形开发。虽然OpenGL是一套API接口,但它并没有具体的实现这些接口,接口的实现是由显卡的驱动程序来完成的。在前一篇文章中介绍过,显卡驱动是其他模块和显卡沟通的入口,开发者通过调用OpenGL的图像编程接口发出渲染命令,这些渲染命令被称为DrawCall,显卡驱动会将渲染命令翻译能GPU能理解的数据,然后通知GPU读取数据进行操作。OpenGL ES又是什么呢?它是为了更好的适应嵌入式等硬件较差的设备,推出的OpenGL的剪裁版,基本和OpenGL是一致的。Android从4.0开始默认开启硬件加速,也就是默认使用OpenGL ES来进行图形的生成和渲染工作。
我们接着来看看如何使用OpenGL ES。
如何使用OpenGL ES?
想要在Android上使用OpenGL ES,我们要先了解EGL。OpenGL虽然是跨平台的,但是在各个平台上也不能直接使用,因为每个平台的窗口都是不一样的,而EGL就是适配Android本地窗口系统和OpenGL ES桥接层。
OpenGL ES 定义了平台无关的 GL 绘图指令,EGL则定义了控制 displays,contexts 以及 surfaces 的统一的平台接口
那么如何使用EGL和OpenGL ES生成图形呢?其实比较简单,主要有这三步
- EGL初始化Display,Context和Surface
- OpenGL ES调用绘制指令
- EGL提交绘制后的buffer
我们详细来看一下每一步的流程
1,EGL进行初始化:主要初始化Display,Context 和Surface三个元素就可以了。
- Display(EGLDisplay) 是对实际显示设备的抽象
//创建于本地窗口系统的连接
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
//初始化display
eglInitialize(display, NULL, NULL);
- Context (EGLContext) 存储 OpenGL ES绘图的一些状态信息
/* create an EGL rendering context */
context = eglCreateContext(display, config, EGL_NO_CONTEXT, NULL);
- Surface(EGLSurface)是对用来存储图像的内存区域
//设置Surface配置
eglChooseConfig(display, attribute_list, &config, 1, &num_config);
//创建本地窗口
native_window = createNativeWindow();
//创建surface
surface = eglCreateWindowSurface(display, config, native_window, NULL);
- 初始化完成后,需要绑定上下文
//绑定上下文
eglMakeCurrent(display, surface, surface, context);
2,OpenGL ES调用绘制指令:主要通过使用 OpenGL ES API ——gl_*(),接口进行绘制图形
//绘制点
glBegin(GL_POINTS);
glVertex3f(0.7f,-0.5f,0.0f); //入参为三维坐标
glVertex3f(0.6f,-0.7f,0.0f);
glVertex3f(0.6f,-0.8f,0.0f);
glEnd();
//绘制线
glBegin(GL_LINE_STRIP);
glVertex3f(-1.0f,1.0f,0.0f);
glVertex3f(-0.5f,0.5f,0.0f);
glVertex3f(-0.7f,0.5f,0.0f);
glEnd();
//……
3,EGL提交绘制后的buffer:通过eglSwapBuffer()进行双缓冲buffer的切换
EGLBoolean res = eglSwapBuffers(mDisplay, mSurface);
swapBuffer切换缓冲区buffer后,显卡就会对Buffer中的图像进行渲染处理。此时,我们的图像就能显示出来了。
我们看一个完整的使用流程Demo
#include <stdlib.h>
#include <unistd.h>
#include <EGL/egl.h>
#include <GLES/gl.h>
typedef ... NativeWindowType;
extern NativeWindowType createNativeWindow(void);
static EGLint const attribute_list[] = {
EGL_RED_SIZE, 1,
EGL_GREEN_SIZE, 1,
EGL_BLUE_SIZE, 1,
EGL_NONE
};
int main(int argc, char ** argv)
{
EGLDisplay display;
EGLConfig config;
EGLContext context;
EGLSurface surface;
NativeWindowType native_window;
EGLint num_config;
/* get an EGL display connection */
display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
/* initialize the EGL display connection */
eglInitialize(display, NULL, NULL);
/* get an appropriate EGL frame buffer configuration */
eglChooseConfig(display, attribute_list, &config, 1, &num_config);
/* create an EGL rendering context */
context = eglCreateContext(display, config, EGL_NO_CONTEXT, NULL);
/* create a native window */
native_window = createNativeWindow();
/* create an EGL window surface */
surface = eglCreateWindowSurface(display, config, native_window, NULL);
/* connect the context to the surface */
eglMakeCurrent(display, surface, surface, context);
/* clear the color buffer */
glClearColor(1.0, 1.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glFlush();
eglSwapBuffers(display, surface);
sleep(10);
return EXIT_SUCCESS;
}
介绍完EGL和OpenGL的使用方式了,我们可以开始看Android是如何通过它进行界面的绘制的,这里会列举两个场景:开机动画,硬件加速来详细的讲解OpenGL ES作为图像生产者,是如何生产,即如何绘制图像的。
OpenGL ES播放开机动画
当Android系统启动时,会启动Init进程,Init进程会启动Zygote,ServerManager,SurfaceFlinger等服务。随着SurfaceFlinger的启动,我们的开机动画也会开始启动。先看看SurfaceFlinger的初始化函数。
//文件-->/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::init() {
...
mStartBootAnimThread = new StartBootAnimThread();
if (mStartBootAnimThread->Start() != NO_ERROR) {
ALOGE("Run StartBootAnimThread failed!");
}
}
//文件-->/frameworks/native/services/surfaceflinger/StartBootAnimThread.cpp
status_t StartBootAnimThread::Start() {
return run("SurfaceFlinger::StartBootAnimThread", PRIORITY_NORMAL);
}
bool StartBootAnimThread::threadLoop() {
property_set("service.bootanim.exit", "0");
property_set("ctl.start", "bootanim");
// Exit immediately
return false;
}
从上面的代码可以看到,SurfaceFlinger的init函数中会启动BootAnimThread线程,BootAnimThread线程会通过property_set来发送通知,它是一种Socket方式的IPC通信机制,对Android IPC通信感兴趣的可以看看我的这篇文章《深入理解Android进程间通信机制》,这里就不过多讲解了。init进程会接收到bootanim的通知,然后启动我们的动画线程BootAnimation。
了解了前面的流程,我们开始看BootAnimation这个类,Android的开机动画的逻辑都在这个类中。我们先看看构造函数和onFirsetRef函数,这是这个类创建时最先执行的两个函数:
//文件-->/frameworks/base/cmds/bootanimation/BootAnimation.cpp
BootAnimation::BootAnimation() : Thread(false), mClockEnabled(true), mTimeIsAccurate(false),
mTimeFormat12Hour(false), mTimeCheckThread(NULL) {
//创建SurfaceComposerClient
mSession = new SurfaceComposerClient();
//……
}
void BootAnimation::onFirstRef() {
status_t err = mSession->linkToComposerDeath(this);
if (err == NO_ERROR) {
run("BootAnimation", PRIORITY_DISPLAY);
}
}
构造函数中创建了SurfaceComposerClient,SurfaceComposerClient是SurfaceFlinger的客户端代理,我们可以通过它来和SurfaceFlinger建立通信。构造函数执行完后就会执行onFirsetRef()函数,这个函数会启动BootAnimation线程
接着看BootAnimation线程的初始化函数readyToRun。
//文件-->/frameworks/base/cmds/bootanimation/BootAnimation.cpp
status_t BootAnimation::readyToRun() {
mAssets.addDefaultAssets();
sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(
ISurfaceComposer::eDisplayIdMain));
DisplayInfo dinfo;
//获取屏幕信息
status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &dinfo);
if (status)
return -1;
// 通知SurfaceFlinger创建Surface,创建成功会返回一个SurfaceControl代理
sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),
dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);
SurfaceComposerClient::openGlobalTransaction();
//设置这个layer在SurfaceFlinger中的层级顺序
control->setLayer(0x40000000);
//获取surface
sp<Surface> s = control->getSurface();
// 以下是EGL的初始化流程
const EGLint attribs[] = {
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_DEPTH_SIZE, 0,
EGL_NONE
};
EGLint w, h;
EGLint numConfigs;
EGLConfig config;
EGLSurface surface;
EGLContext context;
//步骤1:获取Display
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
//步骤2:初始化EGL
eglInitialize(display, 0, 0);
//步骤3:选择参数
eglChooseConfig(display, attribs, &config, 1, &numConfigs);
//步骤4:传入SurfaceFlinger生成的surface,并以此构造EGLSurface
surface = eglCreateWindowSurface(display, config, s.get(), NULL);
//步骤5:构造egl上下文
context = eglCreateContext(display, config, NULL, NULL);
//步骤6:绑定EGL上下文
if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE)
return NO_INIT;
//……
}
通过readyToRun函数可以看到,里面主要做了两件事情:初始化Surface,初始化EGL,EGL的初始化流程和上面OpenGL ES使用中讲的流程是一样的,这里就不详细讲了,主要简单介绍一下Surface初始化的流程,详细的流程会在下一篇文章图像缓冲区中讲,它的步骤如下:
- 创建SurfaceComponentClient
- 通过SurfaceComponentClient通知SurfaceFlinger创建Surface,并返回SurfaceControl
- 有了SurfaceControl之后,我们就可以设置这块Surface的层级等属性,并能获取到这块Surface。
- 获取到Surface后,将Surface绑定到EGL中去
Surface也创建好了,EGL也创建好了,此时我们就可以通过OpenGL来生成图像——也就是开机动画了,我们接着看看线程的执行方法threadLoop函数中是如何播放的动画的。
//文件-->/frameworks/base/cmds/bootanimation/BootAnimation.cpp
bool BootAnimation::threadLoop()
{
bool r;
if (mZipFileName.isEmpty()) {
r = android(); //Android默认动画
} else {
r = movie(); //自定义动画
}
//动画播放完后的释放工作
eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroyContext(mDisplay, mContext);
eglDestroySurface(mDisplay, mSurface);
mFlingerSurface.clear();
mFlingerSurfaceControl.clear();
eglTerminate(mDisplay);
eglReleaseThread();
IPCThreadState::self()->stopProcess();
return r;
}
函数中会判断是否有自定义的开机动画文件,如果没有就播放默认的动画,有就播放自定义的动画,播放完成后就是释放和清除的操作。默认动画和自定义动画的播放方式其实差不多,我们以自定义动画为例,看看具体的实现流程。
//文件-->/frameworks/base/cmds/bootanimation/BootAnimation.cpp
bool BootAnimation::movie()
{
//根据文件路径加载动画文件
Animation* animation = loadAnimation(mZipFileName);
if (animation == NULL)
return false;
//……
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// 调用OpenGL清理屏幕
glShadeModel(GL_FLAT);
glDisable(GL_DITHER);
glDisable(GL_SCISSOR_TEST);
glDisable(GL_BLEND);
glBindTexture(GL_TEXTURE_2D, 0);
glEnable(GL_TEXTURE_2D);
glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//……
//播放动画
playAnimation(*animation);
//……
//释放动画
releaseAnimation(animation);
return false;
}
movie函数主要做的事情如下
- 通过文件路径加载动画
- 调用OpenGL做清屏操作
- 调用playAnimation函数播放动画。
- 停止播放动画后通过releaseAnimation释放资源
我们接着看playAnimation函数
//文件-->/frameworks/base/cmds/bootanimation/BootAnimation.cpp
bool BootAnimation::playAnimation(const Animation& animation)
{
const size_t pcount = animation.parts.size();
nsecs_t frameDuration = s2ns(1) / animation.fps;
const int animationX = (mWidth - animation.width) / 2;
const int animationY = (mHeight - animation.height) / 2;
//遍历动画片段
for (size_t i=0 ; i<pcount ; i++) {
const Animation::Part& part(animation.parts[i]);
const size_t fcount = part.frames.size();
glBindTexture(GL_TEXTURE_2D, 0);
// Handle animation package
if (part.animation != NULL) {
playAnimation(*part.animation);
if (exitPending())
break;
continue; //to next part
}
//循环动画片段
for (int r=0 ; !part.count || r<part.count ; r++) {
// Exit any non playuntil complete parts immediately
if(exitPending() && !part.playUntilComplete)
break;
//启动音频线程,播放音频文件
if (r == 0 && part.audioData && playSoundsAllowed()) {
if (mInitAudioThread != nullptr) {
mInitAudioThread->join();
}
audioplay::playClip(part.audioData, part.audioLength);
}
glClearColor(
part.backgroundColor[0],
part.backgroundColor[1],
part.backgroundColor[2],
1.0f);
//按照frameDuration频率,循环绘制开机动画图片纹理
for (size_t j=0 ; j<fcount && (!exitPending() || part.playUntilComplete) ; j++) {
const Animation::Frame& frame(part.frames[j]);
nsecs_t lastFrame = systemTime();
if (r > 0) {
glBindTexture(GL_TEXTURE_2D, frame.tid);
} else {
if (part.count != 1) {
//生成纹理
glGenTextures(1, &frame.tid);
//绑定纹理
glBindTexture(GL_TEXTURE_2D, frame.tid);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
int w, h;
initTexture(frame.map, &w, &h);
}
const int xc = animationX + frame.trimX;
const int yc = animationY + frame.trimY;
Region clearReg(Rect(mWidth, mHeight));
clearReg.subtractSelf(Rect(xc, yc, xc+frame.trimWidth, yc+frame.trimHeight));
if (!clearReg.isEmpty()) {
Region::const_iterator head(clearReg.begin());
Region::const_iterator tail(clearReg.end());
glEnable(GL_SCISSOR_TEST);
while (head != tail) {
const Rect& r2(*head++);
glScissor(r2.left, mHeight - r2.bottom, r2.width(), r2.height());
glClear(GL_COLOR_BUFFER_BIT);
}
glDisable(GL_SCISSOR_TEST);
}
// 绘制纹理
glDrawTexiOES(xc, mHeight - (yc + frame.trimHeight),
0, frame.trimWidth, frame.trimHeight);
if (mClockEnabled && mTimeIsAccurate && validClock(part)) {
drawClock(animation.clockFont, part.clockPosX, part.clockPosY);
}
eglSwapBuffers(mDisplay, mSurface);
nsecs_t now = systemTime();
nsecs_t delay = frameDuration - (now - lastFrame);
//ALOGD("%lld, %lld", ns2ms(now - lastFrame), ns2ms(delay));
lastFrame = now;
if (delay > 0) {
struct timespec spec;
spec.tv_sec = (now + delay) / 1000000000;
spec.tv_nsec = (now + delay) % 1000000000;
int err;
do {
err = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &spec, NULL);
} while (err<0 && errno == EINTR);
}
checkExit();
}
//休眠
usleep(part.pause * ns2us(frameDuration));
// 动画退出条件判断
if(exitPending() && !part.count)
break;
}
}
// 释放纹理
for (const Animation::Part& part : animation.parts) {
if (part.count != 1) {
const size_t fcount = part.frames.size();
for (size_t j = 0; j < fcount; j++) {
const Animation::Frame& frame(part.frames[j]);
glDeleteTextures(1, &frame.tid);
}
}
}
// 关闭和视频音频
audioplay::setPlaying(false);
audioplay::destroy();
return true;
}
从上面的源码可以看到,playAnimation函数播放动画的原理,其实就是按照一定的频率,循环调用glDrawTexiOES函数,绘制图片纹理,同时调用音频播放模块播放音频。
通过OpenGL ES播放动画的案例就讲完了,我们也了解了通过OpenGL来播放视频的一种方式,我们接着看第二个案例,Activity界面如何通过OpenGL来进行硬件加速,也就是硬件绘制绘制的。
OpenGL ES进行硬件加速
我们知道,Activity界面的显示需要经历Measure测量,Layout布局,和Draw绘制三个过程,而Draw绘制流程又分为软件绘制和硬件绘制,硬件绘制便是通过OpenGL ES进行的。我们直接看看硬件绘制流程里,OpenGL ES是如何来进行绘制的,它的入口在ViewRootImpl的performDraw函数中。
//文件-->/frameworks/base/core/java/android/view/ViewRootImpl.java
private void performDraw() {
//……
draw(fullRedrawNeeded);
//……
}
private void draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
if (!surface.isValid()) {
return;
}