Android硬件加速(二)-RenderThread与OpenGL GPU渲染

Android4.0以后,系统默认开启硬件加速来渲染视图,以前,理解Android硬件加速的小白文简单的讲述了硬件加速的简单模型,不过主要针对前半阶段,并没怎么说是如何使用OpenGL、GPU处理数据的,OpenGL主要处理的任务有Surface的composition及图形图像的渲染,本篇文章简单说一下后半部分的模型,这部分对于理解View渲染也有很多帮助,也能更好的帮助理解GPU渲染玄学曲线。node

不过这里有个概念要先弄清,OpenGL仅仅是提供标准的API及调用规则,在不一样的硬件平台上有不一样的实现,好比驱动等,这部分代码通常是不开源,本文主要基于Android libagl(6.0),它是Android中经过软件方法实现的一套OpenGL动态库,并结合Systrace真机上的调用栈,对比二者区别(GPU厂商提供的硬件实现的OpenGL),猜想libhgl(硬件OpenGL)的实现。对于Android APP而言,基于GPU的硬件加速绘制能够分为以下几个阶段:android

  • 第一阶段:APP在UI线程构建OpenGL渲染须要的命令及数据
  • 第二阶段:CPU将数据上传(共享或者拷贝)给GPU,PC上通常有显存一说,可是ARM这种嵌入式设备内存通常是GPU、CPU共享内存
  • 第三阶段:通知GPU渲染,通常而言,真机不会阻塞等待GPU渲染结束,效率低,CPU通知结束后就返回继续执行其余任务,固然,理论上也能够阻塞执行,glFinish就能知足这样的需求(不一样GPU厂商实现不一样,Android源码自带的是软件实现的,只具备参考意义)(Fence机制辅助GPU CPU同步)
  • 第四阶段:swapBuffers,并通知SurfaceFlinger图层合成
  • 第五阶段:SurfaceFlinger开始合成图层,若是以前提交的GPU渲染任务没结束,则等待GPU渲染完成,再合成(Fence机制),合成依然是依赖GPU,不过这就是下一个任务了

第一个阶段,其实主要作的就是构建DrawOp树(里面封装OpenGL渲染命令),同时,预处理分组一些类似命令,以便提升GPU处理效率,这个阶段主要是CPU在工做,不过这个阶段前期运行在UI线程,后期部分运行在RenderThread(渲染线程),第二个阶段主要运行在渲染线程,CPU将数据同步(共享)给GPU,以后,通知GPU进行渲染,不过这里须要注意的是,CPU通常不会阻塞等待GPU渲染完毕,而是通知结束后就返回,除非GPU很是繁忙,来不及响应CPU的请求,没有给CPU发送通知,CPU才会阻塞等待。CPU返回后,会直接将GraphicBuffer提交给SurfaceFlinger,告诉SurfaceFlinger进行合成,可是这个时候GPU可能并未完成图像的渲染,这个时候就牵扯到一个同步,Android中,这里用的是Fence机制,SurfaceFlinger合成前会查询这个Fence,若是GPU渲染没有结束,则等待GPU渲染结束,GPU结束后,会通知SurfaceFlinger进行合成,SF合成后,提交显示,如此完成图像的渲染显示,简单画下示意图:算法

Android CPU GPU通讯模型

以前已经简单分析过DrawOp树的构建,优化,本文主要是分析GPU如何完成OpenGL渲染,这个过程主要在Render线程,经过OpenGL API通知GPU处理渲染任务。canvas

Android OpenGL环境的初始化

通常在使用OpenGL的时候,首先须要获取OpenGL相应的配置,再为其构建渲染环境,好比必须建立OpenGL上下文(Context),上下文能够看作是OpenGL的化身,没有上下文就没有OpenGL环境,同时还要构建一个用于绘图的画布GlSurface,在Android中抽象出来就是EglContext与EglSurface,示例以下:api

private void initGL() {
    
        mEgl = (EGL10) EGLContext.getEGL();
        <!--获取display显示目标-->
        mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
         <!--构建配置-->
        mEglConfig = chooseEglConfig();
        ...<!--构建上下文-->
        mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
    	  ...<!--构建绘图Surface-->
        mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, mSurface, null);
        }
复制代码

Android系统中,APP端如何为每一个窗口配置OpenGL环境的,在一个窗口被添加到窗口的时候会调用其ViewRootImpl对象的setView:缓存

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        		...
                enableHardwareAcceleration(attrs);
            }
复制代码

setView会调用enableHardwareAcceleration,配置OpenGL的硬件加速环境:app

private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) {
        mAttachInfo.mHardwareAccelerated = false;
        mAttachInfo.mHardwareAccelerationRequested = false;
			...
        final boolean hardwareAccelerated =
                (attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0;

        if (hardwareAccelerated) {
        <!--能够开启硬件加速 ,通常都是true-->
            if (!HardwareRenderer.isAvailable()) {
                return;
            }
 					...
                <!--建立硬件加速环境-->
                mAttachInfo.mHardwareRenderer = HardwareRenderer.create(mContext, translucent);
                if (mAttachInfo.mHardwareRenderer != null) {
                    mAttachInfo.mHardwareRenderer.setName(attrs.getTitle().toString());
                    mAttachInfo.mHardwareAccelerated =
                            mAttachInfo.mHardwareAccelerationRequested = true;
                }
            }
        }
    }
复制代码

Android中每一个显示的Window(Activity、Dialog、PopupWindow等)都对应一个ViewRootImpl对象,也会对应一个AttachInfo对象,以后经过异步

HardwareRenderer.create(mContext, translucent);
复制代码

建立的HardwareRenderer对象就被保存在ViewRootImpl的AttachInfo中,跟Window是一对一的关系,经过HardwareRenderer.create(mContext, translucent)建立硬件加速环境后,在须要draw绘制的时候,经过:ide

mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
复制代码

进一步渲染。回过头,接着看APP如何初始化硬件加速环境:直观上说,主要是构建OpenGLContext、EglSurface、RenderThread(若是没启动的话)函数

static HardwareRenderer create(Context context, boolean translucent) {
    HardwareRenderer renderer = null;
    if (DisplayListCanvas.isAvailable()) {
        renderer = new ThreadedRenderer(context, translucent);
    }
    return renderer;
}
    
ThreadedRenderer(Context context, boolean translucent) {
    final TypedArray a = context.obtainStyledAttributes(null, R.styleable.Lighting, 0, 0);
    ...
	<!--建立rootnode-->
    long rootNodePtr = nCreateRootRenderNode();
    mRootNode = RenderNode.adopt(rootNodePtr);
   <!--建立native ThreadProxy-->
    mNativeProxy = nCreateProxy(translucent, rootNodePtr);
	<!--初始化AssetAtlas,本文不分析-->
    ProcessInitializer.sInstance.init(context, mNativeProxy);
    ...
}
复制代码

以前分析过,nCreateRootRenderNode 为ViewRootimpl建立一个root RenderNode,UI线程经过递归mRootNode,能够构建ViewTree全部的OpenGL绘制命令及数据,nCreateProxy会为当前widow建立一个ThreadProxy ,ThreadProxy则主要用来向RenderThread线程提交一些OpenGL相关任务,好比初始化,绘制、更新等:

class ANDROID_API RenderProxy {
public:
    ANDROID_API RenderProxy(bool translucent, RenderNode* rootNode, IContextFactory* contextFactory);
    ANDROID_API virtual ~RenderProxy();
	...
    ANDROID_API bool initialize(const sp<ANativeWindow>& window);
    ...
    ANDROID_API int syncAndDrawFrame();
    ...
    ANDROID_API DeferredLayerUpdater* createTextureLayer();
    ANDROID_API void buildLayer(RenderNode* node);
    ANDROID_API bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap& bitmap);
    ...
    ANDROID_API void fence();
    ...
    void destroyContext();

    void post(RenderTask* task);
    void* postAndWait(MethodInvokeRenderTask* task);
	...
};
复制代码

RenderProxy的在建立之初会作什么?其实主要两件事,第一:若是RenderThread未启动,则启动它,第二:向RenderThread提交第一个Task--为当前窗口建立CanvasContext,CanvasContext有点EglContext的意味,全部的绘制命令都会经过CanvasContext进行中转:

RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode, IContextFactory* contextFactory)
        : mRenderThread(RenderThread::getInstance())
        , mContext(nullptr) {
     <!--建立CanvasContext-->
    SETUP_TASK(createContext);
    args->translucent = translucent;
    args->rootRenderNode = rootRenderNode;
    args->thread = &mRenderThread;
    args->contextFactory = contextFactory;
    mContext = (CanvasContext*) postAndWait(task);
    <!--初始化DrawFrameTask-->
    mDrawFrameTask.setContext(&mRenderThread, mContext);
}
复制代码

从其构造函数中能够看出,OpenGL Render线程是一个单例,同一个进程只有一个RenderThread,RenderProxy 经过mRenderThread引用该单例,未来须要提交任务的时候,直接经过该引用向RenderThread的Queue中插入消息,而RenderThread主要负责从Queue取出消息,并执行,好比将OpenGL命令issue提交给GPU,并通知GPU渲染。在Android Profile的CPU工具中能够清楚的看到该线程的存在(没有显示任务的进程是没有的:

renderThread

简单看下RenderThread()这个单例线程的建立与启动,

RenderThread::RenderThread() : Thread(true), Singleton<RenderThread>()
        , mNextWakeup(LLONG_MAX)
        , mDisplayEventReceiver(nullptr)
        , mVsyncRequested(false)
        , mFrameCallbackTaskPending(false)
        , mFrameCallbackTask(nullptr)
        , mRenderState(nullptr)
        , mEglManager(nullptr) {
    Properties::load();
    mFrameCallbackTask = new DispatchFrameCallbacks(this);
    mLooper = new Looper(false);
    run("RenderThread");
}
复制代码

RenderThread会维护一个MessageQuene,并经过loop的方式读取消息,执行,RenderThread在启动以前,会为OpenGL建立EglManager、RenderState、VSync信号接收器(这个主要为了动画)等OpenGL渲染须要工具组件,以后启动该线程进入loop:

bool RenderThread::threadLoop() {
	
	<!--初始化-->
    setpriority(PRIO_PROCESS, 0, PRIORITY_DISPLAY);
    initThreadLocals();

    int timeoutMillis = -1;
    for (;;) {
    <!--等待消息队列不为空-->
        int result = mLooper->pollOnce(timeoutMillis);
        nsecs_t nextWakeup;
        // Process our queue, if we have anything
        <!--获取消息并执行-->
        while (RenderTask* task = nextTask(&nextWakeup)) {
            task->run();
        }
        ...	
    return false;}
复制代码

初始化,主要是建立EglContext中必须的一些组件,到这里其实都是工具的建立,基本上还没构建OpenGL须要的任何实质性的东西

void RenderThread::initThreadLocals() {
    sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(
            ISurfaceComposer::eDisplayIdMain));
    status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &mDisplayInfo);
    nsecs_t frameIntervalNanos = static_cast<nsecs_t>(1000000000 / mDisplayInfo.fps);
    mTimeLord.setFrameInterval(frameIntervalNanos);
    <!--初始化vsync接收器-->
    initializeDisplayEventReceiver();
    <!--管家-->
    mEglManager = new EglManager(*this);
    <!--状态机-->
    mRenderState = new RenderState(*this);
    <!--debug分析工具-->
    mJankTracker = new JankTracker(frameIntervalNanos);
}
复制代码

Android5.0以后,有些动画是能够彻底在RenderThread完成的,这个时候render渲染线程须要接受Vsync,等信号到来后,回调RenderThread::displayEventReceiverCallback,计算当前动画状态,最后调用doFrame绘制当前动画帧(不详述),有时间能够看下Vsync机制

void RenderThread::initializeDisplayEventReceiver() {
    mDisplayEventReceiver = new DisplayEventReceiver();
    status_t status = mDisplayEventReceiver->initCheck();
    mLooper->addFd(mDisplayEventReceiver->getFd(), 0,
            Looper::EVENT_INPUT, RenderThread::displayEventReceiverCallback, this);
}
复制代码

其次RenderThread须要new一个EglManager及RenderState,二者跟上面的DisplayEventReceiver都从属RenderThread,所以在一个进程中,也是单例的

EglManager::EglManager(RenderThread& thread)
        : mRenderThread(thread)
        , mEglDisplay(EGL_NO_DISPLAY)
        , mEglConfig(nullptr)
        , mEglContext(EGL_NO_CONTEXT)
        , mPBufferSurface(EGL_NO_SURFACE)
        , mAllowPreserveBuffer(load_dirty_regions_property())
        , mCurrentSurface(EGL_NO_SURFACE)
        , mAtlasMap(nullptr)
        , mAtlasMapSize(0) {
    mCanSetPreserveBuffer = mAllowPreserveBuffer;
}
复制代码

EglManager主要做用是管理OpenGL上下文,好比建立EglSurface、指定当前操做的Surface、swapBuffers等,主要负责场景及节点的管理工做:

class EglManager {
public:
    // Returns true on success, false on failure
    void initialize();
    EGLSurface createSurface(EGLNativeWindowType window);
    void destroySurface(EGLSurface surface);

    bool isCurrent(EGLSurface surface) { return mCurrentSurface == surface; }
    // Returns true if the current surface changed, false if it was already current
    bool makeCurrent(EGLSurface surface, EGLint* errOut = nullptr);
    void beginFrame(EGLSurface surface, EGLint* width, EGLint* height);
    bool swapBuffers(EGLSurface surface, const SkRect& dirty, EGLint width, EGLint height);

    // Returns true iff the surface is now preserving buffers.
    bool setPreserveBuffer(EGLSurface surface, bool preserve);
    void setTextureAtlas(const sp<GraphicBuffer>& buffer, int64_t* map, size_t mapSize);
    void fence();

private:
    friend class RenderThread;

    EglManager(RenderThread& thread);
    // EglContext is never destroyed, method is purposely not implemented
    ~EglManager();
    void createPBufferSurface();
    void loadConfig();
    void createContext();
    void initAtlas();
    RenderThread& mRenderThread;
    EGLDisplay mEglDisplay;
    EGLConfig mEglConfig;
    EGLContext mEglContext;
    EGLSurface mPBufferSurface;
    ,,
};
复制代码

而RenderState能够看作是OpenGL状态机的具体呈现,真正负责OpenGL的渲染状态的维护及渲染命令的issue

RenderState::RenderState(renderthread::RenderThread& thread)
        : mRenderThread(thread)
        , mViewportWidth(0)
        , mViewportHeight(0)
        , mFramebuffer(0) {
    mThreadId = pthread_self();
}
复制代码

在RenderProxy建立之初,插入到的第一条消息就是SETUP_TASK(createContext),构建CanvasContext ,它能够看作OpenGL的Context及Surface的封装,

CREATE_BRIDGE4(createContext, RenderThread* thread, bool translucent,
        RenderNode* rootRenderNode, IContextFactory* contextFactory) {
    return new CanvasContext(*args->thread, args->translucent,
            args->rootRenderNode, args->contextFactory);
}
复制代码

能够看到,CanvasContext同时握有RenderThread、EglManager、RootRenderNode等,它能够看作Android中OpenGL上下文,是上层渲染API的入口

CanvasContext::CanvasContext(RenderThread& thread, bool translucent,
        RenderNode* rootRenderNode, IContextFactory* contextFactory)
        : mRenderThread(thread)
        , mEglManager(thread.eglManager())
        , mOpaque(!translucent)
        , mAnimationContext(contextFactory->createAnimationContext(mRenderThread.timeLord()))
        , mRootRenderNode(rootRenderNode)
        , mJankTracker(thread.timeLord().frameIntervalNanos())
        , mProfiler(mFrames) {
    mRenderThread.renderState().registerCanvasContext(this);
    mProfiler.setDensity(mRenderThread.mainDisplayInfo().density);
}
复制代码

其实到这里初始化完成了通常,另外一半是在draw的时候,进行的也就是ThreadRender的initialize,毕竟,若是不须要绘制,是不须要初始化OpenGL环境的,省的浪费资源:

private void performTraversals() {
   ...
      if (mAttachInfo.mHardwareRenderer != null) {
                        try {
                            hwInitialized = mAttachInfo.mHardwareRenderer.initialize(mSurface);
复制代码

这里的mSurface实际上是已经被WMS填充处理过的一个Surface,它在native层对应一个ANativeWindow(其实就是个native的Surface),随着RenderProxy的initial的初始化,EglContext跟EglSurface会被进一步建立,须要注意的是这里的initialize任务是在Render线程,OpenGL的相关操做都必须在Render线程:

CREATE_BRIDGE2(initialize, CanvasContext* context, ANativeWindow* window) {
    return (void*) args->context->initialize(args->window);
}

bool RenderProxy::initialize(const sp<ANativeWindow>& window) {
    SETUP_TASK(initialize);
    args->context = mContext;
    args->window = window.get();
    return (bool) postAndWait(task);
}

bool CanvasContext::initialize(ANativeWindow* window) {
    setSurface(window);
    if (mCanvas) return false;
    mCanvas = new OpenGLRenderer(mRenderThread.renderState());
    mCanvas->initProperties();
    return true;
}
复制代码

这里传入的ANativeWindow* window其实就是native的Surface,CanvasContext在初始化的时候,会经过setSurface为OpenGL建立E关联Con小text、EglSurface画布,同时会为当前窗口建立一个OpenGLRenderer,OpenGLRenderer主要用来处理以前构建的DrawOp,输出对应的OpenGL命令。

void CanvasContext::setSurface(ANativeWindow* window) {
    mNativeWindow = window;
    <!--建立EglSurface画布-->
    if (window) {
        mEglSurface = mEglManager.createSurface(window);
    }
    if (mEglSurface != EGL_NO_SURFACE) {
        const bool preserveBuffer = (mSwapBehavior != kSwap_discardBuffer);
        mBufferPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer);
        mHaveNewSurface = true;
        <!--绑定上下文-->
        makeCurrent();
    }}

EGLSurface EglManager::createSurface(EGLNativeWindowType window) {
	<!--构建EglContext-->
    initialize();
    <!--建立EglSurface-->
    EGLSurface surface = eglCreateWindowSurface(mEglDisplay, mEglConfig, window, nullptr);
 	    return surface;
}

void EglManager::initialize() {
    if (hasEglContext()) return;
    
    mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    loadConfig();
    createContext();
    createPBufferSurface();
    makeCurrent(mPBufferSurface);
    mRenderThread.renderState().onGLContextCreated();
    initAtlas();
}

void EglManager::createContext() {
    EGLint attribs[] = { EGL_CONTEXT_CLIENT_VERSION, GLES_VERSION, EGL_NONE };
    mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT, attribs);
    LOG_ALWAYS_FATAL_IF(mEglContext == EGL_NO_CONTEXT,
        "Failed to create context, error = %s", egl_error_str());
}
复制代码

EglManager::initialize()以后EglContext、Config全都有了,以后经过eglCreateWindowSurface建立EglSurface,这里先调用eglApi.cpp 的eglCreateWindowSurface

EGLSurface eglCreateWindowSurface(  EGLDisplay dpy, EGLConfig config,
                                    NativeWindowType window,
                                    const EGLint *attrib_list) {
        <!--配置-->
        int result = native_window_api_connect(window, NATIVE_WINDOW_API_EGL);
        <!--Android源码中,实际上是调用egl.cpp的eglCreateWindowSurface,不过这一块软件模拟的跟真实硬件的应该差异很少-->	
        // Eglsurface里面是有Surface的引用的,同时swap的时候,是能通知consumer的
        EGLSurface surface = cnx->egl.eglCreateWindowSurface(
                iDpy, config, window, attrib_list);
        ...	}
复制代码

egl.cpp实际上是软件模拟的GPU实现库,不过这里的eglCreateWindowSurface逻辑其实跟真实GPU平台的代码差异不大,由于只是抽象逻辑:

static EGLSurface createWindowSurface(EGLDisplay dpy, EGLConfig config,
        NativeWindowType window, const EGLint* /*attrib_list*/)
{
   ...
    egl_surface_t* surface;
    <!--其实返回的就是egl_window_surface_v2_t-->
    surface = new egl_window_surface_v2_t(dpy, config, depthFormat,
            static_cast<ANativeWindow*>(window));
..	    return surface;
}
复制代码

从上面代码能够看出,其实就是new了一个egl_window_surface_v2_t,它内部封装了一个ANativeWindow,因为EGLSurface是一个Void* 类型指针,所以egl_window_surface_v2_t型指针能够直接赋值给它,到这里初始化环境结束,OpenGL须要的渲染环境已经搭建完毕,等到View须要显示或者更新的时候,就会接着调用VieWrootImpl的draw去更新,注意这里,一个Render线程,默认一个EglContext,可是能够有多个EglSurface,用eglMakeCurrent切换绑定便可。也就是一个Window对应一个ViewRootImpl->一个AttachInfo->ThreadRender对象->ThreadProxy(RootRenderNode)->CanvasContext.cpp(DrawFrameTask、EglManager(单例复用)、EglSurface)->->RenderThread(单例复用),对于APP而言,通常只会维持一个OpenGL渲染线程,固然,你也能够本身new一个独立的渲染线程,主动调用OpenGL API。简答类图以下

image.png

上面工做结束后,OpenGL渲染环境就已经准备好,或者说RenderThread这个渲染线程已经配置好了渲染环境,接下来,UI线程像渲染线程发送渲染任务就好了。

Android OpenGL GPU 渲染

以前分析理解Android硬件加速的小白文的时候,已经分析过,ViewRootImpl的draw是入口,会调用HardwareRender的draw,先构建DrawOp树,而后合并优化DrawOp,以后issue OpenGL命令到GPU,其中构建DrawOp的任务在UI线程,后面的任务都在Render线程

@Override
void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks) {
   <!--构建DrawOp Tree UI线程-->        
   updateRootDisplayList(view, callbacks);
   <!--渲染 提交任务到render线程-->
    int syncResult = nSyncAndDrawFrame(mNativeProxy, frameInfo, frameInfo.length);
    ...
}
复制代码

如上面代码所说updateRootDisplayList构建DrawOp树在UI线程,nSyncAndDrawFrame提交渲染任务到渲染线程,以前已经分析过构建流程,nSyncAndDrawFrame也简单分析了一些合并等操做,下面接着以前流程分析如何将OpenGL命令issue到GPU,这里有个同步问题,可能牵扯到UI线程的阻塞,先分析下同步

SyncAndDrawFrame 同步

static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz,
        jlong proxyPtr, jlongArray frameInfo, jint frameInfoSize) {
    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
    env->GetLongArrayRegion(frameInfo, 0, frameInfoSize, proxy->frameInfo());
    return proxy->syncAndDrawFrame();
}

int DrawFrameTask::drawFrame() {
    mSyncResult = kSync_OK;
    mSyncQueued = systemTime(CLOCK_MONOTONIC);
    postAndWait();
    return mSyncResult;
}

void DrawFrameTask::postAndWait() {
    AutoMutex _lock(mLock);
    mRenderThread->queue(this);
    <!--阻塞等待,同步资源-->
    mSignal.wait(mLock);
}

void DrawFrameTask::run() {
    bool canUnblockUiThread;
    bool canDrawThisFrame;
    {
        TreeInfo info(TreeInfo::MODE_FULL, mRenderThread->renderState());
        <!--同步操做,其实就是同步Java跟native中的构建DrawOp Tree、图层、图像资源-->
        canUnblockUiThread = syncFrameState(info);
        canDrawThisFrame = info.out.canDrawThisFrame;
    }
    // Grab a copy of everything we need
    CanvasContext* context = mContext;
    <!--若是同步完成,则能够返回-->
    if (canUnblockUiThread) {
        unblockUiThread();
    }
	<!--绘制,提交OpenGL命令道GPU-->
    if (CC_LIKELY(canDrawThisFrame)) {
        context->draw();
    }
   <!--看看是否以前由于同步问题阻塞了UI线程,若是阻塞了,须要唤醒-->
    if (!canUnblockUiThread) {
        unblockUiThread();
    }
}
复制代码

其实就是调用RenderProxy的syncAndDrawFrame,将DrawFrameTask插入RenderThread,而且阻塞等待RenderThread跟UI线程同步,若是同步成功,则UI线程唤醒,不然UI线程阻塞等待直到Render线程完成OpenGL命令的issue完毕。同步结束后,以后RenderThread才会开始处理GPU渲染相关工做,先看下同步:

bool DrawFrameTask::syncFrameState(TreeInfo& info) {
    int64_t vsync = mFrameInfo[static_cast<int>(FrameInfoIndex::Vsync)];
    mRenderThread->timeLord().vsyncReceived(vsync);
    mContext->makeCurrent();
    Caches::getInstance().textureCache.resetMarkInUse(mContext);
	
	<!--关键点1,TextureView类处理,主要牵扯纹理-->
    for (size_t i = 0; i < mLayers.size(); i++) {
        // 更新Layer 这里牵扯到图层数据的处理,可能还有拷贝,
        mContext->processLayerUpdate(mLayers[i].get());
    }
    mLayers.clear();
    <!--关键点2 同步DrawOp Tree -->
    mContext->prepareTree(info, mFrameInfo, mSyncQueued);
	 ...
    // If prepareTextures is false, we ran out of texture cache space
    return info.prepareTextures;
}
复制代码

当Window中的TextureView(目前只考虑系统API,好像就这么一个View,自定义除外)有更新时,须要从TextureView的SurfaceTexture中读取图形缓冲区,而且封装绑定成Open GL纹理,供GPU绘制使用,这里不详述,未来有机会分析TexutureView的时候再分析。第二步,是将UI线程中构建的DrawOpTree等信息同步到Render Thread中,由于以前经过ViewRootImpl再Java层调用构建的DisplayListData还没被真正赋值到RenderNode的mDisplayListData(最终用到的对象),只是被setStagingDisplayList暂存,由于中间可能有那种屡次meausre、layout的,还有可能发生改变,暂存逻辑以下:

static void android_view_RenderNode_setDisplayListData(JNIEnv* env,
        jobject clazz, jlong renderNodePtr, jlong newDataPtr) {
    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
    DisplayListData* newData = reinterpret_cast<DisplayListData*>(newDataPtr);
    renderNode->setStagingDisplayList(newData);
}

void RenderNode::setStagingDisplayList(DisplayListData* data) {
    mNeedsDisplayListDataSync = true;
    delete mStagingDisplayListData;
    mStagingDisplayListData = data;
}
复制代码

View的DrawOpTree同步

void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued) {
    mRenderThread.removeFrameCallback(this);

    if (!wasSkipped(mCurrentFrameInfo)) {
        mCurrentFrameInfo = &mFrames.next();
    }

	<!--同步Java层测绘信息到native,OpenGL玄学曲线的来源-->
    mCurrentFrameInfo->importUiThreadInfo(uiFrameInfo);
    mCurrentFrameInfo->set(FrameInfoIndex::SyncQueued) = syncQueued;
    <!--一个计时节点-->
    mCurrentFrameInfo->markSyncStart();
	    info.damageAccumulator = &mDamageAccumulator;
    info.renderer = mCanvas;
    info.canvasContext = this;

    mAnimationContext->startFrame(info.mode);
    // mRootRenderNode递归遍历全部节点
    mRootRenderNode->prepareTree(info);
  ...
复制代码

经过递归遍历,mRootRenderNode能够检查全部的节点,

void RenderNode::prepareTree(TreeInfo& info) {
    bool functorsNeedLayer = Properties::debugOverdraw;
    prepareTreeImpl(info, functorsNeedLayer);
}

void RenderNode::prepareTreeImpl(TreeInfo& info, bool functorsNeedLayer) {
    info.damageAccumulator->pushTransform(this);

    if (info.mode == TreeInfo::MODE_FULL) {
        // 同步属性 
        pushStagingPropertiesChanges(info);
    }
     
    // layer
    prepareLayer(info, animatorDirtyMask);
    <!--同步DrawOpTree-->
    if (info.mode == TreeInfo::MODE_FULL) {
        pushStagingDisplayListChanges(info);
    }
    <!--递归处理子View-->
    prepareSubTree(info, childFunctorsNeedLayer, mDisplayListData);
    // push
    pushLayerUpdate(info);
    info.damageAccumulator->popTransform();
}
复制代码

到这里同步的时候,基本就是最终结果,只要把mStagingDisplayListData赋值到mDisplayListData便可,

void RenderNode::pushStagingDisplayListChanges(TreeInfo& info) {
    if (mNeedsDisplayListDataSync) {
        mNeedsDisplayListDataSync = false;
        ...
        mDisplayListData = mStagingDisplayListData;
        mStagingDisplayListData = nullptr;
        if (mDisplayListData) {
            for (size_t i = 0; i < mDisplayListData->functors.size(); i++) {
                (*mDisplayListData->functors[i])(DrawGlInfo::kModeSync, nullptr);
            }
        }
        damageSelf(info);
    }
}
复制代码

以后经过递归遍历子View,便可以完成完成全部View的RenderNode的同步。

void RenderNode::prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayListData* subtree) {
    if (subtree) {
        TextureCache& cache = Caches::getInstance().textureCache;
        info.out.hasFunctors |= subtree->functors.size();
        <!--吧RenderNode用到的bitmap封装成纹理-->
        for (size_t i = 0; info.prepareTextures && i < subtree->bitmapResources.size(); i++) {
            info.prepareTextures = cache.prefetchAndMarkInUse(
                    info.canvasContext, subtree->bitmapResources[i]);
        }
        <!--递归子View-->
        for (size_t i = 0; i < subtree->children().size(); i++) {
            ...
            childNode->prepareTreeImpl(info, childFunctorsNeedLayer);
            info.damageAccumulator->popTransform();
        }
    }
}
复制代码

当DrawFrameTask::syncFrameState返回值(实际上是TreeInfo的prepareTextures,这里主要是针对Bitmap的处理)为true时,表示同步完成,能够马上唤醒UI线程,可是若是返回false,则就意UI中的数据没彻底传输给GPU,这个状况下UI线程须要等待, 源码中有句注释 If prepareTextures is false, we ran out of texture cache space,其实就是说一个应用程序进程能够建立的Open GL纹理是有大小限制的,若是超出这个限制,纹理就会同步失败,看6.0代码,这个限制有Bitmap自身大小的限制,还有总体可用内存的限制,看代码中的限制

Texture* TextureCache::getCachedTexture(const SkBitmap* bitmap, AtlasUsageType atlasUsageType) {
    if (CC_LIKELY(mAssetAtlas != nullptr) && atlasUsageType == AtlasUsageType::Use) {
        AssetAtlas::Entry* entry = mAssetAtlas->getEntry(bitmap);
        if (CC_UNLIKELY(entry)) {
            return entry->texture;
        }
    }

    Texture* texture = mCache.get(bitmap->pixelRef()->getStableID());

    // 没找到状况下
    if (!texture) {
        // 判断单个限制
        if (!canMakeTextureFromBitmap(bitmap)) {
            return nullptr;
        }

        const uint32_t size = bitmap->rowBytes() * bitmap->height();
        //
        bool canCache = size < mMaxSize;
        // Don't even try to cache a bitmap that's bigger than the cache
        // 剔除Lru算法中老的,再也不用的,若是可以挪出空间,那就算成功,不然失败
        while (canCache && mSize + size > mMaxSize) {
            Texture* oldest = mCache.peekOldestValue();
            if (oldest && !oldest->isInUse) {
                mCache.removeOldest();
            } else {
                canCache = false;
            }
        }
        // 若是能缓存,就新建一个Texture
        if (canCache) {
            texture = new Texture(Caches::getInstance());
            texture->bitmapSize = size;
            generateTexture(bitmap, texture, false);

            mSize += size;
            TEXTURE_LOGD("TextureCache::get: create texture(%p): name, size, mSize = %d, %d, %d",
                     bitmap, texture->id, size, mSize);
            if (mDebugEnabled) {
                ALOGD("Texture created, size = %d", size);
            }
            mCache.put(bitmap->pixelRef()->getStableID(), texture);
        }
    } else if (!texture->isInUse && bitmap->getGenerationID() != texture->generation) {
        // Texture was in the cache but is dirty, re-upload
        // TODO: Re-adjust the cache size if the bitmap's dimensions have changed
        generateTexture(bitmap, texture, true);
    }

    return texture;
}
复制代码

先看单个Bitmap限制:

bool TextureCache::canMakeTextureFromBitmap(const SkBitmap* bitmap) {
if (bitmap->width() > mMaxTextureSize || bitmap->height() > mMaxTextureSize) {
    ALOGW("Bitmap too large to be uploaded into a texture (%dx%d, max=%dx%d)",
            bitmap->width(), bitmap->height(), mMaxTextureSize, mMaxTextureSize);
    return false;
}
return true;
}
复制代码

单个Bitmap大小限制基本上定义:

#define GL_MAX_TEXTURE_SIZE               0x0D33
复制代码

若是bitmap的宽高超过这个值,可能就会同步失败,再看第二个缘由:超过可以Cache纹理总和上限:

#define DEFAULT_TEXTURE_CACHE_SIZE 24.0f 这里是24M
复制代码

若是空间足够,则直接新建一个Texture,若是不够,则根据Lru算法 ,剔除老的再也不使用的Textrue,剔除后的空间若是够,则新建Texture,不然按失败处理,这里虽说得是GPU Cache,其实仍是在同一个内存中,归CPU管理的不过因为对GPU不是太了解,不知道这个数值是否是跟GPU有关系,纹理在须要新建的前提下:

void TextureCache::generateTexture(const SkBitmap* bitmap, Texture* texture, bool regenerate) {
    SkAutoLockPixels alp(*bitmap);
    <!--glGenTextures新建纹理-->
    if (!regenerate) {
        glGenTextures(1, &texture->id);
    }

    texture->generation = bitmap->getGenerationID();
    texture->width = bitmap->width();
    texture->height = bitmap->height();
    <!--绑定纹理-->
    Caches::getInstance().textureState().bindTexture(texture->id);

    switch (bitmap->colorType()) {
    ...
    case kN32_SkColorType:
     // 32位 RGBA 或者BGREA resize第一次都是true,由于一开始宽高确定不一致
        uploadToTexture(resize, GL_RGBA, bitmap->rowBytesAsPixels(), bitmap->bytesPerPixel(),
                texture->width, texture->height, GL_UNSIGNED_BYTE, bitmap->getPixels());
    ...
}
复制代码

上面代码主要是新建纹理,而后为纹理绑定纹理图片资源,绑定资源代码以下:

void TextureCache::uploadToTexture(bool resize, GLenum format, GLsizei stride, GLsizei bpp,
        GLsizei width, GLsizei height, GLenum type, const GLvoid * data) {
    glPixelStorei(GL_UNPACK_ALIGNMENT, bpp);
    const bool useStride = stride != width
            && Caches::getInstance().extensions().hasUnpackRowLength();
   ...
	     if (resize) {
            glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, temp);
        } else {
            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, temp);
        }
复制代码

关键就是调用glTexImage2D将纹理图片跟纹理绑定,OpenGL的glTexImage2D通常会再次拷贝一次图片,以后,Bitmap就能够释放了,到这里就完成了纹理的上传这部分红功了,就算同步成功,UI线程能够再也不阻塞。那么为何同步失败的时候,CPU须要等待呢?我是这么理解的:若是说正常缓存了,调用glTexImage2D完成了一次数据的转移与备份,那么UI线程就不须要维持这份Bitmap对应的数据了,可是若是失败,没有为GPU生成备份,那就要保留这份数据,直到调用glTexImage2D为其生成备份。那为何不把缓存调整很大呢?多是在内存跟性能之间作的一个平衡,若是很大,可能同一时刻为GPU缓存的Bitmap太大,可是这个时候,GPU并无用的到,多是GPU太忙,来不及处理,那么这部份内存实际上是浪费掉的,并且,这个时候CPU明显比GPU快了不少,能够适当让CPU等等,有的解析说防止Bitmap被修改,说实话,我也没太明白,只是我的理解,欢迎纠正,不过这里就算缓存失败,在issue提交OpenGL命令的时候,仍是会再次upload Bitmap的,这大概也是UI阻塞的缘由,这个时段对应的耗时以下:

OpenGL CPU跟GPU关系玄学曲线.jpg

Render线程issue提交OpenGL渲染命令

同步完成后,就能够处理以前的DrawOpTree,装换成标准的OpenGL API,提交OpenGL进行渲染,继续看DrawFrameTask的后半部分,主要是调用CanvasContext的draw,递归以前的DrawOpTree

void CanvasContext::draw() {
   
    EGLint width, height;
    <!--开始绘制,绑定EglSurface, 申请EglSurface须要的内存-->
    mEglManager.beginFrame(mEglSurface, &width, &height);
    ...
    Rect outBounds;
    <!--递归调用OpenGLRender中的OpenGL API,绘制-->
    mCanvas->drawRenderNode(mRootRenderNode.get(), outBounds);
    bool drew = mCanvas->finish();
    // Even if we decided to cancel the frame, from the perspective of jank
    // metrics the frame was swapped at this point
    mCurrentFrameInfo->markSwapBuffers();
    <!--通知提交画布-->
    if (drew) {
        swapBuffers(dirty, width, height);
    }
   ...
}
复制代码
  • 第一步:mEglManager.beginFrame,实际上是标记当前上下文,而且申请绘制内存,由于一个进程中可能存在多个window,也就是多个EglSurface,那么咱们首先须要标记处理哪一个,也就是用哪块画布绘画。以前理解Android硬件加速的小白文说过,硬件加速场景会提早在SurfaceFlinger申请内存坑位,可是并未真正申请内存,这块内存是在真正绘制的时候才去申请,这里申请的内存是让GPU操做的内存,也是未来用来提交给SurfaceFlinger用来合成用的Layer数据;
  • 第二步:递归issue OpenGL命令,提交给GPU绘制;
  • 第三步:经过swapBuffers将绘制好的数据提交给SF去合成(其实GPU极可能并未完成渲染,可是能够提早释放Render线程,这里须要Fence机制保证同步)。不一样的GPU实现不一样,厂商不会将这部分开源,本文结合Android源码(软件实现的OpenGL)跟真机Systrace猜想实现。

先看第一步,经过EglManager让Context绑定当前EglSurface,完成GPU绘制内存的申请

void EglManager::beginFrame(EGLSurface surface, EGLint* width, EGLint* height) {

    makeCurrent(surface);
    ...
    eglBeginFrame(mEglDisplay, surface);
}
复制代码

makeCurrent都会向BnGraphicproducer申请一块内存,对于非本身编写的Render线程,基本都是向SurfaceFlinger申请,

EGLBoolean eglMakeCurrent(  EGLDisplay dpy, EGLSurface draw,
                            EGLSurface read, EGLContext ctx)
{
    ogles_context_t* gl = (ogles_context_t*)ctx;
    if (makeCurrent(gl) == 0) {
        if (ctx) {
            egl_context_t* c = egl_context_t::context(ctx);
            egl_surface_t* d = (egl_surface_t*)draw;
            egl_surface_t* r = (egl_surface_t*)read;
            ...
            if (d) {
            <!--牵扯到申请内存-->
                if (d->connect() == EGL_FALSE) {
                    return EGL_FALSE;
                }
                d->ctx = ctx;
                <!--绑定-->
                d->bindDrawSurface(gl);
            }
           ...
    return setError(EGL_BAD_ACCESS, EGL_FALSE);
}
复制代码

若是是第一次的话,则须要调用egl_surface_t connect,其实就是调用以前建立的egl_window_surface_v2_t的connect,触发申请绘制内存:

EGLBoolean egl_window_surface_v2_t::connect() 
{
 	 // dequeue a buffer
    int fenceFd = -1;
    <!--调用nativeWindow的dequeueBuffer申请绘制内存,获取一个Fence-->
    if (nativeWindow->dequeueBuffer(nativeWindow, &buffer,
            &fenceFd) != NO_ERROR) {
        return setError(EGL_BAD_ALLOC, EGL_FALSE);
    }

    // wait for the buffer  等待申请的内存可用
    sp<Fence> fence(new Fence(fenceFd));
 	...
    return EGL_TRUE;
}
复制代码

上面的nativeWindow其实就是Surface:

int Surface::dequeueBuffer(android_native_buffer_t** buffer, int* fenceFd) {
        ...
    FrameEventHistoryDelta frameTimestamps;
    status_t result = mGraphicBufferProducer->dequeueBuffer(&buf, &fence, reqWidth, reqHeight,
                                                            reqFormat, reqUsage, &mBufferAge,
                                                            enableFrameTimestamps ? &frameTimestamps
                                                                                  : nullptr);
    ... 若是须要从新分配,则requestBuffer,请求分配
    if ((result & IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) || gbuf == nullptr) {
        <!--请求分配-->
        result = mGraphicBufferProducer->requestBuffer(buf, &gbuf);
       }
    ...
复制代码

简单说就是先申请内存坑位,若是该坑位的内存须要从新分配,则再申请分配匿名共享内存,这里分配的内存才是EglSurface(Surface)绘制所需内存(硬件加速),接下来就能够通知OpenGL渲染绘制了。上面流程牵扯到一个Fence机制,其实就是一种协助生产者消费者的机制,主要做用是处理GPU跟CPU的同步上,先不谈。先走完流程,CanvasContext的mCanvas实际上是OpenGLRenderer,接着看OpenGLRenderer的drawRenderNode:

void OpenGLRenderer::drawRenderNode(RenderNode* renderNode, Rect& dirty, int32_t replayFlags) {
    // All the usual checks and setup operations (quickReject, setupDraw, etc.)
    // will be performed by the display list itself
    if (renderNode && renderNode->isRenderable()) {
        // compute 3d ordering
        <!--计算Z顺序-->
        renderNode->computeOrdering();
        <!--若是禁止合并Op直接绘制-->
        if (CC_UNLIKELY(Properties::drawDeferDisabled)) {
            startFrame();
            ReplayStateStruct replayStruct(*this, dirty, replayFlags);
            renderNode->replay(replayStruct, 0);
            return;
        }
       ...
        DeferredDisplayList deferredList(mState.currentClipRect(), avoidOverdraw);
        DeferStateStruct deferStruct(deferredList, *this, replayFlags);
        <!--合并-->
        renderNode->defer(deferStruct, 0);
		 <!--处理文理图层-->
        flushLayers();
        <!--设置视窗-->
        startFrame();
       <!--flush,生成并提交OpenGL命令-->
        deferredList.flush(*this, dirty);
    } ...
复制代码

计算Z order跟合并DrawOp以前简单说过,不分析,这里只看flushLayers跟最终的issue OpenGL 命令(deferredList.flush,其实也是遍历每一个DrawOp,调用本身的draw函数),flushLayers主要是处理TextureView,为了简化,先不考虑,假设不存在此类试图,那么只看flush便可,

void DeferredDisplayList::flush(OpenGLRenderer& renderer, Rect& dirty) {
    ...
    replayBatchList(mBatches, renderer, dirty);
	...
}

 static void replayBatchList(const Vector<Batch*>& batchList,
        OpenGLRenderer& renderer, Rect& dirty) {
    for (unsigned int i = 0; i < batchList.size(); i++) {
        if (batchList[i]) {
            batchList[i]->replay(renderer, dirty, i);
        }
    }
}
复制代码

DrawOp合并

virtual void  DrawBatch::replay(OpenGLRenderer& renderer, Rect& dirty, int index) override {
  	        for (unsigned int i = 0; i < mOps.size(); i++) {
            DrawOp* op = mOps[i].op;
            const DeferredDisplayState* state = mOps[i].state;
            renderer.restoreDisplayState(*state);
            op->applyDraw(renderer, dirty);     }  }
复制代码

递归每一个合并后的Batch,接着处理Batch中每一个DrawOp,调用其replay,以DrawPointsOp画点为例:

class DrawPointsOp : public DrawLinesOp {
public:
    DrawPointsOp(const float* points, int count, const SkPaint* paint)
            : DrawLinesOp(points, count, paint) {}

    virtual void applyDraw(OpenGLRenderer& renderer, Rect& dirty) override {
        renderer.drawPoints(mPoints, mCount, mPaint);
    }
...
复制代码

最终调用OpenGLrender的drawPoints

void OpenGLRenderer::drawPoints(const float* points, int count, const SkPaint* paint) {
    ...
	 count &= ~0x1; 
	<!--构建VertexBuffer-->
    VertexBuffer buffer;
    PathTessellator::tessellatePoints(points, count, paint, *currentTransform(), buffer);
     ...	
    int displayFlags = paint->isAntiAlias() ? 0 : kVertexBuffer_Offset;
    <!--使用buffer paint绘制 -->
    drawVertexBuffer(buffer, paint, displayFlags);
    mDirty = true;
}

void OpenGLRenderer::drawVertexBuffer(float translateX, float translateY,
        const VertexBuffer& vertexBuffer, const SkPaint* paint, int displayFlags) {
    /...
    Glop glop;
    GlopBuilder(mRenderState, mCaches, &glop)
            .setRoundRectClipState(currentSnapshot()->roundRectClipState)
            .setMeshVertexBuffer(vertexBuffer, shadowInterp)
            .setFillPaint(*paint, currentSnapshot()->alpha)
             ...
            .build();
    renderGlop(glop);
}

void OpenGLRenderer::renderGlop(const Glop& glop, GlopRenderType type) {
...
mRenderState.render(glop);
...
复制代码

Vertex是OpenGL的基础概念,drawVertexBuffer调用RenderState的render,向GPU提交绘制命令(不会当即绘制,GPU也是由缓冲区的,除非手动glFinish或者glFlush,才会即刻渲染),RenderState能够看作OpenGL状态机的抽象,render函数实现以下

void RenderState::render(const Glop& glop) {
    const Glop::Mesh& mesh = glop.mesh;
    const Glop::Mesh::Vertices& vertices = mesh.vertices;
    const Glop::Mesh::Indices& indices = mesh.indices;
    const Glop::Fill& fill = glop.fill;
    // ---------------------------------------------
    // ---------- Program + uniform setup ----------
    // ---------------------------------------------
    mCaches->setProgram(fill.program);

    if (fill.colorEnabled) {
        fill.program->setColor(fill.color);
    }

    fill.program->set(glop.transform.ortho,
            glop.transform.modelView,
            glop.transform.meshTransform(),
            glop.transform.transformFlags & TransformFlags::OffsetByFudgeFactor);

    // Color filter uniforms
    if (fill.filterMode == ProgramDescription::kColorBlend) {
        const FloatColor& color = fill.filter.color;
        glUniform4f(mCaches->program().getUniform("colorBlend"),
                color.r, color.g, color.b, color.a);
    }
     ....
	 // ---------- Mesh setup ----------
    // vertices
    const bool force = meshState().bindMeshBufferInternal(vertices.bufferObject)
            || (vertices.position != nullptr);
    meshState().bindPositionVertexPointer(force, vertices.position, vertices.stride);

    // indices
    meshState().bindIndicesBufferInternal(indices.bufferObject);
    ...
    // ------------------------------------
    // ---------- GL state setup ----------
    // ------------------------------------
    blend().setFactors(glop.blend.src, glop.blend.dst);
    // ------------------------------------
    // ---------- Actual drawing ----------
    // ------------------------------------
    if (indices.bufferObject == meshState().getQuadListIBO()) {
        // Since the indexed quad list is of limited length, we loop over
        // the glDrawXXX method while updating the vertex pointer
        GLsizei elementsCount = mesh.elementCount;
        const GLbyte* vertexData = static_cast<const GLbyte*>(vertices.position);
        while (elementsCount > 0) {
            ...
            glDrawElements(mesh.primitiveMode, drawCount, GL_UNSIGNED_SHORT, nullptr);
            elementsCount -= drawCount;
            vertexData += (drawCount / 6) * 4 * vertices.stride;  } }  
            ...
}
复制代码

能够看到,通过一步步的设置,变换,预处理,最后都是要转换成glXXX函数,生成相应的OpenGL命令发送给GPU,通知GPU绘制,这里有两种处理方式,第一种是CPU阻塞等待GPU绘制结束后返回,再将绘制内容提交给SurfaceFlinger进行合成,第二种是CPU直接返回,而后提交给SurfaceFlinger合成,等到SurfaceFlinger合成的时候,若是还未绘制完毕,则须要阻塞等待GPU绘制完毕,软件实现的采用的是第一种,硬件实现的通常是第二种。须要注意:OpenGL绘制前各类准备包括传给GPU使用的内存都是CPU在APP的私有内存空间申请的,而GPU真正绘制到画布使用的提交给SurfaceFlinger的那块内存,是从匿名共享申请的内存,二者是不同的,这一部分的耗时,其实就是CPU 将命令同步给GPU的耗时,在OpenGL玄学曲线中是:

构建OpenGL命令.jpg

Render线程swapBuffers提交图形缓冲区(加Fence机制)

在Android里,GraphicBuffer的同步主要借助Fence同步机制,它最大的特色是可以处理GPU、CPU、HWC间的同步。由于,GPU处理通常是异步的,当咱们调用OpenGL API返回后,OpenGL命令并非即刻被GPU执行的,而是被缓存在本地的GL命令缓冲区中,等缓冲区满的时候,才会真正通知GPU执行,而CPU可能彻底不知道执行时机,除非CPU主动使用glFinish()强制刷新,阻塞等待这些命令执行完,可是,毫无疑问,这会使得CPU、GPU并行处理效率下降,至少,渲染线程是被阻塞在那里的;相对而言异步处理的效率要高一些,CPU提交命令后就返回,不等待GPU处理完,这样渲染线程被解放处理下一条消息,不过这个时候图形未被处理完毕的前提的下就被提交给SurfaceFlinger图形合成,那么SurfaceFlinger须要知道何时这个GraphicBuffer被GPU处理填充完毕,这个时候就是Fence机制发挥做用的地方,关于Fence不过多分析,毕竟牵扯信息也挺多,只简单画了示意图:

Fence示意图.jpg

以前的命令被issue完毕后,CPU通常会发送最后一个命令给GPU,告诉GPU当前命令发送完毕,能够处理,GPU通常而言须要返回一个确认的指令,不过,这里并不表明执行完毕,仅仅是通知到而已,若是GPU比较忙,来不及回复通知,则CPU须要阻塞等待,CPU收到通知后,会唤起当前阻塞的Render线程,继续处理下一条消息,这个阶段是在swapBuffers中完成的,Google给的解释以下:

Once Android finishes submitting all its display list to the GPU, the system issues one final command to tell the graphics driver that it's done with the current frame. At this point, the driver can finally present the updated image to the screen.

It’s important to understand that the GPU executes work in parallel with the CPU. The Android system issues draw commands to the GPU, and then moves on to the next task. The GPU reads those draw commands from a queue and processes them.

In situations where the CPU issues commands faster than the GPU consumes them, the communications queue between the processors can become full. When this occurs, the CPU blocks, and waits until there is space in the queue to place the next command. This full-queue state arises often during the Swap Buffers stage, because at that point, a whole frame’s worth of commands have been submitted

但看Android源码而言,软件实现的libagl能够看作同步的,不须要考虑Fence机制:

EGLBoolean egl_window_surface_v2_t::swapBuffers()
{
  	...
    // 其实就是queueBuffer,queueBuffer这里用的是-1
    nativeWindow->queueBuffer(nativeWindow, buffer, -1);
    buffer = 0;
    // dequeue a new buffer
    int fenceFd = -1;
    // 这里是为了什么,仍是阻塞等待,难道是为了等待GPU处理完成吗?  
    // buffer换buffer
    if (nativeWindow->dequeueBuffer(nativeWindow, &buffer, &fenceFd) == NO_ERROR) {
        sp<Fence> fence(new Fence(fenceFd));
        // fence->wait
        if (fence->wait(Fence::TIMEOUT_NEVER)) {
            nativeWindow->cancelBuffer(nativeWindow, buffer, );
            return setError(EGL_BAD_ALLOC, EGL_FALSE);
        }
		...
复制代码

能够看到,源码中是先将Buffer提交给SurfaceFlinger,而后再申请一个Buffer用来处理下一次请求。而且这里queueBuffer传递的Fence是-1,也就在swapbuffer的时候,软件实现的OpenGL库是不须要Fence机制的(压根不须要考虑GPU、CPU同步)。queueBuffer会触发Layer回调,并向SurfaceFlinger发送消息,请求SurfaceFlinger执行,这里是一个异步过程,不会致使阻塞,回调入口在Layer的onFrameAvailable

void Layer::onFrameAvailable(const BufferItem& item) {
    { 
    ...queueBuffer后触发Layer的onFrameAvailable回调,
    mFlinger->signalLayerUpdate();
}
复制代码

而dequeueBuffer在slot上限容许的前提下,也不会阻塞,按理说,不会怎么耗时,可是就模拟器效果而言,swapBuffers好像耗时比较严重(其中下图的黄色部分就是swapBuffers耗时),这里不太理解,由于模拟器应该是同步的,应该不会牵扯缓冲区交换时也不会隐式将命令送去GPU执行,也不会阻塞等待,为何耗时这么多呢,模拟器的(Genymotion 6.0),不知道是否是跟Genymotion有关系:

image.png

再看一下Genymotion 的Systrace:

image.png

能够看到,Systace中的函数调用基本跟egl.cpp中基本一致,可是queue跟dequeue buffer为何耗时这么久呢?有些不理解,但愿有人能指点。而对于硬件真机,通常须要处理Fence,其egl_window_surface_v2_t::swapBuffers()应该会被重写,至少须要传递一个有效的Fence过去,

nativeWindow->queueBuffer(nativeWindow, buffer, fenceId(不该该再是-1));
复制代码

也就是说,queueBuffer的fenceid不能再是-1了,由于须要一个有效的Fence处理GPU CPU同步,再再看下真机的Systrace(nexus5 6.0)

真机OpenGL渲染Systrace

能够看到真机函数的调用跟模拟器差异很大,好比dequeue、enqueue,具体可能要看各家的实现了,再看8.0的nexus6p:

nexus6p 8.0

一开始我觉得,swapBuffers会在某个地方调用glFinish()或者glFlush,这个时候可能会阻塞,致使耗时增长,可是看源码说不通,由于好像也跟就不会在enqueue或者dequeue的时候直接触发,就算触发,也是异步的。通常,issue任务给驱动后,若是采用是双缓冲,在缓冲区交换操做会隐式将命令送去执行,这里猜测是不一样厂商本身实现,可是看不到具体的代码,也很差肯定,谁作rom的但愿能指点下。 这段时间的耗时在GPU呈现曲线上以下,文档解释说是CPU等待GPU的时间,我的理解:是等待时间,可是不是等待GPU完成渲染的时间,仅仅是等待一个ACK类的信号,不然,就不存在CPU、GPU并行了:

swapbuffer耗时.jpg

dequeueBuffer会阻塞致使耗时增长吗?应该也不会,关于swapbuffer这段时间的耗时有空再看了

总结

  • UI线程构建OpenGL的DrawOpTree
  • Render线程负责DrawOpTree合并优化、数据的同步
  • Render线程负责将DrawOp转换成标准OpenGL命令,并isssue给GPU
  • Render线程经过swapbuffer通知GPU(待研究),同时完成向SurfaceFlinger画布数据的提交

做者:看书的小蜗牛 Android硬件加速(二)-RenderThread与OpenGL GPU渲染

仅供参考,欢迎指正