主頁 >  其他 > 掌握Android影像顯示原理(中)

掌握Android影像顯示原理(中)

2020-09-22 04:40:53 其他

前言

在上一篇文章《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生成圖形呢?其實比較簡單,主要有這三步

  1. EGL初始化Display,Context和Surface
  2. OpenGL ES呼叫繪制指令
  3. 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函式主要做的事情如下

  1. 通過檔案路徑加載影片
  2. 呼叫OpenGL做清屏操作
  3. 呼叫playAnimation函式播放影片,
  4. 停止播放影片后通過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;
    }

    //……

    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
        if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
            if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
                
                //……

                //硬體渲染
                mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);

            } else {
                
                //……

                //軟體渲染
                if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                    return;
                }
            }
        }

        //……
    }

    //……
}

從上面的代碼可以看到,硬體渲染是通過mThreadedRenderer.draw方法進行的,在分析mThreadedRenderer.draw函式之前,我們需要先了解ThreadedRenderer是什么,它的創建要在Measure,Layout和Draw的流程之前,當我們在Activity的onCreate回呼中執行setContentView函式時,最侄訓執行ViewRootImpl的setView方法,ThreadedRenderer就是在這個此時被創建的,

//檔案-->/frameworks/base/core/java/android/view/ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        if (mView == null) {
            mView = view;

            //……            
            if (mSurfaceHolder == null) {
                enableHardwareAcceleration(attrs);
            }

            //……
        }
    }
}

private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) {
    mAttachInfo.mHardwareAccelerated = false;
    mAttachInfo.mHardwareAccelerationRequested = false;

    // 兼容模式下不開啟硬體加速
    if (mTranslator != null) return;

    
    final boolean hardwareAccelerated =
            (attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0;

    if (hardwareAccelerated) {
        if (!ThreadedRenderer.isAvailable()) {
            return;
        }

        //……

        if (fakeHwAccelerated) {
            //……
        } else if (!ThreadedRenderer.sRendererDisabled
                || (ThreadedRenderer.sSystemRendererDisabled && forceHwAccelerated)) {
            //……
			//創建ThreadedRenderer
            mAttachInfo.mThreadedRenderer = ThreadedRenderer.create(mContext, translucent,
                    attrs.getTitle().toString());
            if (mAttachInfo.mThreadedRenderer != null) {
                mAttachInfo.mHardwareAccelerated =
                        mAttachInfo.mHardwareAccelerationRequested = true;
            }
        }
    }
}

可以看到,當RootViewImpl在呼叫setView的時候,會開啟硬體加速,并通過ThreadedRenderer.create函式來創建ThreadedRenderer,

我們繼續看看ThreadedRenderer這個類的實作,

//檔案-->/frameworks/base/core/java/android/view/ThreadedRenderer.java
public static ThreadedRenderer create(Context context, boolean translucent, String name) {
    ThreadedRenderer renderer = null;
    if (isAvailable()) {
        renderer = new ThreadedRenderer(context, translucent, name);
    }
    return renderer;
}

ThreadedRenderer(Context context, boolean translucent, String name) {
    //……
	
    //創建RootRenderNode
    long rootNodePtr = nCreateRootRenderNode();
    mRootNode = RenderNode.adopt(rootNodePtr);
    mRootNode.setClipToBounds(false);
    mIsOpaque = !translucent;
    //創建RenderProxy
    mNativeProxy = nCreateProxy(translucent, rootNodePtr);
    nSetName(mNativeProxy, name);
	//啟動GraphicsStatsService,統計渲染資訊
    ProcessInitializer.sInstance.init(context, mNativeProxy);
	
    loadSystemProperties();
}

ThreadedRenderer的建構式中主要做了這兩件事情:

  1. 通過JNI方法nCreateRootRenderNode在Native創建RootRenderNode,每一個View都對應了一個RenderNode,它包含了這個View及其子view的DisplayList,DisplayList包含了是可以讓openGL識別的渲染指令,這些渲染指令被封裝成了一條條OP,
//檔案-->/frameworks/base/core/jni/android_view_ThreadedRenderer.cpp
static jlong android_view_ThreadedRenderer_createRootRenderNode(JNIEnv* env, jobject clazz) {
    RootRenderNode* node = new RootRenderNode(env);
    node->incStrong(0);
    node->setName("RootRenderNode");
    return reinterpret_cast<jlong>(node);
}
  1. 通過Jni方法nCreateProxy在Native層的RenderProxy,它就是用來跟渲染執行緒進行通信的句柄,我們看下nCreateProxy的Native實作
//檔案-->/frameworks/base/core/jni/android_view_ThreadedRenderer.cpp
static jlong android_view_ThreadedRenderer_createProxy(JNIEnv* env, jobject clazz,
        jboolean translucent, jlong rootRenderNodePtr) {
    RootRenderNode* rootRenderNode = reinterpret_cast<RootRenderNode*>(rootRenderNodePtr);
    ContextFactoryImpl factory(rootRenderNode);
    return (jlong) new RenderProxy(translucent, rootRenderNode, &factory);
}

//檔案-->/frameworks/base/libs/hwui/renderthread/RenderProxy.cpp
RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode, IContextFactory* contextFactory)
        : mRenderThread(RenderThread::getInstance())
        , mContext(nullptr) {
    SETUP_TASK(createContext);
    args->translucent = translucent;
    args->rootRenderNode = rootRenderNode;
    args->thread = &mRenderThread;
    args->contextFactory = contextFactory;
    mContext = (CanvasContext*) postAndWait(task);
    mDrawFrameTask.setContext(&mRenderThread, mContext, rootRenderNode);
}

從RenderProxy建構式可以看到,通過RenderThread::getInstance()創建了RenderThread,也就是硬體繪制的渲染執行緒,相比于在主執行緒進行的軟體繪制,硬體加速會新建一個執行緒,這樣能減輕主執行緒的作業量,

了解了ThreadedRenderer的創建和初始化流程,我們繼續回到渲染的流程mThreadedRenderer.draw這個函式中來,先看看這個函式的原始碼,

//檔案-->/frameworks/base/core/java/android/view/ThreadedRenderer.java
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
    attachInfo.mIgnoreDirtyState = true;

    final Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer;
    choreographer.mFrameInfo.markDrawStart();

    //1,構建RootView的DisplayList
    updateRootDisplayList(view, callbacks);

    attachInfo.mIgnoreDirtyState = false;

    //…… 視窗影片處理

    final long[] frameInfo = choreographer.mFrameInfo.mFrameInfo;
    //2,通知渲染
    int syncResult = nSyncAndDrawFrame(mNativeProxy, frameInfo, frameInfo.length);
    
    //…… 渲染失敗的處理
}

這個流程我們只需要關心這兩件事情:

  1. 構建DisplayList
  2. 繪制DisplayList**

經過這兩步,界面就顯示出來,我們詳細看一下這這兩步的流程:

構建DisplayList

1,通過updateRootDisplayList函式構建根view的DisplayList,DisplayList在前面提到過,它包含了可以讓openGL識別的渲染指令,先看看函式的實作

//檔案-->/frameworks/base/core/java/android/view/ThreadedRenderer.java
private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Record View#draw()");
	//構建View的DisplayList
    updateViewTreeDisplayList(view);

    if (mRootNodeNeedsUpdate || !mRootNode.isValid()) {
        //獲取DisplayListCanvas
        DisplayListCanvas canvas = mRootNode.start(mSurfaceWidth, mSurfaceHeight);
        try {
            final int saveCount = canvas.save();
            canvas.translate(mInsetLeft, mInsetTop);
            callbacks.onPreDraw(canvas);

            canvas.insertReorderBarrier();
            //合并和優化DisplayList
            canvas.drawRenderNode(view.updateDisplayListIfDirty());
            canvas.insertInorderBarrier();

            callbacks.onPostDraw(canvas);
            canvas.restoreToCount(saveCount);
            mRootNodeNeedsUpdate = false;
        } finally {
            //更新RootRenderNode
            mRootNode.end(canvas);
        }
    }
    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}

updateRootDisplayList函式的主要流程有這幾步:

  1. 構建根View的DisplayList
  2. 合并和優化DisplayList
構建根View的DisplayList

我們先看第一步構建根View的DisplayList的原始碼,

//檔案-->/frameworks/base/core/java/android/view/ThreadedRenderer.java
private void updateViewTreeDisplayList(View view) {
    view.mPrivateFlags |= View.PFLAG_DRAWN;
    view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
            == View.PFLAG_INVALIDATED;
    view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
    view.updateDisplayListIfDirty();
    view.mRecreateDisplayList = false;
}

//檔案-->/frameworks/base/core/java/android/view/View.java
public RenderNode updateDisplayListIfDirty() {
    final RenderNode renderNode = mRenderNode;
    if (!canHaveDisplayList()) {
        return renderNode;
    }

   	//判斷硬體加速是否可用
    if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
        || !renderNode.isValid()
        || (mRecreateDisplayList)) {
        //…… 不需要更新displaylist時,則直接回傳renderNode
        

        
		//獲取DisplayListCanvas
        final DisplayListCanvas canvas = renderNode.start(width, height);       

        try {
            if (layerType == LAYER_TYPE_SOFTWARE) {
                //如果強制開啟了軟體繪制,比如一些不支持硬體加速的組件,或者靜止了硬體加速的組件,會轉換成bitmap后,交給硬體渲染
                buildDrawingCache(true);
                Bitmap cache = getDrawingCache(true);
                if (cache != null) {
                    canvas.drawBitmap(cache, 0, 0, mLayerPaint);
                }
            } else {                               
                if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                    //遞回子View構建或更新displaylist
                    dispatchDraw(canvas);                    
                } else {
                    //呼叫自身的draw方法
                    draw(canvas);
                }
            }
        } finally {
            //講DisplayListCanvas內容系結到renderNode上
            renderNode.end(canvas);
            setDisplayListProperties(renderNode);
        }
    } else {
        mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
    }
    return renderNode;
}

可以看到updateDisplayListIfDirty主要做的事情有這幾件

  1. 獲取DisplayListCanvas
  2. 判斷組件是否支持硬體加速,不支持則轉換成bitmap后交給DisplayListCanvas
  3. 遞回子View執行DisplayList的構建
  4. 呼叫自身的draw方法,交給DisplayListCanvas進行繪制
  5. 回傳RenderNode

看到這里可能會有人疑問,為什么構建更新DisplayList函式updateDisplayListIfDirty中并沒有看到DisplayList,回傳物件也不是DisplayList,而是RenderNode?這個DisplayList其實是在Native層創建的,在前面提到過RenderNode其實包含了DisplayList,renderNode.end(canvas)函式會將DisplayList系結到renderNode中,而DisplayListCanvas的作用,就是在Native層創建DisplayList,那么我們接著看DisplayListCanvas這個類,

//檔案-->/frameworks/base/core/java/android/view/RenderNode.java
public DisplayListCanvas start(int width, int height) {
    return DisplayListCanvas.obtain(this, width, height);
}

//檔案-->/frameworks/base/core/java/android/view/DisplayListCanvas.java
static DisplayListCanvas obtain(@NonNull RenderNode node, int width, int height) {
    if (node == null) throw new IllegalArgumentException("node cannot be null");
    DisplayListCanvas canvas = sPool.acquire();
    if (canvas == null) {
        canvas = new DisplayListCanvas(node, width, height);
    } else {
        nResetDisplayListCanvas(canvas.mNativeCanvasWrapper, node.mNativeRenderNode,
                                width, height);
    }
    canvas.mNode = node;
    canvas.mWidth = width;
    canvas.mHeight = height;
    return canvas;
}

private DisplayListCanvas(@NonNull RenderNode node, int width, int height) {
    super(nCreateDisplayListCanvas(node.mNativeRenderNode, width, height));
    mDensity = 0; // disable bitmap density scaling
}

我們通過RenderNode.start方法獲取一個DisplayListCanvas,RenderNode會通過obtain來創建或從快取中獲取DisplayListCanvas,這是一種享元模式,DisplayListCanvas的建構式里,會通過JNI方法nCreateDisplayListCanvas創建native的Canvas,我們接著看一下Native的流程

//檔案-->/frameworks/base/core/jni/android_view_DisplayListCanvas.cpp
static jlong android_view_DisplayListCanvas_createDisplayListCanvas(jlong renderNodePtr,
        jint width, jint height) {
    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
    return reinterpret_cast<jlong>(Canvas::create_recording_canvas(width, height, renderNode));
}

//檔案-->/frameworks/base/libs/hwui/hwui/Canvas.cpp
Canvas* Canvas::create_recording_canvas(int width, int height, uirenderer::RenderNode* renderNode) {
    if (uirenderer::Properties::isSkiaEnabled()) {
        return new uirenderer::skiapipeline::SkiaRecordingCanvas(renderNode, width, height);
    }
    return new uirenderer::RecordingCanvas(width, height);
}

可以看到,java層的DisplayListCanvas對應了native層RecordingCanvas或者SkiaRecordingCanvas

這里簡單介紹一下這兩個Canvas的區別,在Android8之前,HWUI中通過OpenGL對繪制操作進行封裝后,直接送GPU進行渲染,Android 8.0開始,HWUI進行了重構,增加了RenderPipeline的概念,RenderPipeline有三種型別,分別為Skia,OpenGL和Vulkan,分別對應不同的渲染,并且Android8.0開始強化和重視Skia的地位,Android10版本后,所有通過硬體加速的渲染,都是通過SKIA進行封裝,然后再經過OpenGL或Vulkan,最后交給GPU渲染,我講解的原始碼是8.0的原始碼,可以看到,其實已經可以通過配置,來開啟skiapipeline了,

在這里插入圖片描述

為了更容易的講解如何通過OpenGL進行硬體渲染,這里我還是以RecordingCanvas來講解,這里列舉幾個RecordingCanvas中的常規操作

//檔案-->/frameworks/base/libs/hwui/RecordingCanvas.cpp
//繪制點
void RecordingCanvas::drawPoints(const float* points, int floatCount, const SkPaint& paint) {
    if (CC_UNLIKELY(floatCount < 2 || paint.nothingToDraw())) return;
    floatCount &= ~0x1; // round down to nearest two

    addOp(alloc().create_trivial<PointsOp>(
            calcBoundsOfPoints(points, floatCount),
            *mState.currentSnapshot()->transform,
            getRecordedClip(),
            refPaint(&paint), refBuffer<float>(points, floatCount), floatCount));
}

struct PointsOp : RecordedOp {
    PointsOp(BASE_PARAMS, const float* points, const int floatCount)
            : SUPER(PointsOp)
            , points(points)
            , floatCount(floatCount) {}
    const float* points;
    const int floatCount;
};
//繪制線
void RecordingCanvas::drawLines(const float* points, int floatCount, const SkPaint& paint) {
    if (CC_UNLIKELY(floatCount < 4 || paint.nothingToDraw())) return;
    floatCount &= ~0x3; // round down to nearest four

    addOp(alloc().create_trivial<LinesOp>(
            calcBoundsOfPoints(points, floatCount),
            *mState.currentSnapshot()->transform,
            getRecordedClip(),
            refPaint(&paint), refBuffer<float>(points, floatCount), floatCount));
}
struct LinesOp : RecordedOp {
    LinesOp(BASE_PARAMS, const float* points, const int floatCount)
            : SUPER(LinesOp)
            , points(points)
            , floatCount(floatCount) {}
    const float* points;
    const int floatCount;
};

//繪制矩陣
void RecordingCanvas::drawRect(float left, float top, float right, float bottom, const SkPaint& paint) {
    if (CC_UNLIKELY(paint.nothingToDraw())) return;

    addOp(alloc().create_trivial<RectOp>(
            Rect(left, top, right, bottom),
            *(mState.currentSnapshot()->transform),
            getRecordedClip(),
            refPaint(&paint)));
}

struct RectOp : RecordedOp {
    RectOp(BASE_PARAMS)
            : SUPER(RectOp) {}
};

struct RoundRectOp : RecordedOp {
    RoundRectOp(BASE_PARAMS, float rx, float ry)
            : SUPER(RoundRectOp)
            , rx(rx)
            , ry(ry) {}
    const float rx;
    const float ry;
};

int RecordingCanvas::addOp(RecordedOp* op) {
    // skip op with empty clip
    if (op->localClip && op->localClip->rect.isEmpty()) {
        // NOTE: this rejection happens after op construction/content ref-ing, so content ref'd
        // and held by renderthread isn't affected by clip rejection.
        // Could rewind alloc here if desired, but callers would have to not touch op afterwards.
        return -1;
    }

    int insertIndex = mDisplayList->ops.size();
    mDisplayList->ops.push_back(op);
    if (mDeferredBarrierType != DeferredBarrierType::None) {
        // op is first in new chunk
        mDisplayList->chunks.emplace_back();
        DisplayList::Chunk& newChunk = mDisplayList->chunks.back();
        newChunk.beginOpIndex = insertIndex;
        newChunk.endOpIndex = insertIndex + 1;
        newChunk.reorderChildren = (mDeferredBarrierType == DeferredBarrierType::OutOfOrder);
        newChunk.reorderClip = mDeferredBarrierClip;

        int nextChildIndex = mDisplayList->children.size();
        newChunk.beginChildIndex = newChunk.endChildIndex = nextChildIndex;
        mDeferredBarrierType = DeferredBarrierType::None;
    } else {
        // standard case - append to existing chunk
        mDisplayList->chunks.back().endOpIndex = insertIndex + 1;
    }
    return insertIndex;
}

可以看到,我們通過RecordingCanvas繪制的圖元,都被封裝成了一個個能夠讓GPU能夠識別的OP,這些OP都存盤在了mDisplayList中,這就回答了前面的疑問,為什么updateDisplayListIfDirty沒有看到DisplayList,因為DisplayListCanvas通過呼叫Natice層的RecordingCanvas,更新了Natice層的mDisplayList,

我們在接著看renderNode.end(canvas)函式,如何將Natice層的DisplayList系結到renderNode中,

//檔案-->/frameworks/base/core/java/android/view/RenderNode.java
public void end(DisplayListCanvas canvas) {
    long displayList = canvas.finishRecording();
    nSetDisplayList(mNativeRenderNode, displayList);
    canvas.recycle();
}

這里通過JNI方法nSetDisplayList進行了DisplayList和RenderNode的系結,此時,我們就能理解我在前面說的:RenderNode包含了這個View及其子view的DisplayList,DisplayList包含了一條條可以讓openGL識別的渲染指令——OP操作,它是一個基本的能讓GPU識別的繪制元素,

合并和優化DisplayList

updateViewTreeDisplayList花了比較大精力,將所有的View的DisplayList已經創建好了,DisplayList里的DrawOP樹也創建好了,為什么還要在呼叫**canvas.drawRenderNode(view.updateDisplayListIfDirty())**這個函式呢?這個函式的主要功能是對前面構建的DisplayList做優化和合并處理,我們看看具體的實作細節,

//檔案-->/frameworks/base/core/java/android/view/DisplayListCanvas.java
public void drawRenderNode(RenderNode renderNode) {
    nDrawRenderNode(mNativeCanvasWrapper, renderNode.getNativeDisplayList());
}

//檔案-->/frameworks/base/core/jni/android_view_DisplayListCanvas.cpp
static void android_view_DisplayListCanvas_drawRenderNode(jlong canvasPtr, jlong renderNodePtr) {
    Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr);
    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
    canvas->drawRenderNode(renderNode);
}

//檔案-->/frameworks/base/libs/hwui/RecordingCanvas.cpp
void RecordingCanvas::drawRenderNode(RenderNode* renderNode) {
    auto&& stagingProps = renderNode->stagingProperties();
    RenderNodeOp* op = alloc().create_trivial<RenderNodeOp>(
            Rect(stagingProps.getWidth(), stagingProps.getHeight()),
            *(mState.currentSnapshot()->transform),
            getRecordedClip(),
            renderNode);
    int opIndex = addOp(op);
    if (CC_LIKELY(opIndex >= 0)) {
        int childIndex = mDisplayList->addChild(op);

        // update the chunk's child indices
        DisplayList::Chunk& chunk = mDisplayList->chunks.back();
        chunk.endChildIndex = childIndex + 1;

        if (renderNode->stagingProperties().isProjectionReceiver()) {
            // use staging property, since recording on UI thread
            mDisplayList->projectionReceiveIndex = opIndex;
        }
    }
}

可以看到,最終執行到了RecordingCanvas中的drawRenderNode函式,這個函式會對DisplayList做合并和優化,

繪制DisplayList

經過比較長的篇幅,我們把mThreadedRenderer.draw函式中的第一個流程,構建DisplayList說完,現在開始說第二個流程,nSyncAndDrawFrame進行幀繪制,這個流程結束,我們的界面就能在螢屏上顯示出來了,nSyncAndDrawFrame是一個native方法,我們看看它的實作

static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz,
        jlong proxyPtr, jlongArray frameInfo, jint frameInfoSize) {
    LOG_ALWAYS_FATAL_IF(frameInfoSize != UI_THREAD_FRAME_INFO_SIZE,
            "Mismatched size expectations, given %d expected %d",
            frameInfoSize, UI_THREAD_FRAME_INFO_SIZE);
    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
    env->GetLongArrayRegion(frameInfo, 0, frameInfoSize, proxy->frameInfo());
    return proxy->syncAndDrawFrame();
}

int RenderProxy::syncAndDrawFrame() {
    return mDrawFrameTask.drawFrame();
}


nSyncAndDrawFrame函式呼叫了RenderProxy的syncAndDrawFrame,syncAndDrawFrame呼叫了DrawFrameTask.drawFrame()方法

//檔案-->/frameworks/base/libs/hwui/renderthread/DrawFrameTask.cpp
int DrawFrameTask::drawFrame() {
    LOG_ALWAYS_FATAL_IF(!mContext, "Cannot drawFrame with no CanvasContext!");

    mSyncResult = SyncResult::OK;
    mSyncQueued = systemTime(CLOCK_MONOTONIC);
    postAndWait();

    return mSyncResult;
}

void DrawFrameTask::postAndWait() {
    AutoMutex _lock(mLock);
    mRenderThread->queue(this);
    mSignal.wait(mLock);
}

void DrawFrameTask::run() {
    ATRACE_NAME("DrawFrame");

    bool canUnblockUiThread;
    bool canDrawThisFrame;
    {
        TreeInfo info(TreeInfo::MODE_FULL, *mContext);
        canUnblockUiThread = syncFrameState(info);
        canDrawThisFrame = info.out.canDrawThisFrame;
    }

    // Grab a copy of everything we need
    CanvasContext* context = mContext;

    // From this point on anything in "this" is *UNSAFE TO ACCESS*
    if (canUnblockUiThread) {
        unblockUiThread();
    }

    if (CC_LIKELY(canDrawThisFrame)) {
        context->draw();
    } else {
        // wait on fences so tasks don't overlap next frame
        context->waitOnFences();
    }

    if (!canUnblockUiThread) {
        unblockUiThread();
    }
}

DrawFrameTask做了兩件事情

  1. 呼叫syncFrameState函式同步frame資訊
  2. 呼叫CanvasContext.draw()函式進行繪制
同步Frame資訊

我們先看看第一件事情,同步Frame資訊,它主要的作業是將主執行緒的RenderNode同步到RenderNode來,在前面講mAttachInfo.mThreadedRenderer.draw函式中,第一步會將DisplayList構建完畢,然后系結到RenderNode中,這個RenderNode是在主執行緒創建的,而我們的DrawFrameTask,是在native層的RenderThread中執行的,所以需要講資料同步過來,

//檔案-->/frameworks/base/libs/hwui/renderthread/DrawFrameTask.cpp
bool DrawFrameTask::syncFrameState(TreeInfo& info) {
    ATRACE_CALL();
    int64_t vsync = mFrameInfo[static_cast<int>(FrameInfoIndex::Vsync)];
    mRenderThread->timeLord().vsyncReceived(vsync);
    bool canDraw = mContext->makeCurrent();
    mContext->unpinImages();

    for (size_t i = 0; i < mLayers.size(); i++) {
        mLayers[i]->apply();
    }
    mLayers.clear();
    mContext->prepareTree(info, mFrameInfo, mSyncQueued, mTargetNode);
    
	//……
   
    // If prepareTextures is false, we ran out of texture cache space
    return info.prepareTextures;
}

這里呼叫了mContext->prepareTree函式,mContext在下面會詳細講,我們這里先看看這個方法的實作,

//檔案-->/frameworks/base/libs/hwui/renderthread/CanvasContext.cpp
void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo,
        int64_t syncQueued, RenderNode* target) {
   //……
    for (const sp<RenderNode>& node : mRenderNodes) {
        // Only the primary target node will be drawn full - all other nodes would get drawn in
        // real time mode. In case of a window, the primary node is the window content and the other
        // node(s) are non client / filler nodes.
        info.mode = (node.get() == target ? TreeInfo::MODE_FULL : TreeInfo::MODE_RT_ONLY);
        node->prepareTree(info);
        GL_CHECKPOINT(MODERATE);
    }
    //……
}

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();
}

同步Frame的操作完成了,我們接著看最后繪制的流程,

進行繪制

圖形的硬體渲染,是通過呼叫CanvasContext的draw方法來進行繪制的,CanvasContext是什么呢?

它是渲染的背景關系,CanvasContext可以選擇不同的渲染模式進行渲染,這是策略模式的設計,我們看一下CanvasContext的create方法,可以看到,方法中會根據渲染型別,創建不同的渲染管道,總共有三種渲染管道——OpenGL,SKiaGL和SkiaVulkan,

CanvasContext* CanvasContext::create(RenderThread& thread,
        bool translucent, RenderNode* rootRenderNode, IContextFactory* contextFactory) {

    auto renderType = Properties::getRenderPipelineType();

    switch (renderType) {
        case RenderPipelineType::OpenGL:
            return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
                    std::make_unique<OpenGLPipeline>(thread));
        case RenderPipelineType::SkiaGL:
            return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
                    std::make_unique<skiapipeline::SkiaOpenGLPipeline>(thread));
        case RenderPipelineType::SkiaVulkan:
            return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
                                std::make_unique<skiapipeline::SkiaVulkanPipeline>(thread));
        default:
            LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t) renderType);
            break;
    }
    return nullptr;
}

我們這里這里只看通過OpenGL進行渲染的OpenGLPipeline

OpenGLPipeline::OpenGLPipeline(RenderThread& thread)
        :  mEglManager(thread.eglManager())
        , mRenderThread(thread) {
}

在OpenGLPipeline的建構式里面,創建了EglManager,EglManager將我們對EGL的操作全部封裝好了,我們看看EglManager的初始化方法

//檔案-->/frameworks/base/libs/hwui/renderthread/EglManager.cpp
void EglManager::initialize() {
    if (hasEglContext()) return;

    ATRACE_NAME("Creating EGLContext");

    //獲取 EGL Display 物件
    mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    LOG_ALWAYS_FATAL_IF(mEglDisplay == EGL_NO_DISPLAY,
            "Failed to get EGL_DEFAULT_DISPLAY! err=%s", eglErrorString());

    EGLint major, minor;
    //初始化與 EGLDisplay 之間的連接
    LOG_ALWAYS_FATAL_IF(eglInitialize(mEglDisplay, &major, &minor) == EGL_FALSE,
            "Failed to initialize display %p! err=%s", mEglDisplay, eglErrorString());
	//……

    //EGL配置設定
    loadConfig();
    //創建EGL背景關系
    createContext();
    //創建離屏渲染Buffer
    createPBufferSurface();
    //系結背景關系
    makeCurrent(mPBufferSurface);
    DeviceInfo::initialize();
    mRenderThread.renderState().onGLContextCreated();
}

在這里我們看到了熟悉的身影,EglManager中的初始化流程和前面所有EGL初始化的流程都是一樣的,但在初始化的流程中,我們沒看到WindowSurface的設定,只看到了PBufferSurface的創建,它是一個離屏渲染的Buffer,這里簡單介紹一下WindowSurface和PbufferSurface

  • WindowSurface:是和視窗相關的,也就是在螢屏上的一塊顯示區的封裝,渲染后即顯示在界面上,
  • PbufferSurface:在顯存中開辟一個空間,將渲染后的資料(幀)存放在這里,

可以看到沒有WindowSurface,OpenGL ES渲染的圖形是沒法顯示在界面上的,其實EglManager已經封裝了初始化WindowSurface的方法,

//檔案-->/frameworks/base/libs/hwui/renderthread/EglManager.cpp
EGLSurface EglManager::createSurface(EGLNativeWindowType window) {
    initialize();

    EGLint attribs[] = {
#ifdef ANDROID_ENABLE_LINEAR_BLENDING
            EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR,
            EGL_COLORSPACE, EGL_COLORSPACE_sRGB,
#endif
            EGL_NONE
    };

    EGLSurface surface = eglCreateWindowSurface(mEglDisplay, mEglConfig, window, attribs);
    LOG_ALWAYS_FATAL_IF(surface == EGL_NO_SURFACE,
            "Failed to create EGLSurface for window %p, eglErr = %s",
            (void*) window, eglErrorString());

    if (mSwapBehavior != SwapBehavior::Preserved) {
        LOG_ALWAYS_FATAL_IF(eglSurfaceAttrib(mEglDisplay, surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_DESTROYED) == EGL_FALSE,
                            "Failed to set swap behavior to destroyed for window %p, eglErr = %s",
                            (void*) window, eglErrorString());
    }

    return surface;
}

這個surface又是什么時候設定的呢?在activity的界面顯示流程中,當我們setView后,ViewRootImpl會執行performTraveserl函式,然后執行Measure測量,Layout布局,和Draw繪制的流程,setView函式在前面講過,會開啟硬體加速,創建ThreadedRenderer,draw函式也講過,measure,layout的流程就不在這兒說了,它和OpgenGL沒關系,其實performTraveserl函式里,同時也設定了EGL的Surface,可見這個函式是多么重要的一個函式,我們看一下,

private void performTraversals() {
    //……
    if (mAttachInfo.mThreadedRenderer != null) {
        try {
            //呼叫ThreadedRenderer initialize函式
            hwInitialized = mAttachInfo.mThreadedRenderer.initialize(
                    mSurface);
            if (hwInitialized && (host.mPrivateFlags
                    & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) == 0) {
                // Don't pre-allocate if transparent regions
                // are requested as they may not be needed
                mSurface.allocateBuffers();
            }
        } catch (OutOfResourcesException e) {
            handleOutOfResourcesException(e);
            return;
        }
    }
    //……
}

boolean initialize(Surface surface) throws OutOfResourcesException {
    boolean status = !mInitialized;
    mInitialized = true;
    updateEnabledState(surface);
    nInitialize(mNativeProxy, surface);
    return status;
}


ThreadedRenderer的initialize函式呼叫了native層的initialize方法,

static void android_view_ThreadedRenderer_initialize(JNIEnv* env, jobject clazz,
        jlong proxyPtr, jobject jsurface) {
    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
    sp<Surface> surface = android_view_Surface_getSurface(env, jsurface);
    proxy->initialize(surface);
}

void RenderProxy::initialize(const sp<Surface>& surface) {
    SETUP_TASK(initialize);
    args->context = mContext;
    args->surface = surface.get();
    post(task);
}

void CanvasContext::initialize(Surface* surface) {
    setSurface(surface);
}

void CanvasContext::setSurface(Surface* surface) {
    ATRACE_CALL();

    mNativeSurface = surface;

    bool hasSurface = mRenderPipeline->setSurface(surface, mSwapBehavior);

    mFrameNumber = -1;

    if (hasSurface) {
         mHaveNewSurface = true;
         mSwapHistory.clear();
    } else {
         mRenderThread.removeFrameCallback(this);
    }
}

從這里可以看到,EGL的Surface在很早之前就已經設定好了,

此時我們的流程中,EGL的初始化作業都已經完成了,現在可以開始繪制了,我們回到DrawFrameTask::run的draw流程上來

void CanvasContext::draw() {
    SkRect dirty;
    mDamageAccumulator.finish(&dirty);

    mCurrentFrameInfo->markIssueDrawCommandsStart();

    Frame frame = mRenderPipeline->getFrame();

    SkRect windowDirty = computeDirtyRect(frame, &dirty);
	//呼叫OpenGL的draw函式
    bool drew = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue,
            mContentDrawBounds, mOpaque, mLightInfo, mRenderNodes, &(profiler()));

    waitOnFences();

    bool requireSwap = false;
    //交換緩沖區
    bool didSwap = mRenderPipeline->swapBuffers(frame, drew, windowDirty, mCurrentFrameInfo,
            &requireSwap);

    mIsDirty = false;

	//……

}

這里呼叫mRenderPipeline的draw方法,其實就是呼叫了OpenGL的draw方法,然后呼叫mRenderPipeline->swapBuffers進行快取區交換

//檔案-->/frameworks/base/libs/hwui/renderthread/OpenGLPipeline.cpp
bool OpenGLPipeline::draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
        const FrameBuilder::LightGeometry& lightGeometry,
        LayerUpdateQueue* layerUpdateQueue,
        const Rect& contentDrawBounds, bool opaque,
        const BakedOpRenderer::LightInfo& lightInfo,
        const std::vector< sp<RenderNode> >& renderNodes,
        FrameInfoVisualizer* profiler) {

    //……
	//BakedOpRenderer用于替代之前的OpenGLRenderer
    BakedOpRenderer renderer(caches, mRenderThread.renderState(),
            opaque, lightInfo);
    frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
    //呼叫GPU進行渲染
    drew = renderer.didDraw();

    //……

    return drew;
}

bool OpenGLPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
        FrameInfo* currentFrameInfo, bool* requireSwap) {

    GL_CHECKPOINT(LOW);

    // Even if we decided to cancel the frame, from the perspective of jank
    // metrics the frame was swapped at this point
    currentFrameInfo->markSwapBuffers();

    *requireSwap = drew || mEglManager.damageRequiresSwap();

    if (*requireSwap && (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty)))) {
        return false;
    }

    return *requireSwap;
}

至此,通過OpenGL ES進行硬體渲染的主要流程結束了,看完了兩個例子,是不是對OpenGL ES作為影像生產者是如何生產影像已經了解了呢?我們接著看下一個影像生產者Skia,

Skia

Skia是谷歌開源的一款跨平臺的2D圖形引擎,目前谷歌的Chrome瀏覽器、Android、Flutter、以及火狐瀏覽器、火狐作業系統和其它許多產品都使用它作為圖形引擎,它作為Android系統第三方軟體,放在external/skia/ 目錄下,雖然Android從4.0開始默認開啟了硬體加速,但不代表Skia的作用就不大了,其實Skia在Android中的地位是越來越重要了,從Android 8開始,我們可以選擇使用Skia進行硬體加速,Android 9開始就默認使用Skia來進行硬體加速,Skia的硬體加速主要是通過 copybit 模塊呼叫OpenGL或者SKia來實作分,
在這里插入圖片描述

由于Skia的硬體加速也是通過Copybit模塊呼叫的OpenGL或者Vulkan介面,所以我們這兒只說說Skia通過cpu繪制的,也就是軟繪的方式,還是老規則,先看看Skia要如何使用

如何使用Skia?

OpenGL ES的使用要配合EGL,需要初始化Display,surface,context等,用法還是比較繁瑣的,Skia在使用上就方便很多了,掌握Skia繪制三要素:畫板SKCanvas 、畫紙SiBitmap、畫筆Skpaint,我們就能很輕松的用Skia來繪制圖形,

下面詳細的解釋Skia的繪圖三要素

  1. SKBitmap用來存盤圖形資料,它封裝了與位圖相關的一系列操作
SkBitmap bitmap = new SkBitmap();
//設定位圖格式及寬高
bitmap->setConfig(SkBitmap::kRGB_565_Config,800,480);
//分配位圖所占空間
bitmap->allocPixels();
  1. SKCanvas 封裝了所有畫圖操作的函式,通過呼叫這些函式,我們就能實作繪制操作,
//使用前傳入bitmap
SkCanvas canvas(bitmap);
//移位,縮放,旋轉,變形操作
translate(SkiaScalar dx, SkiaScalar dy);
scale(SkScalar sx, SkScalar sy);
rotate(SkScalar degrees);
skew(SkScalar sx, SkScalar sy);
//繪制操作
drawARGB(u8 a, u8 r, u8 g, u8 b....)  //給定透明度以及紅,綠,蘭3色,填充整個可繪制區域,
drawColor(SkColor color...) //給定顏色color, 填充整個繪制區域,
drawPaint(SkPaint& paint) //用指定的畫筆填充整個區域,
drawPoint(...)//根據各種不同引數繪制不同的點,
drawLine(x0, y0, x1, y1, paint) //畫線,起點(x0, y0), 終點(x1, y1), 使用paint作為畫筆,
drawRect(rect, paint) //畫矩形,矩形大小由rect指定,畫筆由paint指定,
drawRectCoords(left, top, right, bottom, paint),//給定4個邊界畫矩陣,
drawOval(SkRect& oval, SkPaint& paint) //畫橢圓,橢圓大小由oval矩形指定,
//……其他操作
  1. Skpaint用來設定繪制內容的風格,樣式,顏色等資訊
setAntiAlias: 設定畫筆的鋸齒效果, 
setColor: 設定畫筆顏色 
setARGB:  設定畫筆的a,r,p,g值, 
setAlpha:  設定Alpha值 
setTextSize: 設定字體尺寸, 
setStyle:  設定畫筆風格,空心或者實心, 
setStrokeWidth: 設定空心的邊框寬度, 
getColor:  得到畫筆的顏色 
getAlpha:  得到畫筆的Alpha值, 

我們看一個完整的使用Demo

void draw() {
    SkBitmap bitmap = new SkBitmap();
	//設定位圖格式及寬高
	bitmap->setConfig(SkBitmap::kRGB_565_Config,800,480);
	//分配位圖所占空間
	bitmap->allocPixels();
    //使用前傳入bitmap
	SkCanvas canvas(bitmap);
    //定義畫筆
    SkPaint paint1, paint2, paint3;

    paint1.setAntiAlias(true);
    paint1.setColor(SkColorSetRGB(255, 0, 0));
    paint1.setStyle(SkPaint::kFill_Style);

    paint2.setAntiAlias(true);
    paint2.setColor(SkColorSetRGB(0, 136, 0));
    paint2.setStyle(SkPaint::kStroke_Style);
    paint2.setStrokeWidth(SkIntToScalar(3));

    paint3.setAntiAlias(true);
    paint3.setColor(SkColorSetRGB(136, 136, 136));

    sk_sp<SkTextBlob> blob1 =
            SkTextBlob::MakeFromString("Skia!", SkFont(nullptr, 64.0f, 1.0f, 0.0f));
    sk_sp<SkTextBlob> blob2 =
            SkTextBlob::MakeFromString("Skia!", SkFont(nullptr, 64.0f, 1.5f, 0.0f));

    canvas->clear(SK_ColorWHITE);
    canvas->drawTextBlob(blob1.get(), 20.0f, 64.0f, paint1);
    canvas->drawTextBlob(blob1.get(), 20.0f, 144.0f, paint2);
    canvas->drawTextBlob(blob2.get(), 20.0f, 224.0f, paint3);
}

這個Demo的效果如下

在這里插入圖片描述

了解了Skia如何使用,我們接著看兩個場景:Skia進行軟體繪制,Flutter界面繪制

Skia進行軟體繪制

在上面我講了通過使用OpenGL渲染的硬體繪制方式,這里會接著講使用Skia渲染的軟體繪制方式,雖然Android默認開啟了硬體加速,但是由于硬體加速會有耗電和記憶體的問題,一些系統應用和常駐應用依然是使用的軟體繪制的方式,軟繪入口還是在draw方法中,

//檔案-->/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;
    }

    //……

    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
        if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
            if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
                
                //……

                //硬體渲染
                mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);

            } else {
                
                //……

                //軟體渲染
                if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                    return;
                }
            }
        }

        //……
    }

    //……
}

我們來看看drawSoftware函式的實作

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
        boolean scalingRequired, Rect dirty) {

    // Draw with software renderer.
    final Canvas canvas;

    //……

    canvas = mSurface.lockCanvas(dirty);
    
    //……
        
    mView.draw(canvas);
    
    //……
    
    surface.unlockCanvasAndPost(canvas);
    
    //……
    
    return true;
}

drawSoftware函式的流程主要為三步

  1. 通過mSurface.lockCanvas獲取Canvas
  2. 通過draw方法,將根View及其子View遍歷繪制到Canvas上
  3. 通過surface.unlockCanvasAndPost將繪制內容提交給surfaceFlinger進行合成

Lock Surface

我們先來看第一步,這個Canvas對應著Native層的SKCanvas,

//檔案-->/frameworks/base/core/java/android/view/Surface.java
public Canvas lockCanvas(Rect inOutDirty)
    throws Surface.OutOfResourcesException, IllegalArgumentException {
    synchronized (mLock) {
        checkNotReleasedLocked();
        if (mLockedObject != 0) {           
            throw new IllegalArgumentException("Surface was already locked");
        }
        mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
        return mCanvas;
    }
}

lockCanvas函式中通過JNI函式nativeLockCanvas,創建Nativce層的Canvas,nativeLockCanvas的入參mNativeObject對應著Native層的Surface,關于Surface和Buffer的知識,在下一篇圖形緩沖區中會詳細簡介,這里不做太多介紹,我們直接著看nativeLockCanvas的實作,

static jlong nativeLockCanvas(JNIEnv* env, jclass clazz,
        jlong nativeObject, jobject canvasObj, jobject dirtyRectObj) {
    sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));

    if (!isSurfaceValid(surface)) {
        doThrowIAE(env);
        return 0;
    }

    Rect dirtyRect(Rect::EMPTY_RECT);
    Rect* dirtyRectPtr = NULL;

    if (dirtyRectObj) {
        dirtyRect.left   = env->GetIntField(dirtyRectObj, gRectClassInfo.left);
        dirtyRect.top    = env->GetIntField(dirtyRectObj, gRectClassInfo.top);
        dirtyRect.right  = env->GetIntField(dirtyRectObj, gRectClassInfo.right);
        dirtyRect.bottom = env->GetIntField(dirtyRectObj, gRectClassInfo.bottom);
        dirtyRectPtr = &dirtyRect;
    }

    ANativeWindow_Buffer outBuffer;
    //1,獲取用來存盤圖形繪制的buffer
    status_t err = surface->lock(&outBuffer, dirtyRectPtr);
    if (err < 0) {
        const char* const exception = (err == NO_MEMORY) ?
                OutOfResourcesException :
                "java/lang/IllegalArgumentException";
        jniThrowException(env, exception, NULL);
        return 0;
    }

    SkImageInfo info = SkImageInfo::Make(outBuffer.width, outBuffer.height,
                                         convertPixelFormat(outBuffer.format),
                                         outBuffer.format == PIXEL_FORMAT_RGBX_8888
                                                 ? kOpaque_SkAlphaType : kPremul_SkAlphaType,
                                         GraphicsJNI::defaultColorSpace());

    SkBitmap bitmap;
    ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format);
    bitmap.setInfo(info, bpr);
   
    if (outBuffer.width > 0 && outBuffer.height > 0) {
         //將上一個buffer里的圖形資料復制到當前bitmap中
        bitmap.setPixels(outBuffer.bits);
    } else {
        // be safe with an empty bitmap.
        bitmap.setPixels(NULL);
    }

    //2,創建一個SKCanvas
    Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvasObj);
    //3,給SKCanvas設定Bitmap
    nativeCanvas->setBitmap(bitmap);
    //如果指定了臟區,則設定臟區的區域
    if (dirtyRectPtr) {
        nativeCanvas->clipRect(dirtyRect.left, dirtyRect.top,
                dirtyRect.right, dirtyRect.bottom, SkClipOp::kIntersect);
    }

    if (dirtyRectObj) {
        env->SetIntField(dirtyRectObj, gRectClassInfo.left,   dirtyRect.left);
        env->SetIntField(dirtyRectObj, gRectClassInfo.top,    dirtyRect.top);
        env->SetIntField(dirtyRectObj, gRectClassInfo.right,  dirtyRect.right);
        env->SetIntField(dirtyRectObj, gRectClassInfo.bottom, dirtyRect.bottom);
    }

    sp<Surface> lockedSurface(surface);
    lockedSurface->incStrong(&sRefBaseOwner);
    return (jlong) lockedSurface.get();
}

nativeLockCanvas主要做了這幾件事情

  1. 通過surface->lock函式獲取繪制用的Buffer
  2. 根據Buffer資訊創建SKBitmap
  3. 根據SKBitmap,創建并初始化SKCanvas

通過nativeLockCanvas,我們就創建好了SKCanvas了,并且設定了可以繪制圖形的bitmap,此時我們就可以通過SKCanvas往bitmap里面繪制圖形,mView.draw()函式,就做了這件事情,

繪制

我們接著看看View中的draw()函式

//檔案-->/frameworks/base/core/java/android/view/View.java
public void draw(Canvas canvas) {
    final int privateFlags = mPrivateFlags;
    final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
            (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
    mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

    int saveCount;
    //1,繪制背景
    if (!dirtyOpaque) {
        drawBackground(canvas);
    }

    final int viewFlags = mViewFlags;
    boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
    boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
    if (!verticalEdges && !horizontalEdges) {
        // 2,繪制當前view的圖形
        if (!dirtyOpaque) onDraw(canvas);

        // 3,繪制子view的圖形
        dispatchDraw(canvas);

        drawAutofilledHighlight(canvas);

        // Overlay is part of the content and draws beneath Foreground
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }

        //4,繪制decorations,如滾動條,前景等 Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);

        // 5,繪制焦點的高亮
        drawDefaultFocusHighlight(canvas);

        if (debugDraw()) {
            debugDrawFocus(canvas);
        }

        // we're done...
        return;
    }

    //……
}

draw函式中做了這幾件事情

  1. 繪制背景
  2. 繪制當前view
  3. 遍歷繪制子view
  4. 繪制前景

我們可以看看Canvas里的繪制方法,這些繪制方法都是JNI方法,并且一一對應著SKCanvas中的繪制方法

//檔案-->/frameworks/base/graphics/java/android/graphics/Canvas.java

//……
private static native void nDrawBitmap(long nativeCanvas, int[] colors, int offset, int stride,
            float x, float y, int width, int height, boolean hasAlpha, long nativePaintOrZero);

private static native void nDrawColor(long nativeCanvas, int color, int mode);

private static native void nDrawPaint(long nativeCanvas, long nativePaint);

private static native void nDrawPoint(long canvasHandle, float x, float y, long paintHandle);

private static native void nDrawPoints(long canvasHandle, float[] pts, int offset, int count,
                                       long paintHandle);

private static native void nDrawLine(long nativeCanvas, float startX, float startY, float stopX,
                                     float stopY, long nativePaint);

private static native void nDrawLines(long canvasHandle, float[] pts, int offset, int count,
                                      long paintHandle);

private static native void nDrawRect(long nativeCanvas, float left, float top, float right,
                                     float bottom, long nativePaint);

private static native void nDrawOval(long nativeCanvas, float left, float top, float right,
                                     float bottom, long nativePaint);

private static native void nDrawCircle(long nativeCanvas, float cx, float cy, float radius,
                                       long nativePaint);

private static native void nDrawArc(long nativeCanvas, float left, float top, float right,
                                    float bottom, float startAngle, float sweep, boolean useCenter, long nativePaint);

private static native void nDrawRoundRect(long nativeCanvas, float left, float top, float right,
                                          float bottom, float rx, float ry, long nativePaint);
//……

Post Surface

軟體繪制的最后一步,通過surface.unlockCanvasAndPost將繪制內容提交給surfaceFlinger繪制,將繪制出來的圖形提交給SurfaceFlinger,然后SurfaceFlinger作為消費者處理圖形后,我們的界面就顯示出來了,

public void unlockCanvasAndPost(Canvas canvas) {
    synchronized (mLock) {
        checkNotReleasedLocked();

        if (mHwuiContext != null) {
            mHwuiContext.unlockAndPost(canvas);
        } else {
            unlockSwCanvasAndPost(canvas);
        }
    }
}

private void unlockSwCanvasAndPost(Canvas canvas) {
    if (canvas != mCanvas) {
        throw new IllegalArgumentException("canvas object must be the same instance that "
                                           + "was previously returned by lockCanvas");
    }
    if (mNativeObject != mLockedObject) {
        Log.w(TAG, "WARNING: Surface's mNativeObject (0x" +
              Long.toHexString(mNativeObject) + ") != mLockedObject (0x" +
              Long.toHexString(mLockedObject) +")");
    }
    if (mLockedObject == 0) {
        throw new IllegalStateException("Surface was not locked");
    }
    try {
        nativeUnlockCanvasAndPost(mLockedObject, canvas);
    } finally {
        nativeRelease(mLockedObject);
        mLockedObject = 0;
    }
}

這里呼叫了Native函式nativeUnlockCanvasAndPost,我們接著往下看,

static void nativeUnlockCanvasAndPost(JNIEnv* env, jclass clazz,
        jlong nativeObject, jobject canvasObj) {
    sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));
    if (!isSurfaceValid(surface)) {
        return;
    }

    // detach the canvas from the surface
    Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvasObj);
    nativeCanvas->setBitmap(SkBitmap());

    // unlock surface
    status_t err = surface->unlockAndPost();
    if (err < 0) {
        doThrowIAE(env);
    }
}

在這里,**surface->unlockAndPost()**函式就會將Skia繪制出來的影像傳遞給SurfaceFlinger進行合成,通過skia進行軟體繪制的流程已經講完了,至于如何通過Surface獲取緩沖區,在緩沖區繪制完資料后,**surface->unlockAndPost()**又如何通知SurfaceFlinger,這一點在下一篇文章的圖形緩沖區中會詳細的講解,

可以看到,Skia軟體繪制的流程比硬體繪制要簡單很多,我們接著看看Skia進行Flutter繪制的案例,

Skia進行Flutter的界面繪制

在講解Flutter如何通過Skia生產影像之前,先簡單介紹一下Flutter,Flutter的架構分為Framework層,Engine層和Embedder三層,

  • Framework層使用dart語言實作,包括UI,文本,圖片,按鈕等Widgets,渲染,影片,手勢等,

  • Engine使用C++實作,主要包括渲染引擎Skia, Dart虛擬機和文字排版Tex等模塊,

  • Embedder是一個嵌入層,通過該層把Flutter嵌入到各個平臺上去,Embedder的主要作業包括渲染Surface設定, 執行緒設定,以及插件等

C:\Users\Administrator\OneDrive\技術筆記\素材\cbef8449301eaff5864272c3c609bee2.png

了解了Flutter的架構,我們在接著了解Flutter顯示一個界面的流程,我們知道在Android中,顯示一個界面需要將XML界面布局決議成ViewGroup,然后再經過測量Measure,布局Layout和繪制Draw的流程,Flutter和Android的顯示不太一樣,它會將通過Dart語言撰寫的Widget界面布局轉換成ElementTree和Render ObjectTree,ElementTree相當于是ViewGroup,Render ObjectTree相當于是經過Measure和Layout流程之后的ViewGroup,這種模式在很多場景上都有使用,比如Webview,在渲染界面時,也會創建一顆Dom樹,render樹和RenderObject,這樣的好處是可以通過Diff比較改變過的組件,然后渲染時,只對改變過的組件做渲染,同時對跨平臺友好,可以通過這種樹的形式來抽象出不同平臺的公共部分,

在這里插入圖片描述

講完了上面兩個背景,我們直接來看Flutter是如何使用Skia來繪制界面的,

下面是一個Flutter頁面的Demo

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

這個頁面是一個WidgetTree,相當于我們Activity的xml,widget樹會轉換成ElementTree和RenderObjectTree,我們看看入口函式runApp時如何進行樹的轉換的,

//檔案-->/packages/flutter/lib/src/widgets
void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();
}

void scheduleAttachRootWidget(Widget rootWidget) {
    Timer.run(() {
        attachRootWidget(rootWidget);
    });
}

void attachRootWidget(Widget rootWidget) {
    _readyToProduceFrames = true;
    _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
        container: renderView,
        debugShortDescription: '[root]',
        child: rootWidget,
    ).attachToRenderTree(buildOwner, renderViewElement as RenderObjectToWidgetElement<RenderBox>);
}

接著看attachToRenderTree函式

RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [RenderObjectToWidgetElement<T> element]) {
    if (element == null) {
      owner.lockState(() {
        element = createElement();  //創建rootElement
        element.assignOwner(owner); //系結BuildOwner
      });
      owner.buildScope(element, () { //子widget的初始化從這里開始
        element.mount(null, null);  // 初始化子Widget前,先執行rootElement的mount方法
      });
    } else {
      ...
    }
    return element;
  }

void mount(Element parent, dynamic newSlot) {
  super.mount(parent, newSlot);
  _renderObject = widget.createRenderObject(this);
  attachRenderObject(newSlot);
  _dirty = false;
}

從代碼中可以看到,Widget都被轉換成了Element,Element接著呼叫了mount方法,在mount方法中,可以看到Widget又被轉換成了RenderObject,此時Widget Tree的ElementTree和RenderObject便都生成完了,

前面提到了RenderObject類似于經過了Measure和Layout流程的ViewGroup,RenderObject的Measure和Layout就不在這兒說了,那么還剩一個流程Draw流程,同樣是在RenderObject中進行的,它的入口在RenderObject的paint函式中,

// 繪制入口,從 view 根節點開始,逐個繪制所有子節點
@override
  void paint(PaintingContext context, Offset offset) {
    if (child != null)
      context.paintChild(child, offset);
  }

可以看到,RenderObject通過PaintingContext來進行了圖形的繪制,我們接著來了解一下PaintingContext是什么,

//檔案-->/packages/flutter/lib/src/rendering/object.dart

import 'dart:ui' as ui show PictureRecorder;

class PaintingContext extends ClipContext {
  @protected
  PaintingContext(this._containerLayer, this.estimatedBounds)
 
  final ContainerLayer _containerLayer;
  final Rect estimatedBounds;
  
  PictureLayer _currentLayer;
  ui.PictureRecorder _recorder;
  Canvas _canvas;
 
  @override
  Canvas get canvas {
    if (_canvas == null)
      _startRecording();
    return _canvas;
  }
 
  void _startRecording() {
    _currentLayer = PictureLayer(estimatedBounds);
    _recorder = ui.PictureRecorder();
    _canvas = Canvas(_recorder);
    _containerLayer.append(_currentLayer);
  }
  
   void stopRecordingIfNeeded() {
    if (!_isRecording)
      return;
    _currentLayer.picture = _recorder.endRecording();
    _currentLayer = null;
    _recorder = null;
    _canvas = null;
}

可以看到,PaintingContext是繪制的背景關系,前面講OpenGL進行硬體加速時提到的CanvasContext,它也是繪制的背景關系,里面封裝了Skia,Opengl或者Vulkan的渲染管線,這里的PaintingContext則封裝了Skia,

我們可以通過CanvasContext的get canvas函式獲取Canvas,它呼叫了_startRecording函式中,函式中創建了PictureRecorder和Canvas,這兩個類都是位于dart:ui庫中,dart:ui位于engine層,在前面架構中提到,Flutter分為Framewrok,Engine和embened三層,Engine中包含了Skia,dart虛擬機和Text,dart:ui就是位于Engine層的,

我們接著去Engine層的代碼看看Canvas的實作,

//檔案-->engine-master\lib\ui\canvas.dart  
Canvas(PictureRecorder recorder, [ Rect? cullRect ]) : assert(recorder != null) { // ignore: unnecessary_null_comparison
    if (recorder.isRecording)
      throw ArgumentError('"recorder" must not already be associated with another Canvas.');
    _recorder = recorder;
    _recorder!._canvas = this;
    cullRect ??= Rect.largest;
    _constructor(recorder, cullRect.left, cullRect.top, cullRect.right, cullRect.bottom);
  }
void _constructor(PictureRecorder recorder,
                  double left,
                  double top,
                  double right,
                  double bottom) native 'Canvas_constructor';

這里Canvas呼叫了Canvas_constructor這一個native方法,我們接著看這個native方法的實作,

//檔案-->engine-master\lib\ui\painting\engine.cc
static void Canvas_constructor(Dart_NativeArguments args) {
  UIDartState::ThrowIfUIOperationsProhibited();
  DartCallConstructor(&Canvas::Create, args);
}
fml::RefPtr<Canvas> Canvas::Create(PictureRecorder* recorder,
                                   double left,
                                   double top,
                                   double right,
                                   double bottom) {
  if (!recorder) {
    Dart_ThrowException(
        ToDart("Canvas constructor called with non-genuine PictureRecorder."));
    return nullptr;
  }
  fml::RefPtr<Canvas> canvas = fml::MakeRefCounted<Canvas>(
      recorder->BeginRecording(SkRect::MakeLTRB(left, top, right, bottom)));
  recorder->set_canvas(canvas);
  return canvas;
}

Canvas::Canvas(SkCanvas* canvas) : canvas_(canvas) {}

可以看到,這里通過PictureRecorder->BeginRecording創建了SKCanvas,這其實是SKCanvas的另外一種使用方式,這里我簡單的介紹一個使用demo,

Picture createSolidRectanglePicture(
  Color color, double width, double height)
{

  PictureRecorder recorder = PictureRecorder();
  Canvas canvas = Canvas(recorder);

  Paint paint = Paint();
  paint.color = color;

  canvas.drawRect(Rect.fromLTWH(0, 0, width, height), paint);
  return recorder.endRecording();
}

這個demo的效果如下圖,它創建Skia的方式就和Flutter創建Skia的方式是一樣的,
在這里插入圖片描述

此時,我們的SKCanvas創建好了,并且直接通過PaintingContext的get canvas函式就能獲取到,那么獲取到SKCanvas后直接呼叫Canvas的繪制api,就可以將影像繪制出來了,

Flutter界面顯示的全流程是比較復雜的,Flutter是完全是自建的一套影像顯示流程,無法通過Android的SurfaceFlinger進行影像合成,也無法使用Android的Gralloc模塊分配影像緩沖區,所以它需要有自己的影像生產者,有自己的圖形消費者,也有自己的圖形緩沖區,這里面就有非常多的流程,比如如何接收VSync,如何處理及合成Layer,如何創建影像緩沖區,這里只是對Flutter的影像生產者的部分做了一個初步的介紹,關于Flutter更深入一步的細節,就不在這里繼續講解了,后面我會專門寫一系列文章來詳細講解Flutter,

Vulkan

與OpenGL相比,Vulkan可以更詳細的向顯卡描述你的應用程式打算做什么,從而可以獲得更好的性能和更小的驅動開銷,作為OpenGL的替代者,它設計之初就是為了跨平臺實作的,可以同時在Windows、Linux和Android開發,甚至在Mac OS系統上運行,Android在7.0開始,便增加了對Vulkan的支持,Vulkan一定是未來的趨勢,因為它比OpenGL的性能更好更強大,下面我們就了解一下,如何使用Vulkan來生產影像,

如何使用Vulkan?

Vulkan的使用和OpenGL類似,同樣是三步:初始化,繪制,提交buffer下面來看一下具體的流程

1,初始化Vulkan實體,物理設備和任務佇列以及Surface

  • 創建Instances實體
VkInstanceCreateInfo instance_create_info = { 
  VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, 
  nullptr, 
  0, 
  &application_info, 
  0, 
  nullptr, 
  static_cast<uint32_t>(desired_extensions.size()), 
  desired_extensions.size() > 0 ? &desired_extensions[0] : nullptr 
};

VkInstance inst;
VkResult result = vkCreateInstance( &instance_create_info, nullptr, &inst ); 
  • 初始化物理設備,也就是我們的顯卡設備,Vulkna的設計是支持多GPU的,這里選擇第一個設備就行了,
uint32_t extensions_count = 0; 
VkResult result = VK_SUCCESS; 

//獲取所有可用物理設備,并選擇第一個
result = vkEnumerateDeviceExtensionProperties( physical_device, nullptr, &extensions_count, &available_extensions[0]); 
if( (result != VK_SUCCESS) || 
    (extensions_count == 0) ) { 
  std::cout << "Could not get the number of device extensions." << std::endl; 
  return false; 
}
  • 獲取queue,Vulkan的所有操作,從繪圖到上傳紋理,都需要將命令提交到佇列中
uint32_t queue_families_count = 0; 

//獲取佇列簇,并選擇第一個
queue_families.resize( queue_families_count ); 
vkGetPhysicalDeviceQueueFamilyProperties( physical_device, &queue_families_count, &queue_families[0] ); 
if( queue_families_count == 0 ) { 
  std::cout << "Could not acquire properties of queue families." << std::endl; 
  return false; 
} 
  • 初始化邏輯設備,在選擇要使用的物理設備之后,我們需要設定一個邏輯設備用于互動,
VkResult result = vkCreateDevice( physical_device, &device_create_info, nullptr, &logical_device ); 
if( (result != VK_SUCCESS) || 
    (logical_device == VK_NULL_HANDLE) ) { 
  std::cout << "Could not create logical device." << std::endl; 
  return false; 
} 

return true;
  • 上述初始完畢后,接著初始化Surface,然后我們就可以使用Vulkan進行繪制了
#ifdef VK_USE_PLATFORM_WIN32_KHR 

//創建WIN32的surface,如果是Android,需要使用VkAndroidSurfaceCreateInfoKHR
VkWin32SurfaceCreateInfoKHR surface_create_info = { 
  VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, 
  nullptr, 
  0, 
  window_parameters.HInstance, 
  window_parameters.HWnd 
}; 

VkResult result = vkCreateWin32SurfaceKHR( instance, &surface_create_info, nullptr, &presentation_surface );

2,通過vkCmdDraw函式進行影像繪制

void vkCmdDraw(
    //在Vulkan中,像繪畫命令、記憶體轉換等操作并不是直接通過方法呼叫去完成的,而是需要把所有的操作放在Command Buffer里
    VkCommandBuffer commandBuffer,
    uint32_t vertexCount, //頂點數量
    uint32_t instanceCount, // 要畫的instance數量,沒有:置1
    uint32_t firstVertex,// vertex buffer中第一個位置 和 vertex Shader 里gl_vertexIndex 相關,
    uint32_t firstInstance);// 同firstVertex 類似,

3,提交buffer

if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) {
    throw std::runtime_error("failed to submit draw command buffer!");
}

我在這里比較淺顯的介紹了Vulkan的用法,但上面介紹的只是Vulkan的一點皮毛,Vulkan的使用比OpenGL要復雜的很多,機制也復雜很多,如果想進一步了解Vulkan還是得專門去深入研究,雖然只介紹了一點皮毛,但已經可以讓我們去了解Vulkan這一影像生產者,是如何在Android系統中生產影像的,下面就來看看吧,

Vulkan進行硬體加速

在前面講OpenGL 進行硬體加速時,提到了CanvasContext,它會根據渲染的型別選擇不同的渲染管線,Android是通過Vulkan或者還是通過OpenGL渲染,主要是CanvasContext里選擇的渲染管線的不同,

CanvasContext* CanvasContext::create(RenderThread& thread,
        bool translucent, RenderNode* rootRenderNode, IContextFactory* contextFactory) {

    auto renderType = Properties::getRenderPipelineType();

    switch (renderType) {
        case RenderPipelineType::OpenGL:
            return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
                    std::make_unique<OpenGLPipeline>(thread));
        case RenderPipelineType::SkiaGL:
            return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
                    std::make_unique<skiapipeline::SkiaOpenGLPipeline>(thread));
        case RenderPipelineType::SkiaVulkan:
            return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
                                std::make_unique<skiapipeline::SkiaVulkanPipeline>(thread));
        default:
            LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t) renderType);
            break;
    }
    return nullptr;
}

我們這里直接看SkiaVulkanPipeline,

//檔案->/frameworks/base/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
SkiaVulkanPipeline::SkiaVulkanPipeline(renderthread::RenderThread& thread)
        : SkiaPipeline(thread), mVkManager(thread.vulkanManager()) {}

SkiaVulkanPipeline的建構式中初始化了VulkanManager,VulkanManager是對Vulkan使用的封裝,和前面講到的OpenGLPipeline中的EglManager類似,我們看一下VulkanManager的初始化函式,

//檔案-->/frameworks/base/libs/hwui/renderthread/VulkanManager.cpp
void VulkanManager::initialize() {
    if (hasVkContext()) {
        return;
    }

    auto canPresent = [](VkInstance, VkPhysicalDevice, uint32_t) { return true; };
    mBackendContext.reset(GrVkBackendContext::Create(vkGetInstanceProcAddr, vkGetDeviceProcAddr,
                                                     &mPresentQueueIndex, canPresent));
	//……
}

初始化函式中我們主要關注GrVkBackendContext::Create方法,

// Create the base Vulkan objects needed by the GrVkGpu object
const GrVkBackendContext* GrVkBackendContext::Create(uint32_t* presentQueueIndexPtr,
                                                     CanPresentFn canPresent,
                                                     GrVkInterface::GetProc getProc) {
    //……

    const VkInstanceCreateInfo instance_create = {
        VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,    // sType
        nullptr,                                   // pNext
        0,                                         // flags
        &app_info,                                 // pApplicationInfo
        (uint32_t) instanceLayerNames.count(),     // enabledLayerNameCount
        instanceLayerNames.begin(),                // ppEnabledLayerNames
        (uint32_t) instanceExtensionNames.count(), // enabledExtensionNameCount
        instanceExtensionNames.begin(),            // ppEnabledExtensionNames
    };

    ACQUIRE_VK_PROC(CreateInstance, VK_NULL_HANDLE, VK_NULL_HANDLE);
    //1,創建Vulkan實體
    err = grVkCreateInstance(&instance_create, nullptr, &inst);
    if (err < 0) {
        SkDebugf("vkCreateInstance failed: %d\n", err);
        return nullptr;
    }

    

    uint32_t gpuCount;
    //2,查詢可用物理設備
    err = grVkEnumeratePhysicalDevices(inst, &gpuCount, nullptr);
    if (err) {
        //……
    }
    //……
    gpuCount = 1;
    //3,選擇物理設備
    
    err = grVkEnumeratePhysicalDevices(inst, &gpuCount, &physDev);
    if (err) {
        //……
    }

    //4,查詢佇列簇
    uint32_t queueCount;
    grVkGetPhysicalDeviceQueueFamilyProperties(physDev, &queueCount, nullptr);
    if (!queueCount) {
        //……
        return nullptr;
    }

    SkAutoMalloc queuePropsAlloc(queueCount * sizeof(VkQueueFamilyProperties));
    // now get the actual queue props
    VkQueueFamilyProperties* queueProps = (VkQueueFamilyProperties*)queuePropsAlloc.get();
	//5,選擇佇列簇
    grVkGetPhysicalDeviceQueueFamilyProperties(physDev, &queueCount, queueProps);
    
    //……

    // iterate to find the graphics queue
    const VkDeviceCreateInfo deviceInfo = {
        VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,    // sType
        nullptr,                                 // pNext
        0,                                       // VkDeviceCreateFlags
        queueInfoCount,                          // queueCreateInfoCount
        queueInfo,                               // pQueueCreateInfos
        (uint32_t) deviceLayerNames.count(),     // layerCount
        deviceLayerNames.begin(),                // ppEnabledLayerNames
        (uint32_t) deviceExtensionNames.count(), // extensionCount
        deviceExtensionNames.begin(),            // ppEnabledExtensionNames
        &deviceFeatures                          // ppEnabledFeatures
    };
	//6,創建邏輯設備
    err = grVkCreateDevice(physDev, &deviceInfo, nullptr, &device);
    if (err) {
        SkDebugf("CreateDevice failed: %d\n", err);
        grVkDestroyInstance(inst, nullptr);
        return nullptr;
    }

    auto interface =
        sk_make_sp<GrVkInterface>(getProc, inst, device, extensionFlags);
    if (!interface->validate(extensionFlags)) {
        SkDebugf("Vulkan interface validation failed\n");
        grVkDeviceWaitIdle(device);
        grVkDestroyDevice(device, nullptr);
        grVkDestroyInstance(inst, nullptr);
        return nullptr;
    }

    VkQueue queue;
    grVkGetDeviceQueue(device, graphicsQueueIndex, 0, &queue);

    GrVkBackendContext* ctx = new GrVkBackendContext();
    ctx->fInstance = inst;
    ctx->fPhysicalDevice = physDev;
    ctx->fDevice = device;
    ctx->fQueue = queue;
    ctx->fGraphicsQueueIndex = graphicsQueueIndex;
    ctx->fMinAPIVersion = kGrVkMinimumVersion;
    ctx->fExtensions = extensionFlags;
    ctx->fFeatures = featureFlags;
    ctx->fInterface.reset(interface.release());
    ctx->fOwnsInstanceAndDevice = true;

    return ctx;
}

可以看到,GrVkBackendContext::Create中所作的事情就是初始化Vulkan,初始化的流程和前面介紹如何使用Vulkan中初始化流程都是一樣的,這些都是通用的流程,

初始化完成,我們接著看看Vulkan如何系結Surface,只有系結了Surface,我們才能使用Vulkan進行影像繪制,

//檔案-->/frameworks/base/libs/hwui/renderthread/VulkanManager.cpp
VulkanSurface* VulkanManager::createSurface(ANativeWindow* window) {
    initialize();

    if (!window) {
        return nullptr;
    }

    VulkanSurface* surface = new VulkanSurface();

    VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo;
    memset(&surfaceCreateInfo, 0, sizeof(VkAndroidSurfaceCreateInfoKHR));
    surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR;
    surfaceCreateInfo.pNext = nullptr;
    surfaceCreateInfo.flags = 0;
    surfaceCreateInfo.window = window;

    VkResult res = mCreateAndroidSurfaceKHR(mBackendContext->fInstance, &surfaceCreateInfo, nullptr,
                                            &surface->mVkSurface);
    if (VK_SUCCESS != res) {
        delete surface;
        return nullptr;
    }

    SkDEBUGCODE(VkBool32 supported; res = mGetPhysicalDeviceSurfaceSupportKHR(
                                            mBackendContext->fPhysicalDevice, mPresentQueueIndex,
                                            surface->mVkSurface, &supported);
                // All physical devices and queue families on Android must be capable of
                // presentation with any
                // native window.
                SkASSERT(VK_SUCCESS == res && supported););

    if (!createSwapchain(surface)) {
        destroySurface(surface);
        return nullptr;
    }

    return surface;
}

可以看到,這個創建了VulkanSurface,并系結了ANativeWindow,ANativeWindow是Android的原生視窗,在前面介紹OpenGL進行硬體渲染時,也提到過createSurface這個函式,它是在performDraw被執行的,在這里就不重復說了,

接下來就是呼叫Vulkan的api進行繪制的影像的流程

bool SkiaVulkanPipeline::draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
                              const FrameBuilder::LightGeometry& lightGeometry,
                              LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds,
                              bool opaque, bool wideColorGamut,
                              const BakedOpRenderer::LightInfo& lightInfo,
                              const std::vector<sp<RenderNode>>& renderNodes,
                              FrameInfoVisualizer* profiler) {
    sk_sp<SkSurface> backBuffer = mVkSurface->getBackBufferSurface();
    if (backBuffer.get() == nullptr) {
        return false;
    }
    SkiaPipeline::updateLighting(lightGeometry, lightInfo);
    renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, wideColorGamut, contentDrawBounds,
                backBuffer);
    layerUpdateQueue->clear();

    // Draw visual debugging features
    if (CC_UNLIKELY(Properties::showDirtyRegions ||
                    ProfileType::None != Properties::getProfileType())) {
        SkCanvas* profileCanvas = backBuffer->getCanvas();
        SkiaProfileRenderer profileRenderer(profileCanvas);
        profiler->draw(profileRenderer);
        profileCanvas->flush();
    }

    // Log memory statistics
    if (CC_UNLIKELY(Properties::debugLevel != kDebugDisabled)) {
        dumpResourceCacheUsage();
    }

    return true;
}

最后通過swapBuffers提交繪制內容

void VulkanManager::swapBuffers(VulkanSurface* surface) {
    if (CC_UNLIKELY(Properties::waitForGpuCompletion)) {
        ATRACE_NAME("Finishing GPU work");
        mDeviceWaitIdle(mBackendContext->fDevice);
    }

    SkASSERT(surface->mBackbuffers);
    VulkanSurface::BackbufferInfo* backbuffer =
            surface->mBackbuffers + surface->mCurrentBackbufferIndex;
    GrVkImageInfo* imageInfo;
    SkSurface* skSurface = surface->mImageInfos[backbuffer->mImageIndex].mSurface.get();
    skSurface->getRenderTargetHandle((GrBackendObject*)&imageInfo,
                                     SkSurface::kFlushRead_BackendHandleAccess);
    // Check to make sure we never change the actually wrapped image
    SkASSERT(imageInfo->fImage == surface->mImages[backbuffer->mImageIndex]);

    // We need to transition the image to VK_IMAGE_LAYOUT_PRESENT_SRC_KHR and make sure that all
    // previous work is complete for before presenting. So we first add the necessary barrier here.
    VkImageLayout layout = imageInfo->fImageLayout;
    VkPipelineStageFlags srcStageMask = layoutToPipelineStageFlags(layout);
    VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
    VkAccessFlags srcAccessMask = layoutToSrcAccessMask(layout);
    VkAccessFlags dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;

    VkImageMemoryBarrier imageMemoryBarrier = {
            VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,     // sType
            NULL,                                       // pNext
            srcAccessMask,                              // outputMask
            dstAccessMask,                              // inputMask
            layout,                                     // oldLayout
            VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,            // newLayout
            mBackendContext->fGraphicsQueueIndex,       // srcQueueFamilyIndex
            mPresentQueueIndex,                         // dstQueueFamilyIndex
            surface->mImages[backbuffer->mImageIndex],  // image
            {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}     // subresourceRange
    };

    mResetCommandBuffer(backbuffer->mTransitionCmdBuffers[1], 0);
    VkCommandBufferBeginInfo info;
    memset(&info, 0, sizeof(VkCommandBufferBeginInfo));
    info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
    info.flags = 0;
    mBeginCommandBuffer(backbuffer->mTransitionCmdBuffers[1], &info);
    mCmdPipelineBarrier(backbuffer->mTransitionCmdBuffers[1], srcStageMask, dstStageMask, 0, 0,
                        nullptr, 0, nullptr, 1, &imageMemoryBarrier);
    mEndCommandBuffer(backbuffer->mTransitionCmdBuffers[1]);

    surface->mImageInfos[backbuffer->mImageIndex].mImageLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;

    // insert the layout transfer into the queue and wait on the acquire
    VkSubmitInfo submitInfo;
    memset(&submitInfo, 0, sizeof(VkSubmitInfo));
    submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
    submitInfo.waitSemaphoreCount = 0;
    submitInfo.pWaitDstStageMask = 0;
    submitInfo.commandBufferCount = 1;
    submitInfo.pCommandBuffers = &backbuffer->mTransitionCmdBuffers[1];
    submitInfo.signalSemaphoreCount = 1;
    // When this command buffer finishes we will signal this semaphore so that we know it is now
    // safe to present the image to the screen.
    submitInfo.pSignalSemaphores = &backbuffer->mRenderSemaphore;

    // Attach second fence to submission here so we can track when the command buffer finishes.
    mQueueSubmit(mBackendContext->fQueue, 1, &submitInfo, backbuffer->mUsageFences[1]);

    // Submit present operation to present queue. We use a semaphore here to make sure all rendering
    // to the image is complete and that the layout has been change to present on the graphics
    // queue.
    const VkPresentInfoKHR presentInfo = {
            VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,  // sType
            NULL,                                // pNext
            1,                                   // waitSemaphoreCount
            &backbuffer->mRenderSemaphore,       // pWaitSemaphores
            1,                                   // swapchainCount
            &surface->mSwapchain,                // pSwapchains
            &backbuffer->mImageIndex,            // pImageIndices
            NULL                                 // pResults
    };

    mQueuePresentKHR(mPresentQueue, &presentInfo);

    surface->mBackbuffer.reset();
    surface->mImageInfos[backbuffer->mImageIndex].mLastUsed = surface->mCurrentTime;
    surface->mImageInfos[backbuffer->mImageIndex].mInvalid = false;
    surface->mCurrentTime++;
}

這些流程都和OpenGL是一樣的,初始化,系結Surface,繪制,提交,所以就不細說了,對Vulkan有興趣的,可以深入的去研究,至此Android中的另一個影像生產者Vulkan生產影像的流程也講完了,

結尾

OpenGL,Skia,Vulkan都是跨平臺的圖形生產者,我們不僅僅可以在Android設備上使用,我們也可以在IOS設備上使用,也可以在Windows設備上使用,使用的流程基本和上面一致,但是需要適配設備的原生視窗和緩沖,所以掌握了Android是如何繪制影像的,我們也具備了掌握其他任何設備上是如何繪制影像的能力,

在下一篇文章中,我會介紹Android影像渲染原理的最后一部分:影像緩沖區,這三部分如果都能掌握,我們基本就能掌握Android中影像繪制的原理了,


歡迎關注個人技術公眾號,堅持更新,堅持只寫高質量文章,堅持探索技術的本質,
在這里插入圖片描述

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/101865.html

標籤:其他

上一篇:Cocos Creator 運行時與本地代碼雙向通訊

下一篇:安卓應用啟動頁布局如何高效實作?

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more